Kivy canvas doesn't fit to the parent layout - python

I am trying to make a trial canvas (having just yellow rectangle as bounding box) which should fit to the parent layout (in this case FloatLayout).
At the beginning, the canvas is created at [0,0] of size [100,100]. 2) If I resize by dragging the App window corners, the canvas resizes well. However, 3) & 4) if I maximize or minimize, the canvas fails to resize properly. So as weird canvas size is noticed if I drag too fast. Any help would be highly appreciated :)
Please note create_canvas method an example and it should be in the main.py.
main.py
from kivy.app import App
from kivy.graphics.vertex_instructions import Line
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import InstructionGroup, Color
class TestOut(BoxLayout):
canvas_items = InstructionGroup()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.create_canvas(pos=[dp(150), dp(0)], size=[dp(650), dp(500)])
def on_size(self, *args):
self.can_obj = self.ids.can
# print(self.can_obj.pos, self.can_obj.size)
self.canvas_items.clear()
self.can_obj.canvas.clear()
self.create_canvas(self.can_obj.pos, self.can_obj.size)
def create_canvas(self, pos, size):
self.canvas_items.add(Color(1, 1, 0, 1))
self.canvas_items.add(Line(rectangle=(pos[0], pos[1], size[0], size[1]), width=5))
self.canvas.add(self.canvas_items)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
playground.kv
<TestOut>:
orientation: 'vertical'
Button:
id: button1
text:'A'
height: dp(100)
size_hint: 1, None
pos_hint: {'top':1}
BoxLayout:
orientation: 'horizontal'
Button:
id: button2
text:'B'
width: dp(150)
size_hint: None, 1
FloatLayout:
id: can

The problem is that you want the rectangle size to follow the FloatLayout size, but your on_size() is triggered by changes in the size of TestOut. One easy fix is to just remove the canvas drawing from the python and put the rectangle in the kv, like this:
FloatLayout:
id: can
canvas:
Color:
rgba: 1, 1, 0, 1
Line:
width: 5
rectangle: self.x, self.y, self.width, self.height
A different fix (that leaves the canvas drawing in the python), is to trigger on the size of the FloatLayout. First, change the name of on_size() to something else, say do_on_size():
def do_on_size(self, *args): # changed name to eliminate dependency on size of TestOut
self.can_obj = self.ids.can
# print('on_size:', self.can_obj.pos, self.can_obj.size)
self.canvas_items.clear()
self.can_obj.canvas.clear()
self.create_canvas(self.can_obj.pos, self.can_obj.size)
Then add some code in the PlaygroundApp to set the binding to the size of the FloatLayout:
class PlaygroundApp(App):
title = "blabla"
def build(self):
Clock.schedule_once(self.set_binding)
return TestOut()
def set_binding(self, dt):
fl = self.root.ids.can
fl.bind(size=self.root.do_on_size)

Related

Python kivy position of the Label moves when resizing screen

I'm making an app in python kivy and in my tacscreen I have a draggable Image and an Label, whenever I drag my draggable Image my label drags with it as well. The position of the label is just below the draggable Image. The problem that I'm facing is whenever I resize my window and drag the image the label goes from this to this the space between the Image and the label is too much. How can I fix this? I want the label to always be like how it is in the first screenshot even after I resize the screen and drag. Below is my code. I have been trying to find a solution to this for a months now. I really appreciate any help. Thanks!
main.py
from kivy.properties import BooleanProperty
from kivy.properties import ObjectProperty
from kivy.metrics import dp
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
from kivy.uix.behaviors import DragBehavior
from kivy.uix.image import Image
class DragImage1(DragBehavior, Image):
dragging = BooleanProperty(False)
drag_label = ObjectProperty()
def on_touch_move(self, touch):
if touch.grab_current is self:
self.drag_label.pos = self.x, self.y - dp(300)
return super().on_touch_move(touch)
def on_touch_up(self, touch):
uid = self._get_uid()
if uid in touch.ud:
# do something on drop
print(self.source, 'dropped at', touch.x, touch.y)
return super(DragImage1, self).on_touch_up(touch)
class TacScreen(Screen):
Pass
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name, *args):
self.root.current = screen_name
MainApp().run()
main.kv
#:include tacscreen.kv
ScreenManager:
id: screen_manager
TacScreen:
name: "tac_screen"
id: tac_screen
tacscreen.kv
<tacscreen>:
#:import utils kivy.utils
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "Background.png"
DragImage1:
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
pos: root.center_x - self.width/2, root.center_y - self.height/1.2
You can modify your kv to do the positioning for you:
DragImage1:
id: di # added id for use by the Label
drag_label: per
pos: root.center_x - self.width/2, root.center_y - self.height/0.3
size_hint: 1, .1
source: "Image.png"
Label:
id: per
text: "Hello"
font_size: "20dp"
size_hint: None, None
size: self.texture_size # set size to just contain the text
pos: di.center_x - self.width/2, di.y - self.height # adjust postion based on the DragImage1
And, since kv is now handling the positioning, you should remove the on_touch_move() method from the DragImage1 class.
Note that this will handle positioning when resizing before dragging. But if you drag before resizing, then the resizing will revert the position to that defined in the kv.

