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.
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 have two button widgets. The Button1 was rotated and the Button2 wasn't. I'm trying to test if the two button widgets will collide.
Here is my code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.graphics import *
from kivy.logger import Logger
class RotateMe(Button):
def __init__(self, **kwargs):
Button.__init__(self, **kwargs)
with self.canvas.before:
PushMatrix()
self.rot = Rotate(angle= 45, origin= self.center)
with self.canvas.after:
PopMatrix()
class Main(Widget):
def __init__(self):
super(Main, self).__init__()
self.size = Window.size
self.rotatethis = RotateMe(text= "Button1", center= self.center)
self.add_widget(self.rotatethis)
self.somebutton = Button()
self.somebutton.text = "Button2"
self.somebutton.right = self.center_x - 60
self.somebutton.y = self.center_y
self.add_widget(self.somebutton)
if self.somebutton.collide_widget(self.rotatethis):
Logger.info("The buttons collided! Much happiness")
else:
Logger.info("The buttons didn't collide. Such sadness.")
class TestApp(App):
def build(self):
return Main()
TestApp().run()
This is how it looked when I ran my code.
I can clearly see from the output (the image) that the two buttons collided but when I checked the log , it says that the Button Widgets didn't collide. It seems that the widget position of Button1 didn't change/rotate.
I tried to change the position of the Button2 to see at which point it collides with Button1. And I found out that the Widget position of Button1 didn't change.
Like this:
The white square at the back of Button1 is the actual position of the Button1 widget.
Is there a way to rotate the position of Button1 widget?
UPDATE: I tried to use the ScatterLayout as suggested in here.
Here is my updated code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.graphics import *
from kivy.logger import Logger
from kivy.uix.scatterlayout import ScatterLayout
from kivy.uix.floatlayout import FloatLayout
class RotateMe(ScatterLayout):
def __init__(self):
ScatterLayout.__init__(self)
self.size_hint = (None, None)
self.size = (100, 100)
self.rotation = 45
self.do_rotation = False
self.add_widget(Button(text= "Button1"))
class Main(FloatLayout):
def __init__(self):
super(Main, self).__init__()
self.size = Window.size
self.rotatethis = RotateMe()
self.rotatethis.center = self.center
self.add_widget(self.rotatethis)
self.button2 = Button(text= "Button2")
self.button2.size_hint = (None, None)
self.button2.size = (100, 100)
self.button2.right = self.center_x - 60
self.button2.y = self.center_y
self.add_widget(self.button2)
if self.button2.collide_widget(self.rotatethis):
Logger.info("The buttons collided! Much happiness")
else:
Logger.info("The buttons didn't collide. Such sadness.")
class TestApp(App):
def build(self):
return Main()
TestApp().run()
Now the buttons were collided but when I try to change the position of the Button2, like this:
self.button2.y = self.center_y + 45
The output is like this:
Looking at the output (image), the two buttons' widgets doesn't seem to collide with each other but when I look at the log, it says that they collided with each other.
It turns out that the widget size of Button1 is this (the colored teal background):
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()
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.
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.