Hi there when i run my code , it is supposed to show a rectangle shape moving up and down. However, i can only get the rectangle shape in static mode (not moving). However, according to the function under the widget class, it should work. Any help will be aprreciated !
countdown.py file
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen, ScreenManager
class WindowManager(ScreenManager):
pass
class GameWindow(Screen):
def __init__(self, **kwargs):
super(GameWindow, self).__init__(**kwargs)
self.add_widget(Root())
self.add_widget(Rect())
class Root(Widget):
pass
class Rect(Widget):
velocity = ListProperty([10, 2])
def __init__(self, **kwargs):
super(Rect, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1/8)
def update(self, *args):
self.y += self.velocity[1]
if self.y < 0 or (self.y + self.height) > 250:
self.velocity[1] *= -1
kys = Builder.load_file("countdown.kv")
class MyMainApp(App):
def build(self):
return kys
if __name__ == "__main__":
MyMainApp().run()
countdown.kv file
WindowManager:
GameWindow:
<GameWindow>:
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: (42,15)
Rect:
pos: 85, 100
A Widget has some of the following characteristics:
If you are placed as a child of another Widget (Screen, Spinner, etc) this will take as much space as you can since the size_hint is (1,1), in your case it occupies the entire space of the Screen.
It cannot be distinguished since it does not have a background color or other visual element.
With the above mentioned it follows that even when "Rect" which is a Widget was moved this cannot be distinguished so we must use the canvas for its painting (I suppose that is what you tried when using the canvas in GameWindow), in addition to set a container independent size should be set the size_hint to (None, None).
Considering the above, the solution is:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen, ScreenManager
class WindowManager(ScreenManager):
pass
class GameWindow(Screen):
pass
class Rect(Widget):
velocity = ListProperty([10, 2])
def __init__(self, **kwargs):
super(Rect, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1. / 8.)
def update(self, *args):
self.y += self.velocity[1]
if self.y < 0 or (self.y + self.height) > 250:
self.velocity[1] *= -1
kys = Builder.load_file("countdown.kv")
class MyMainApp(App):
def build(self):
return kys
if __name__ == "__main__":
MyMainApp().run()
WindowManager:
GameWindow:
<GameWindow>:
Rect:
<Rect>:
size_hint : None, None
size : 42, 15
canvas:
Color:
rgba: 1, 0, 0, 1
Rectangle:
pos: self.pos
size: self.size
Related
I'm trying to make an app where I can display a circular progressbar, I've been told to use a FloatLayout so the arrangements stay organized no matter the size of the mobile screen. I've used this example for the circular progressbar How to make circular progress bar in kivy? , however, I can't seem to put the progressbar within the FloatLayout so that it organizes itself as I change the size of the screen. Anyone has a suggestion?
EDIT: I want the progressbar to stay at the center of the window, relatively based on the current window size and height. With the current code, when I change the size of the window, the progressbar doesn't relocate itself to the middle.
Here's the code I'm using:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.progressbar import ProgressBar
from kivy.core.text import Label as CoreLabel
from kivy.lang.builder import Builder
from kivy.graphics import Color, Ellipse, Rectangle
from kivy.clock import Clock
class CircularProgressBar(ProgressBar, FloatLayout):
def __init__(self,**kwargs):
super(CircularProgressBar,self).__init__(**kwargs)
self.thickness = 40
self.label = CoreLabel(text="0", font_size=self.thickness, )
self.texture_size = None
self.refresh_text()
self.draw()
def draw(self):
with self.canvas:
self.canvas.clear()
Color(0.26,0.26,0.26)
Ellipse(pos=self.pos, size=self.size)
Color(1,0,0)
Ellipse(pos=self.pos,size=self.size,angle_end=(self.value/100.0)*360)
Color(0,0,0)
Ellipse(pos=(self.pos[0] + self.thickness / 2, self.pos[1] + self.thickness / 2),size=(self.size[0] - self.thickness, self.size[1] - self.thickness))
Color(1, 1, 1, 1)
Rectangle(texture=self.label.texture,size=self.texture_size,pos=(self.size[0]/2-self.texture_size[0]/2,self.size[1]/2 - self.texture_size[1]/2))
self.label.text = str(int(self.value))
def refresh_text(self):
self.label.refresh()
self.texture_size=list(self.label.texture.size)
def set_value(self, value):
self.value = value
self.label.text = str(int(self.value))
self.refresh_text()
self.draw()
class MainWindow(Screen, Widget):
current = ""
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("main.kv")
sm = WindowManager()
screens = [MainWindow(name="main")]
for screen in screens:
sm.add_widget(screen)
sm.current = "main"
class Main(App):
def animate(self,dt):
circProgressBar = self.root.get_screen('main').ids.cp
if circProgressBar.value < 99:
circProgressBar.set_value(circProgressBar.value+1)
else:
circProgressBar.set_value(0)
def build(self):
Clock.schedule_interval(self.animate, 0.1)
return sm
if __name__ == "__main__":
Main().run()
Here is the .kv file:
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
FloatLayout:
CircularProgressBar:
id: cp
pos: 250, 250
size_hint:(None,None)
height:200
width:200
max:100
You can accomplish that by using pos_hint to set the position of the CircularProgressBar, and then making sure that everything in your draw() method is based on that position. For example, here is pos_hint in your kv:
WindowManager:
MainWindow:
<MainWindow>:
name: "main"
FloatLayout:
CircularProgressBar:
id: cp
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
size_hint:(None,None)
height:200
width:200
max:100
Then your draw() method can be:
def draw(self):
with self.canvas:
self.canvas.clear()
Color(0.26,0.26,0.26)
Ellipse(pos=self.pos, size=self.size)
Color(1,0,0)
Ellipse(pos=self.pos,size=self.size,angle_end=(self.value/100.0)*360)
Color(0,0,0)
Ellipse(pos=(self.pos[0] + self.thickness / 2, self.pos[1] + self.thickness / 2),size=(self.size[0] - self.thickness, self.size[1] - self.thickness))
Color(1, 1, 1, 1)
Rectangle(texture=self.label.texture,size=self.texture_size,pos=(self.pos[0]-self.texture_size[0],self.center[1] - self.texture_size[1]/2))
self.label.text = str(int(self.value))
The only modification to your draw() method is in the pos of the Rectangle.
I just started to learn Kivy so I am still familiar with its functionalities.
I am trying to put an image as a background to my app main page. This is what I did:
class Prin(BoxLayout):
def __init__(self,**kwargs):
super(Prin,self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.center, size=(self.width, self.height))
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint =(0.5, 0.1), pos_hint ={'center_x':.5, 'y':.5}, padding=(10, 0), on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self,event):
self.text.text = self.text.text+ "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
The desired behavior is an image covering the whole screen, that's why I've put pos=self.center, size=(self.width, self.height)
This is the output:
So I have two questions:
1/ Why is the image appearing in the left bottom side ? What widget is actually there ? I am supposed to have only a BoxLayout with 2 widgets in a vertical orientation. I don't understand what is there.
2/ What should why put in size and pos to have the desired output ?
I would recommend putting all graphic elements in a .kv file, so there are fewer imports and it looks better.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
KV = ("""
<Prin>
BoxLayout:
orientation: 'vertical'
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'test.png'
Label:
id: label
text: 'TEXT'
Button:
text: 'Start!'
size_hint: None, None
size_hint: 0.5, 0.1
pos_hint: {'center_x': .5, 'center_y': .5}
padding: 10, 0
on_press: root.start()
""")
class Prin(BoxLayout):
Builder.load_string(KV)
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
def start(self):
self.ids.label.text += "\nNew line"
class MainApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = MainApp()
app.run()
If you still want to do this not in the kv file, then the problem is in self.size, by default, this value is [100, 100], only after calling the class and adding it to the main window it changes.
from kivy.core.window import Window
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
with self.canvas.before:
Rectangle(source='test.png', pos=self.pos, size=Window.size)
print(self.size) # [100, 100]
...
class MainApp(App):
def build(self):
self.screen = Prin()
return self.screen
def on_start(self):
print(self.screen.size) # [800, 600]
And don't forget about imports when you ask a question, the code should run without any manipulation
In response to your questions:
The image is appearing in that position because pos=layout.center is not a valid position and so instead sets it to a default value ([100, 100] I believe). To fix this, change pos=layout.center to pos=layout.pos
Your size is the default value also! This is getting a little technical but when you initialise your Prin class you are specifying the size of the Rectangle to be the current size of the BoxLayout. However, since it has not been initialised yet, the BoxLayout doesn't yet have a size! Again, Kivy handles this by giving it a default size.
Why are my Buttons and Labels correct?
Kivy automatically binds the children of a BoxLayout to the size and position of the BoxLayout. This binding ensures that when the position and size of the BoxLayout are changed, so too are the widgets within it (https://kivy.org/doc/stable/api-kivy.event.html).
Why doesn't Kivy bind the rectangle?
This has something to do with the canvas. The canvas is a drawing instruction shared by a widget, and not a property of any individual widget. Hence you'll programmatically bind your rectangle to the BoxLayout. (https://kivy.org/doc/stable/api-kivy.graphics.instructions.html)
How do I achieve this binding you speak of?
Two ways. Firstly (preferred), you can define your widgets in the KV language as this will automatically handle any binding you wish. Second, you can create an 'on_size' callback. Something like:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.graphics import Rectangle
class Prin(BoxLayout):
def __init__(self, **kwargs):
super(Prin, self).__init__(**kwargs)
layout = BoxLayout(orientation='vertical')
with self.canvas:
self.rect = Rectangle(source='test.png', pos=layout.pos, size=self.size)
self.text = Label(text='Press start')
fb = Button(text='Start!', size_hint=(0.5, 0.1), pos_hint={'center_x': .5, 'y': .5}, padding=(10, 0),
on_press=self.start)
layout.add_widget(self.text)
layout.add_widget(fb)
self.add_widget(layout)
def start(self, *_):
self.text.text = self.text.text + "\nNew line"
def resize(self, *_):
widgets = self.children[:]
self.canvas.clear()
self.clear_widgets()
with self.canvas:
self.rect = Rectangle(source='test.png', pos=self.pos, size=self.size)
for widget in widgets:
self.add_widget(widget)
on_size = resize
class TestApp(App):
def build(self):
return Prin()
if __name__ == "__main__":
app = TestApp()
app.run()
I just would like to add as a BIG P.S. although the above code solves your problem, it does so in probably the least efficient way imaginable. It is far better to define your widget in the kv file.
Hello I'm relatively new to kivy. So far doing basic stuff has been relatively straightforward but this has stumped me. I'm making an app that needs to dynamically add rectangular canvas items to a grid in a scrollview. Since I'm doing this I need to create the scrollview in python and not in the .kv file. How can I do this so that the size of the rectangles will be the same as the window size upon resizing the windows?
.py file:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line,Rectangle
from kivy.uix.carousel import Carousel
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
class Scroll(ScrollView):
def __init__(self, **kwargs):
super(Scroll, self).__init__(**kwargs)
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
layout.bind(minimum_height=layout.setter('height'))
# Make sure the height is such that there is something to scroll.
for i in range(100):
SkillStat = RelativeLayout(pos=(0,0), height=100, size_hint_y=None, size_hint_x=self.width)
with SkillStat.canvas:
Rectangle(pos=self.pos,size=(self.width, 90))
layout.add_widget(SkillStat)
self.add_widget(layout)
pass
pass
class Sheet(Carousel):
pass
class SheetApp(App):
def build(self):
return Sheet()
if __name__ == '__main__':
SheetApp().run()
.kv file:
# file name: Sheet.kv
<Sheet>:
RelativeLayout:
Scroll:
size_hint:(1,1)
The two main problem in your code are:
You are doing all your size and position setting of your SkillStat and its canvas in an __init__() method. In an __init__() method of a widget, the position of the widget is always (0,0), and the size is (100, 100). Those properties are not set to real values until the widget is actually drawn.
You are doing all this in python instead of in kv. In kv, bindings are created for many properties that you set, and get automatically updated. If you do your widget setup in python, you must provide those bindings yourself.
Here is a modified version of your Scroll class and a new MyRelativeLayout class that handle those bindings:
class MyRelativeLayout(RelativeLayout):
def adjust_size(self, *args):
self.rect.size = self.size # set the size of the Rectangle
class Scroll(ScrollView):
def __init__(self, **kwargs):
super(Scroll, self).__init__(**kwargs)
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
layout.bind(minimum_height=layout.setter('height'))
# Make sure the height is such that there is something to scroll.
for i in range(100):
SkillStat = MyRelativeLayout(pos=(0,0), height=100, size_hint=(1.0, None))
with SkillStat.canvas.before:
SkillStat.rect = Rectangle()
SkillStat.bind(size=SkillStat.adjust_size)
layout.add_widget(SkillStat)
self.add_widget(layout)
Note the SkillStat.bind() call to create the needed bindings, and the Rectangle is saved as SkillStat.rect in each MyRelativeLayout instance. Those bindings will get triggered as soon as the SkillStat gets displayed, so the initial pos and size of the Rectangle are not needed.
EDIT: Setting the pos of the Rectangle in a binding was probably causing problems. The default pos of the Rectangle is (0,0), which is what it should always be. So, we only need to adjust the size of the Rectangle. I have removed the binding for pos.
Solution
Create a class with inheritance from RelativeLayout.
Update or remove the instructions you have added to a canvas by using bind function.
Snippets
class CustomLayout(RelativeLayout):
def __init__(self, **kwargs):
super(CustomLayout, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=(self.width, 90))
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
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.graphics import Line, Rectangle
from kivy.uix.carousel import Carousel
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from kivy.lang import Builder
class CustomLayout(RelativeLayout):
def __init__(self, **kwargs):
super(CustomLayout, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=(self.width, 90))
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 Scroll(ScrollView):
def __init__(self, **kwargs):
super(Scroll, self).__init__(**kwargs)
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
layout.bind(minimum_height=layout.setter('height'))
# Make sure the height is such that there is something to scroll.
for i in range(100):
SkillStat = CustomLayout(pos=(0, 0), height=100, size_hint_y=None, size_hint_x=self.width)
layout.add_widget(SkillStat)
self.add_widget(layout)
class Sheet(Carousel):
pass
Builder.load_file('main.kv')
class SheetApp(App):
def build(self):
return Sheet()
if __name__ == '__main__':
SheetApp().run()
main.kv
#:kivy 1.11.0
<Sheet>:
RelativeLayout:
Scroll:
size_hint:(1,1)
bar_width: 10
effect_cls: "ScrollEffect"
scroll_type: ['bars']
bar_color: [1, 0, 0, 1] # red color
bar_inactive_color: [0, 0, 1, 1] # blue color
Output
In the following code the ellipse gets displayed with no transparency
(alpha=1). Probably when self.color changes, the alpha is set to 1 (interestingly
this doesn't happen if the color is set to (1,1,1) in the setup method).
Is there a way to change the color without changing the alpha?
I would like to set the alpha in the kv file and change the color from python.
Thanks
alpha.kv:
#:kivy 1.9.1
<MyWidget>:
color: 1,1,1
canvas:
Color:
hsv: self.color
a: 0.3
Ellipse:
pos: (200, 200)
size: (30, 30)
main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
class MyWidget(Widget):
colorH = NumericProperty(1)
colorS = NumericProperty(1)
colorV = NumericProperty(1)
color = ReferenceListProperty(colorH,colorS,colorV)
def setup(self):
self.color = (0.5,1,1)
# self.color = (1,1,1) # here the alpha doesn't change
class AlphaApp(App):
def build(self):
w = MyWidget()
w.setup()
return w
if __name__ == '__main__':
AlphaApp().run()
I tried to make by own coockie-clicker, so I creaded an kivy widget and declared an image of an coockie as part of it.
Everytime you click on the wiget, a counter goes up and the number is displayed on an label.
Everything went fine, after I got help here on stack overflow, but now I am faced with the problem, that the widet is to big, so even if I click on the right upper corner, to counter goes up, aldoug I do not clicked on the coockie.
Here is the sourcecode:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.core.text.markup import *
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
Builder.load_string('''
<Root>:
Kecks:
pos: 300, 300
size: 100, 100
<Kecks>:
Image:
pos: root.pos
id: my_image
source: root.weg
Label:
id: my_Label
font_size: 50
text: root.txt
center_x: 345
center_y: 200
''')
class Root(FloatLayout):
def __init__(self, *args, **kwargs):
super(Root, self).__init__(*args, **kwargs)
class Kecks(Widget):
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty()
level = NumericProperty(1)
weg = StringProperty('piernik.png')
def __init__(self, *args, **kwargs):
super(Kecks, self).__init__(*args, **kwargs)
#self.txt = str(self.count)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
self.txt = str(self.count)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.count += self.amount
class app(App):
def build(self):
Window.clearcolor = (10, 0, 0, 1)
return Root()
if __name__ == "__main__":
app().run()
The problem is you haven't defined your collide_points on which area you want that event to be triggered.
Consider if you want your collide_points on your my_image to trigger on_touch_down event, you need to adjust like this:
def on_touch_down(self, touch):
# to check if touch.pos collides on my_image
if self.ids.my_image.collide_point(*touch.pos):
self.count += self.amount
Also perhaps consider using pos_hint and size_hint as these will help you with consistency with your app running in different devices (size of screen, for instance), rather than using absolute size and/or position.
Hope this helps.