Kivy: Adding a label and image to a button for a reusable widget

I am making a reusable widget in kivy that contains a couple of child widgets. One of those child widgets is a button that I want to have a text centered on the button, and a small icon aligned on the right of the button. I am trying to achieve it by adding a StackLayout to the button, but because the button is a widget, the stacklayout's position isn't inside the button. Since I am going to reuse the widget in multiple places, I don't see how I can make the position relative to the parent widget.
With the current example, the image isn't displayed at all and the text is displayed at the bottom of the app.
A complete minimal working example is on https://github.com/dolfandringa/kivy_playground/blob/master/button_label_image/
But this is the relevant code for my widget:
from pathlib import Path
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.stacklayout import StackLayout
ICON = Path(__file__).parent.resolve() / 'caret-down-solid.png'
class MyWidget(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.size_hint_y = None
self.height = '32dp'
self.button = Button()
button_layout = StackLayout()
self.button.add_widget(button_layout)
label1 = Label(text="[color=000000]text1[/color]",
markup=True)
icon = Image(source=str(ICON), size=(16, 16))
button_layout.add_widget(label1)
button_layout.add_widget(icon)
self.add_widget(self.button)
and this is how the widget is being used in a sample app kv file:
#:import MyWidget widgets
GridLayout:
canvas.before:
Color:
rgb: 1,1,1
Rectangle:
size: self.size
cols: 1
Button:
text: "hello"
size_hint_x: None
size_hint_y: None
MyWidget:
size_hint_x: None
width: root.width*0.5
Button:
text: "hello2"
size_hint_x: None
size_hint_y: None
You can change the widget py file like below to achieve your target:
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
Builder.load_string("""
<MyWidget>:
size_hint: 1, None
height: '32dp'
Button:
text: 'text1'
icon_size: 16, 16
canvas:
Rectangle:
source: 'caret-down-solid.png'
size: self.icon_size
pos: self.pos[0] + self.width - self.icon_size[0], self.pos[1] + self.icon_size[1] / 2
""")
class MyWidget(BoxLayout):
pass
Here a custom property icon_size is defined for Button and used it to adjust the size and position of the icon inside Button.
Another option in addition to the one from #amras is to use a RelativeLayout like this:
class MyWidget(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.size_hint_y = None
self.height = '32dp'
self.button = Button(
text="text1",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(1, 1)
)
button_layout = RelativeLayout()
self.add_widget(button_layout)
button_layout.add_widget(self.button)
icon = Image(source=str(ICON), size=(16, 16), size_hint=(None, None))
icon.reload()
icon.pos_hint = {'center_x': 0.95, 'center_y': 0.5}
button_layout.add_widget(icon)

How to draw a rectangle over a button with fixed width in Kivy?

I want to cover a button with a red rectangle in Kivy. This button stands in a BoxLayout. I have defined the MyButton class that draws a rectangle over a button and it works. However, if I fix the width of the BoxLayout the button is in, the rectangle goes to the bottom left corner.
MWE:
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
BoxLayout:
orientation: 'vertical'
# width: 100 # uncommenting these two lines breaks the rectangle positioning
# size_hint: (None,1)
MyButton:
text: 'button1'
size_hint: (1,None)
height: 50
Label:
Label:
''')
class MyButton(Button):
def __init__(self, **kwargs):
super(MyButton, self).__init__(**kwargs)
def on_size(self, *args):
self.canvas.after.clear()
with self.canvas.after:
Color(1, 0, 0, 1)
Rectangle(pos=self.pos, size=self.size)
class MainApp(BoxLayout):
pass
class TestApp(App):
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()
Uncommenting the two commented lines in Builder.load_string breaks the rectangle positioning. What am I missing?
In your MyButton class, you have a on_size() method, but no on_pos() method.
I think you just need to add that method:
def on_pos(self, *args):
self.on_size()

Advanced open and close animation for a button

The animation I currently have for opening and closing a button, is simply a sub button comes out from the main button and expands using size_hint. This animation simply enlarges the sublayout. I would like to 'unveil' the sublayout instead. What I mean by that, is to not mess with size_hint of the sub and instead of expanding itself, I want it to 'unveil' itself which makes for a much cleaner animation. Heres a link of a button animation that uses the same effect. https://codemyui.com/button-to-a-list-animation/.
In the linked animation, the bottom border goes down and reveals the list underneath, the same effect I want.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import *
from kivy.animation import Animation
from kivy.lang.builder import Builder
from kivy.uix.behaviors import *
from kivy.properties import *
Builder.load_string("""
<Root>
Button1:
size_hint: 0.2,0.1
pos_hint: {'top':0.9,'center_x':0.5}
canvas:
Color:
rgba: [0.4,0.4,0.4,1]
Rectangle
size: self.size
pos: self.pos
SubLayout:
pos_hint: {'top':1,'center_x':0.5}
canvas:
Color:
rgba: [0.7,0.7,0.7,1]
Rectangle
size: self.size
pos: self.pos
Color:
rgba: [1,1,1,1]
Ellipse:
pos: self.pos
size: self.size
""")
class Root(FloatLayout):
pass
class Button1(ToggleButtonBehavior, FloatLayout):
def __init__(self,**kwargs):
super(Button1,self).__init__(**kwargs)
def on_state(self, *args):
if self.state == 'down':
anim = Animation(pos_hint={'top':0},d=0.2)
anim += Animation(size_hint=(2,2),d=0.2) #: expanding sublayout, how do I unveil instead?
anim.start(self.children[0])
else:
anim = Animation(size_hint=(1,1),d=0.2)
anim += Animation(pos_hint={'top':1},d=0.2)
anim.start(self.children[0])
class SubLayout(FloatLayout):
pass
class TestApp(App):
def build(self):
return Root()
if __name__ == '__main__':
TestApp().run()
As you can see, SubLayout expands and you can see the ellipse inside expanding with the button. What I would like is for the borders to move away, revealing whats underneath them while leaving the ellipse's size the same, which is the 'unveiling' effect. Any ideas on how to do this would be appreciated. Maybe something to do with an Fbo?

What is a good way to handle widget placement to account for screen rotation using python / kivy?

When I build an application using kivy, I can get everything where I want it, but when I rotate the screen from portrait to landscape, my widgets start colliding with one another. What is a good way to prevent this from happening?
In the attached example, I was able to bind the placement of a settings button to my header label widget, but I was unsuccessful in getting my scrollview to bind to it so when it rotates it stays X amount of space away from the bottom of the label.
Python:
class First_Screen(Screen):
def __init__(self, **kwargs):
super(First_Screen, self).__init__(**kwargs)
list_of_buttons = [
'button1',
'button2',
'button3',
'button4',
'button5',
'button6 ',
'button7',
'button8',
]
class My_Grid(FloatLayout, ScrollView):
grid = ObjectProperty(None)
def on_grid(self, *args):
for btn in list_of_buttons:
btn = Button(
text = btn,
)
btn.bind(on_press= First_Screen.print_message)
self.grid.add_widget(btn)
def print_message(self):
print ('Congratulations! You have clicked a button!')
class ScreenManagement(ScreenManager):
pass
class Stack_Overflow(App):
def build(self):
sm = ScreenManager(transition = FadeTransition())
sc0 = First_Screen(name = 'first_screen')
sm.add_widget(sc0)
return sm
if __name__ == '__main__':
Stack_Overflow().run()
kv:
ScreenManagement:
First_Screen:
<ImageButton#ButtonBehavior+Image>:
<First_Screen>:
name: 'first_screen'
Label:
id: header
text: 'header'
pos_hint: ({'left' : 1, 'top' : 1})
size_hint: (1,None)
height: dp(50)
canvas.before:
Color:
rgba: (.6, .6, .6, 1)
Rectangle:
pos: self.pos
size: self.size
ScrollView:
size_hint: (1,.8)
pos_hint: ({'center_x' : .5, 'center_y' : .5})
My_Grid:
grid: grid
GridLayout:
id: grid
cols: 1
size_hint_y: None
row_default_height: "40dp"
height: self.minimum_height
ImageButton:
id: settings
size_hint: None, None
height: dp(30)
width: dp(30)
pos: header.x, header.y + 10
pos_hint: {'right': 1}
source: 'settings_black.png'
Images: portrait, landscape
Thanks!
Edit: I misunderstood the question, I thought you were unsatisfied with the Image. In the case of ScrollView the problem isn't the ScrollView itself, because that uses relative positions and sizes.
The problem is the Label(header), which has height set as an absolute number i.e. 50 dense pixels. It means that whatever screen you have or however you size anything else, this widget will have 50 pixels corrected with dpi of the screen.
In reality this means that if your screen reaches 400x100 resolution, your Label will take half of the screen and ScrollView will be placed on top of that (most likely you won't see the Label then).
To fix that you have two options. If you want to play with FloatLayout alone, you'll need to use correct positioning/sizing i.e. relative and make sure widgets aren't overlaping.
Otherwise just drop the widgets to BoxLayout or a similar other layout that manages such things on its own:
class First_Screen(BoxLayout, Screen):
def __init__(self, **kwargs):
super(First_Screen, self).__init__(orientation='vertical', **kwargs)
or a little bit different (and better looking):
class First_Screen(BoxLayout, Screen):
def __init__(self, **kwargs):
and in kv:
<First_Screen>:
name: 'first_screen'
orientation: 'vertical'
Also note, that there's a screen module, which makes testing such things a way more easier than dropping the code to apk and to android when it's just unnecessary. Example:
python main.py -m screen:note2,landscape,scale=.5

Categories