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.
Related
I would like to draw something in canvas from .py by using class 'BotRightCanvas'. The initial widgets are all defined in .kv
Problem is when I finally declare BotRightCanvas in .kv, the size of the canvas widget is not coming proper (still 100x100). I would like the green rectangle to coincide with the cyan one.
Here's .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(0, 1, 0, 1)
Line(circle=(self.width / 2, self.height / 2, 25), width=2)
Line(rectangle=(0, 0, self.width, self.height), width=5)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
if __name__ == "__main__":
PlaygroundApp().run()
and .kv
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
I tried printing the final canvas shape in console, seems it initializes with size of 0x0 but later on gets correct size 650x500.
The console output
canvas [0.0, 0]
cSize [0.0, 0]
win (800, 600)
canvas [650.0, 500.0]
cSize [650.0, 500.0]
Any help? Thanks !!
EDIT: Would like to control canvas items from .py
from kivy.graphics import Color
from kivy.graphics.vertex_instructions import Line, Rectangle, Ellipse
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivy.app import App
class TestOut(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def getSize(self, id):
cSize = id.size
print('win ' + str(Window.size))
print('canvas ' + str(id.size))
print('cSize ' + str(cSize))
class BotRightCanvas(Widget):
pass
Builder.load_string(
'''
<BotRightCanvas>:
canvas:
Color:
rgba:0, 1, 0, 1
Line:
circle:self.width / 2, self.height / 2, 25
width:2
Line:
rectangle:0, 0, self.width, self.height
width:5
<TestOut>:
BoxLayout:
orientation: 'vertical'
Button:
text:'A'
height: dp(100)
size_hint: 1, None
BoxLayout:
Button:
text:'B'
width: dp(150)
size_hint: None, 1
RelativeLayout:
id: rel_lo
on_size: root.getSize(rel_lo)
Widget:
canvas:
Line:
rectangle: (0,0, self.width, self.height)
width: 2
canvas.before:
Color:
rgba: 0,1,1,1
BotRightCanvas:
size:rel_lo.size
'''
)
class PlaygroundApp(App):
title = "blabla"
def build(self):
return TestOut()
for line in open('2.py').readlines():
print(' '+line)
if __name__ == "__main__":
PlaygroundApp().run()
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.
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
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()
I have a couple of questions:
I have animation of spinning ball which should be always on top of screen and screen should always show only half of it. I can do it but only by clicking buttom to call function which take ball to the right place. I need ball to be always in right place not only then i click the button. I tried to use init function but it always give me this error: 'super' object has no attribute 'getattr'. I also tried to use getattr instead of init but it doesn't take ball to the right place.
So I want click a button and ball start spins for a couple of seconds. When it stops I want ball to be another color or even another image of ball. I tried to use on_complete but I don't understand where I should use it.
My main py file:
from kivy.app import App
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.properties import NumericProperty
class OpenScreen(Screen):
angle = NumericProperty(0)
#Trying to make __init__ function
#def __init__(self, **kwargs):
#super(OpenScreen, self).__init__(**kwargs)
#y = self.height
#y1 = self.width / 2
#self.ids.test.pos = 0, y - y1
#Function of taking ball in the right place and spinning it
def z(self):
anim = Animation(angle=360, duration=0.5)
anim += Animation(angle=360, duration=0.5)
anim.start(self)
y = self.height
y1 = self.width / 2
self.ids.test.pos = 0, y - y1
self.ids.test.source = 'redball.png'
def on_angle(self, item, angle):
if angle == 360:
item.angle = 0
...screens classes...
GUI = Builder.load_file('game.kv')
class GameApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids['screen_manager']
screen_manager.current = screen_name
GameApp().run()
My Openscreen.kv file:
#:kivy 1.10.1
<OpenScreen>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'bg.png'
Image:
id: test
size_hint_y: None
height: self.width
source: 'blueball.png'
allow_stretch: True
keep_ratio: False
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: self.center
canvas.after:
PopMatrix
Button:
text: "spin"
font_size: self.height - 29
valign: 'middle'
halign: 'center'
padding: 2,2
size_hint: .7, .1
pos_hint:{"x":.15, "y":.585}
background_color: 0,0,0,0
on_press:
root.z()
....
How I can do this?
You can start the animation using Clock.schedule_once() as:
def __init__(self, **kwargs):
super(OpenScreen, self).__init__(**kwargs)
Clock.schedule_once(self.z)
You will need to add a dt argument to the z() method and utilize the on_complete event as:
def z(self, dt):
anim = Animation(angle=360, duration=0.5)
anim += Animation(angle=360, duration=0.5)
anim.bind(on_complete=self.switch_source)
anim.start(self)
Then use a switch_source() method as:
def switch_source(self, *args):
y = self.height
y1 = self.width / 2
self.ids.test.pos = 0, y - y1
self.ids.test.source = 'redball.png'