I just start Kivy programming and have a problem with the understanding of doing a layout:
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color
class CornerRectangleWidget(Widget):
def __init__(self, **kwargs):
super(CornerRectangleWidget, self).__init__(**kwargs)
with self.canvas:
Color(1, 0, 1, 1)
self.rect = Rectangle(size_hint=(1,None),height=48)
class ControllerApp(App):
def build(self):
Window.clearcolor = (1, 0, 0, 1)
root = FloatLayout(size_hint=(1,1))
root.add_widget(CornerRectangleWidget())
return root
if __name__ == '__main__':
ControllerApp().run()
Why is my CornerRectangleWidget keeping so small. I wanted to have a FloatLayout with Fullscreen and the CornerRectangleWidget too.
Actually I want to build the Widget "CornerRectangleWidget" to a floating top Toolbar with the x dimensins of the root Widget "FloatLayout size_hint=(1,None)"! How to do that?
Rectangle doesn't have a size_hint, you need to set a pos and size.
You'll also need to bind to a function to update its position when the widget position changes, as during the __init__ it will have the default pos of (0, 0) and size of (100, 100). This is described here.
Related
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
I want to draw circle in center of FloatLayout. With my knowledges I obtained only default values for this. Why circle in showed code isn't red? Can You explain me process for obtaining necessary coordinates, please?
import kivy
from kivy.config import Config
kivy.config.Config.set('graphics','resizable', False)
from kivy.app import App
from kivy.graphics import Color, Ellipse
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class Scene(FloatLayout):
def __init__(self, **kwargs):
super(Scene, self).__init__(**kwargs)
def draw_circle(self):
with self.canvas:
Color=(1,0,0)
circ = Ellipse(pos = (self.center_x, self.center_y), size=(20,20))
def on_touch_down(self, touch):
pass
class Game(BoxLayout):
def __init__ (self,**kwargs):
super(Game, self).__init__(**kwargs)
self.orientation = 'vertical'
but1 = Button(text = 'button 1')
self.add_widget(but1)
self.scene = Scene()
self.add_widget(self.scene)
class TestApp(App):
def build(self):
game = Game()
game.scene.draw_circle()
return game
if __name__ == '__main__':
TestApp().run()
You should define the size of your float layout when you create it.
self.scene = Scene(size=(300, 300))
Then your circle should be at the center of the FloatLayout dimensions.
I also think FloatLayout is better used with size_hint and pos_hint instead of fixed coordinates.
You can call draw_circle with Clock to make sure the layout is completely initiated first.
Then make sure to create your color like this Color(1, 0, 0). Not Color = ()
from kivy.config import Config
Config.set('graphics','resizable', False)
from kivy.app import App
from kivy.graphics import Color, Ellipse
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
class Scene(FloatLayout):
def draw_circle(self, dt):
with self.canvas:
Color(1,0,0)
circ = Ellipse(pos = (self.center_x, self.center_y), size=(20,20))
class Game(BoxLayout):
def __init__ (self,**kwargs):
super(Game, self).__init__(**kwargs)
self.orientation = 'vertical'
but1 = Button(text = 'button 1')
self.add_widget(but1)
self.scene = Scene()
self.add_widget(self.scene)
class TestApp(App):
def build(self):
game = Game()
Clock.schedule_once(game.scene.draw_circle) # call draw_circle on next frame
return game
if __name__ == '__main__':
TestApp().run()
Using Kivy, how can you change the background colour of a label in Python and not by using the Kv language?
I have tried this:
with self.canvas:
Color(1., 0, 0)
Rectangle(pos=(10, 10), size=(500, 500))
However this just creates a red square on the bottom left of the screen. It would be really useful if there was a alternative to the idea above and I could change the background colour of a label using python and not kv language.
Well, Rectangle position and size should rather match Label position and size:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle
class MyApp(App):
def build(self):
layout = FloatLayout()
label = Label(
text='test',
pos=(20, 20),
size=(180, 100),
size_hint=(None, None))
with label.canvas:
Color(0, 1, 0, 0.25)
Rectangle(pos=label.pos, size=label.size)
layout.add_widget(label)
return layout
if __name__ == '__main__':
MyApp().run()
Version with auto-adjusting:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle
class MyLabel(Label):
def on_size(self, *args):
self.canvas.before.clear()
with self.canvas.before:
Color(0, 1, 0, 0.25)
Rectangle(pos=self.pos, size=self.size)
class MyApp(App):
def build(self):
layout = FloatLayout()
label = MyLabel(
text='test',
pos=(20, 20),
size_hint=(0.5, 0.5))
layout.add_widget(label)
return layout
if __name__ == '__main__':
MyApp().run()
Just to share my trick after nearly 5 years of your question is that instead of using canvas, you can just simply use Button: with disabled: True. Because button is a clickable label, then why don't we reuse it's background (This to make you code as simplest as possible)
If you are wonder how to remove it's darken color, you can see here.
I tried to make an kivy program wit two images , which are supposed to change when I click on them.
But when I tred to add two Widgets with the containing Images to an BoxLayout, I got one image at the position 0, 0
Why dosen't the BoxLayout work with my images?
Here is my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class Feld(Widget):
droga = StringProperty('hinterg.png')
kr = StringProperty('kreuz.png')
ks = StringProperty('kreis.png')
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.droga = self.kr
root = Builder.load_string('''
BoxLayout:
orientation: 'horizontal'
Feld:
id: a1
Feld:
id: a2
<Feld>:
Image:
source: root.droga
''')
class app(App):
def build(self):
Window.clearcolor = (0, 0.54, 1, 1)
return root
if __name__ == "__main__":
app().run()
Your Feld instances are positioned by the BoxLayout, but the image is displayed in an Image widget that is a child of the Feld. Since Feld is just a normal Widget, it doesn't impose any automatic position or size on its children, so both actual images have the default size of (100, 100) and pos of (0, 0).
The best solution is to make Feld a subclass of some layout that will make its child fill itself by default, such as a FloatLayout.
I've tried multiple different solutions for this and I'm out of idea's. I'm brand new to Kivy and still learning. I'm building a very basic memorization game. I need the buttons inside the grid to blink in a certain order. I'm confident that I can make that work, however I can't figure out how to get one of the buttons to blink. Can anyone point me in the right direction? I know I just need need it to change colors for a second and then change back.
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from random import random
import time
class MemorizeGame(GridLayout):
def __init__(self, **kwargs):
super(MemorizeLayout, self).__init__(**kwargs)
self.cols = 2
#create buttons
self.button1 = Button(text='', background_color=(0,0,1,1))
self.button2 = Button(text='', background_color=(0,1,1,1))
self.button3 = Button(text='', background_color=(1,0,1,1))
self.button4 = Button(text='', background_color=(0,1,0,1))
self.buttonList = [self.button1, self.button2, self.button3, self.button4]
#add buttons to the screen
for button in self.buttonList:
self.add_widget(button)
def blinkSquare(self):
#logic to make a square blink
class MemorizeApp(App):
def build(self):
game= MemorizeGame()
return game
if __name__ == "__main__":
MemorizeApp().run()
Here's an example of how to do it, using kivy's clock.
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from random import random
import time
from kivy.clock import Clock
from functools import partial
def set_color(button, color, *args):
button.color = color
class MemorizeGame(GridLayout):
def __init__(self, **kwargs):
super(MemorizeGame, self).__init__(**kwargs)
self.cols = 2
#create buttons
self.button1 = Button(text='', background_color=(0,0,1,1))
self.button2 = Button(text='', background_color=(0,1,1,1))
self.button3 = Button(text='', background_color=(1,0,1,1))
self.button4 = Button(text='', background_color=(0,1,0,1))
self.buttonList = [self.button1, self.button2, self.button3, self.button4]
#add buttons to the screen
for button in self.buttonList:
self.add_widget(button)
def blinkSquare(self):
self.button1.background_color = (1, 1, 1, 1)
def reset_color(*args):
self.button1.background_color = (0, 0, 1, 1)
Clock.schedule_once(reset_color, 1)
# alternative:
# Clock.schedule_once(partial(set_color, self.button1, (0, 0, 1, 1)))
def on_touch_down(self, touch):
self.blinkSquare()
class MemorizeApp(App):
def build(self):
game= MemorizeGame()
return game
if __name__ == "__main__":
MemorizeApp().run()
This just blinks the first button, but I'm sure you can extend it to do exactly what you want.
You might find it neatest to make your own button subclass with the color reset function, rather than making a new function each time.