Kivy Custom Button on_press change canvas colour - python

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

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.

Kivy rectangles are stuck at bottom left

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

How to use Kivy Rotate in pure Python code (not kvlang)?

I want to rotate a button with the pure python code instead of the kvlang.
With kvlang we can rotate a button as shown in the example.
The button is rotated 45 degree around its own center. Below is the original code:
from kivy.app import App
from kivy.lang import Builder
kv = '''
FloatLayout:
Button:
text: 'hello world'
size_hint: None, None
pos_hint: {'center_x': .5, 'center_y': .5}
canvas.before:
PushMatrix
Rotate:
angle: 45
origin: self.center
canvas.after:
PopMatrix
'''
class RotationApp(App):
def build(self):
return Builder.load_string(kv)
RotationApp().run()
But when I try to rewrite this example with the pure python code as below, the button is rotated around somewhere else, as shown here:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
Rotate(origin=self.center, angle=45)
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
The above two pieces of code are not producing the same result. Is there anything we did wrong?
UPDATE:
The puzzle is solved as suggested by #inclement, solution as below:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.graphics import PushMatrix, PopMatrix, Rotate
class MyButton(Button):
def __init__(self):
super().__init__()
self.text = 'hello world'
self.size_hint = (None, None)
self.pos_hint = {'center_x': .5, 'center_y': .5}
with self.canvas.before:
PushMatrix()
# Rotate(origin=self.center, angle=45) # previous approach
self.rotation = Rotate(origin=self.center, angle=45)
self.bind(center=lambda _, value: setattr(self.rotation, "origin", value))
with self.canvas.after:
PopMatrix()
class RotationApp(App):
def __init__(self):
super().__init__()
self.layout = FloatLayout()
self.button = MyButton()
self.layout.add_widget(self.button)
def build(self):
return self.layout
RotationApp().run()
In your Python code, self.center is evaluated just once during the __init__. In the kv code, a binding is automatically created to reset the Rotate instruction's origin property every time it changes.
You need to put that missing functionality in the Python code, something like self.rotation = Rotate(...) and self.bind(center=lambda instance, value: setattr(self.rotation, "origin", value)) (although I'm sure you can think of a nicer way to set that up, that's just the inline example).

Can't move my widget position which writed in .py file

Hello and thanks for reading. I have trouble about move widget position on screen. Actually i checked widget's position, its changing as i want but at the screen i can't see any changing. I tried this too, in for loop self.remove_widget(i) and then self.add_widget(i). But there is nothing changed too.
.py file :
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.uix.widget import Widget
from kivy.graphics import Color,Rectangle
from random import randint
class Page(Screen):
def on_enter(self, *args):
self.list1 = []
self.list2 = []
self.currentball = 0
def add(self):
self.currentball += 1
self.list1.append('Ball[{}]'.format(self.currentball))
self.list2.append(self.list1[len(self.list1)-1])
print('List1 => ',self.list1)
print('List2 => ',self.list2)
#Widget oluşturalım:
self.list2[len(self.list1)-1] = Widget(size_hint=(.1,.1),pos=(randint(100,200),randint(250,500)))
with self.list2[len(self.list1)-1].canvas:
Color(1,1,1,1)
Rectangle(source='worker.png',size=self.list2[len(self.list1)-1].size,pos=self.list2[len(self.list1)-1].pos)
self.add_widget(self.list2[len(self.list1)-1])
def move(self):
for i in self.list2:
i.pos[0] += randint(0,20)
print(i.pos)
def remove(self):
self.update()
class ScMn(ScreenManager):
pass
class test2(App):
def build(self):
return ScMn()
if __name__ == '__main__':
test2().run()
.kv file :
<ScMn>:
Page:
name: 'page'
id: pageid
<Page>:
RelativeLayout:
Button:
size_hint: .1,.1
pos: 0,0
text: 'Add'
on_press: root.add()
Button:
size_hint: .1,.1
pos_hint:{'x':.1,'y':0}
text: 'Move'
on_press: root.move()
Button:
size_hint: .1,.1
pos_hint:{'x':.2,'y':0}
text: 'RemoveLast'
on_press: root.remove()
Thanks for answering already...
Implement a custom widget class, e.g. MyWidget() with method to update pos and size
Replace Widget(...) with MyWidget(...)
Remove with self.list2[len(self.list1)-1].canvas: ... because it is done in the new class MyWidget()
Snippets
class MyWidget(Widget):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=self.size)
self.bind(pos=self.update_rect, size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
class Page(Screen):
...
def add(self):
...
self.list2[len(self.list1) - 1] = MyWidget(size_hint=(.1, .1), pos=(randint(100, 200), randint(250, 500)))
self.add_widget(self.list2[len(self.list1) - 1])

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.

Categories

Resources