Kivy rectangles are stuck at bottom left - python

I am trying to add buttons to a kivy gridlayout with a rectangle shape above each of them. But all the rectangles seem to stack up at the bottom left of the screen, nowhere near the buttons. I think multiple rectangles are being created, but all of them are placed above each other, and not over the button. How do I fix it?
main.py:
from kivymd.app import MDApp
from kivy.app import App
from kivy.graphics import Ellipse, Rectangle, Color
from kivy.uix.button import ButtonBehavior, Button
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.metrics import dp, sp
Window.size = (550, 730)
class Recta(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas.after:
Color(1., 1., 0)
Rectangle(pos=self.pos, size=(self.width, self.height))
class Grid(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rows = 8
self.cols = 8
self.size_hint = None, None
if Window.height > Window.width:
self.width = Window.width
self.height = Window.width
elif Window.height < Window.width:
self.width = Window.height
self.height = Window.height
elif Window.height == Window.width:
self.width = Window.width
self.height = Window.height
self.pos_hint = {"center_x": 0.5, "center_y": 0.5}
for i in range(64):
self.add_widget(Recta())
class TestApp(MDApp):
def build(self):
grid_w = Grid()
return grid_w
if __name__ == '__main__':
TestApp().run()

The problem is that you are drawing the Rectangle in the __init__() method using pos and size of the Recta widget. In the __init__(), the pos and size of a widget have not yet been set, and are still the default values of (0,0) and (100,100).
You can fix this in either of two ways. The easiest is just to define the Rectangle using kv like this:
Builder.load_string('''
<Recta>:
canvas.after:
Color:
rgb: 1,1,0
Rectangle:
pos: self.pos
size: self.size
''')
and remove the drawing of the Rectangle from the __init__() method
The second way is to create the bindings (that kv does automatically) like this:
class Recta(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas.after:
Color(1., 1., 0)
self.rect = Rectangle(pos=self.pos, size=(self.width, self.height))
self.bind(pos=self.update)
self.bind(size=self.update)
def update(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size

Related

cannot put the Button in center position by widget class initialziation

I am trying to initialize the widget class by placing a button at the root.center. Strangely, root.size during initialization is only (100,100). When it's rendered, the actual size is (600,800). So my button is placed in the left bottom instead of in the center.
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.uix.widget import Widget
w = Builder.load_string('''
<MyWidget>:
canvas.before:
Color:
rgba: 1, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
Button:
id: score_button
center: root.score_button_center
text:'Best Scores'
''')
class MyWidget(Widget):
score_center_y = NumericProperty(-100)
score_center_x = NumericProperty(-100)
score_button_center = ReferenceListProperty(score_center_x, score_center_y)
hide_center = (-100, -100)
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
self.score_button_center = self.width, self.height
class MyApp(App):
def build(self):
g = MyWidget()
return g
if __name__ == '__main__':
MyApp().run()
Just change:
center: root.score_button_center
to:
center: root.center
That will take advantage of the kivy feature that sets up bindings to adjust the Button position whenever the root widget changes size or position.

How to create a circular or round button in Kivy using. Kv and without using. Kv file

I am a newbie to Kivy and I want to know how can we create the circular or rounded button using Kivy (with and without .kv file).
There are many ways to create a circular Button. Here is just one way:
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
class CircularButton(FloatLayout):
button = ObjectProperty()
def __init__(self, **kwargs):
super(CircularButton, self).__init__()
# all size and position attributes get assigned to the CircularButton
# the below is not an exhaustive list
if 'size_hint' in kwargs:
self.size_hint = kwargs.pop('size_hint', (1,1))
if 'size' in kwargs:
self.size = kwargs.pop('size', (100, 100))
if 'width' in kwargs:
self.width = kwargs.pop('width', 100)
if 'height' in kwargs:
self.height = kwargs.pop('height', 100)
if 'pos_hint' in kwargs:
self.pos_hint = kwargs.pop('pos_hint', (None, None))
if 'pos' in kwargs:
self.pos = kwargs.pop('pos', (0,0))
if 'x' in kwargs:
self.x = kwargs.pop('x', 0)
if 'y' in kwargs:
self.y = kwargs.pop('y', 0)
# remaining args get applied to the Button
self.butt_args = kwargs
Clock.schedule_once(self.set_button_attrs)
def set_button_attrs(self, dt):
for k,v in self.butt_args.items():
setattr(self.button, k, v)
Builder.load_string('''
<CircularButton>:
button: button
canvas.before:
StencilPush
Ellipse:
# self.pos and self.size should work here
# adjustments are just hacks to get it to look right
pos: self.x+1, self.y+2
size: self.width-2, self.height-2
StencilUse
canvas.after:
StencilUnUse
Ellipse:
pos: self.x+1, self.y+2
size: self.width-2, self.height-2
StencilPop
Button:
id: button
pos_hint: {'center_x':0.5, 'center_y':0.5}
size_hint: None, None
size: root.size
''')
if __name__ == '__main__':
from kivy.app import App
class TestApp(App):
def build(self):
return CircularButton(text='Circular Button', size_hint=(None, None), size=(150, 150), pos_hint={'right':1, 'top':1}, on_release=self.butt)
def butt(self, *args):
print('button pressed')
TestApp().run()

use collide_point with a rotated widget

I'm trying to rotate a widget and then use the method collide_point to check if I'm touching the widget or not
The problem is that when I rotate the widget, the canvas (image) works fine, but the widget itself doesn't seems to rotate, so the check fails
.py:
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
class Game(Widget):
pass
def on_touch_down(self, touch):
if self.player.collide_point(*touch.pos):
print("collide")
class TestApp(App):
def build(self):
Window.size = 300, 300
self.game = Game()
return self.game
if __name__ == "__main__":
TestApp().run()
.kv:
<Game>:
player: player
Widget:
id: player
angle: 50
size: 40, 200
canvas:
Color:
rgba: 1,0.5,0,1
Rectangle:
pos: self.pos
size: self.size
canvas.before:
PushMatrix
Rotate:
axis: 0, 0, 1
angle: self.angle
origin: self.right, self.top
canvas.after:
PopMatrix
I expect to print collide when I touch in the image, but only print collide when I touch where the image was before the rotation
edited:
I must do someting like this? (it works, but there is not an easy way?)
def on_touch_down(self, touch):
top_center = Vector(self.player.center_x, self.player.top)
touch_pos = Vector(*touch.pos)
top2touch = touch_pos - top_center
top2touch_rotate = top2touch.rotate(-self.player.angle)
correct_touch = top_center + top2touch_rotate
if self.player.collide_point(*correct_touch):
print("collide")

How to assign a canvas to multiple widgets in Kivy

I would like to give all of the widgets on the screen a white canvas. I know how to create the intended canvas in KV but in this case I must use Python code.
In my code, I tried self.cavas.add(...) and in the code below, I use with self.canvas:. Both attempts resulted in a canvas being drawn in the corner of the screen, rather than inside of the widgets.
How do I put a canvas to be inside of every widget using Python code?
Code:
from kivy.app import App
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from random import random
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.clock import Clock
class LittleButtons(Button):
dur = 2
def reup(self, *args):
Animation.cancel_all(self)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
def __init__(self, **kwargs):
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = self.width
self.background_color = [0,0,0,.05]
with self.canvas:
Color(rgba = [1,1,1,.2])
Rectangle(pos = self.pos, size = self.size)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
KV = Builder.load_string("""
#:import Factory kivy.factory.Factory
Screen:
FloatLayout:
on_parent:
(lambda ltext: [self.add_widget(Factory.LittleButtons(text=ltext)) for i in range (150)])('hi!')
Button:
background_color: 0,0,0,0
canvas:
Color:
rgba: 0,1,1,1
Rectangle:
pos: self.pos
size:self.size
""")
class MyApp(App):
def build(self):
return KV
if __name__ == '__main__':
MyApp().run()
The problem is that in the LittleButtons __init__() method, its position is still the default of (0,0), so the position of the Rectangle is set to (0,0). When you use KV, it cleverly binds the Rectangle position to the LittleButtons position when you reference self.pos. Unfortunately, in a .py file, you must provide that binding yourself. So, here is a modification of your LittleButtons that should handle the position changes:
class LittleButtons(Button):
dur = 2
def reup(self, *args):
Animation.cancel_all(self)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
def __init__(self, **kwargs):
self.rect = None
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = self.width
self.background_color = [0,0,0,.05]
with self.canvas:
Color(rgba = [1,1,1,.2])
self.rect = Rectangle(pos = self.pos, size = self.size)
Animation(pos_hint = {'center_x': random(), 'center_y': random()}, duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
def on_pos(self, instance, pos):
if self.rect is not None:
self.rect.pos = pos
The changes are the addition of a self.rect attribute and a on_pos method.
You can add something similar if you need to handle changing size.

Kivy Custom Button on_press change canvas colour

Further to my last question, I have been looking at the documentation and online for examples for how to change the canvas color of a custom button on press. Here is what I have; nothing changes when clicked:
class CircularButton(ButtonBehavior, Label):
# code inspired from:
# https://github.com/kivy/kivy/issues/4263#issuecomment-217430358
# https://stackoverflow.com/a/42886979/6924364
# https://blog.kivy.org/2014/10/updating-canvas-instructions-declared-in-python/
def __init__(self, **kwargs):
super(CircularButton,self).__init__(**kwargs)
with self.canvas.before:
Color(rgba=(.5,.5,.5,.5))
self.shape = Ellipse(pos=self.pos,size=self.size)
self.bind(pos=self.update_shape, size=self.update_shape)
def update_shape(self, *args):
self.shape.pos = self.pos
self.shape.size = self.size
def on_press(self, *args): #<--- what am I doing wrong here?
with self.canvas:
Color(rgba=(0,0,0,0))
def collide_point(self, x, y):
return Vector(x, y).distance(self.center) <= self.width / 2
You have to store and reuse the Color instruction and change the color as Canvas adds the instructions, in your case you are adding a new Color instruction that does not apply to another element like Rectangle or Ellipse so you do not see the effect.
from kivy.base import runTouchApp
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.label import Label
from kivy.vector import Vector
from kivy.graphics import Color, Ellipse
from kivy.properties import ListProperty
class CircularButton(ButtonBehavior, Label):
background_color = ListProperty((0.5,.5,.5,.5))
def __init__(self, **kwargs):
super(CircularButton,self).__init__(**kwargs)
self.draw()
self.text='test'
def update_shape(self, *args):
self.shape.pos = self.pos
self.shape.size = self.size
def on_background_color(self, *args):
self.shape_color.rgba = self.background_color
def draw(self, *args):
with self.canvas.before:
self.shape_color = Color(rgba=(0.5,.5,.5,.5))
self.shape = Ellipse(pos=self.pos,size=self.size)
self.bind(pos=self.update_shape, size=self.update_shape)
def on_press(self, *args):
self.background_color= (1, 0, 0, 1)
def on_release(self, *arg):
self.background_color = (0.5,.5,.5,.5)
def collide_point(self, x, y):
return Vector(x, y).distance(self.center) <= self.width / 2
if __name__ == '__main__':
runTouchApp(CircularButton())
Although I prefer to combine the .kv and the .py taking advantage of the kv language is declarative making the connections are simple:
from kivy.base import runTouchApp
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.label import Label
from kivy.vector import Vector
from kivy.lang import Builder
Builder.load_string('''
<CircularButton>
background_color: 0.5,.5,.5,.5
canvas.before:
Color:
rgba: self.background_color
Ellipse:
pos: self.pos
size: self.size
''')
class CircularButton(ButtonBehavior, Label):
def __init__(self, **kwargs):
super(CircularButton, self).__init__(**kwargs)
self.text = "test"
def on_press(self, *args):
self.background_color = (1, 0, 0, 1)
def collide_point(self, x, y):
return Vector(x, y).distance(self.center) <= self.width / 2
if __name__ == '__main__':
runTouchApp(CircularButton())
Maybe helps someone this solution:
.py
class PositionButton(Button):
b_color = ListProperty()
.kv
<PositionButton>:
background_color: [0,0,0,0]
b_color: [0, 0, 1, .3] if self.state == 'normal' else [0, 0, 1, .5]
canvas.before:
Color:
rgba: self.b_color

Categories

Resources