How to target canvas.before in kv - python

In a button i have made a rounded button with canvas.before, and it changes colors as it should. The line is:
canvas.before:
Color:
rgba: btn_color_not_pressed if self.state=='normal' else btn_color_pressed
RoundedRectangle:
size: self.size
pos: self.pos
radius: [40]
The variables btn_color_not_pressed and btn_color_not_pressed are made with #:set in the start of the kv-file
I have tried to target the line with self.canvas.before.Color.rgba, as i am used to normally, but i get following error:
AttributeError: 'kivy.graphics.instructions.CanvasBase' object has no attribute 'Color'
How do i target that line from within kv and replace the variables ... or if necessary from the python file.?
How do i target the source: "some_file.jpg under Rectangle?
My goal is that when a user has clicked an option all the button colors (and maybe the background) in the app must change.

You cannot change the variables created in kv. Once your app is running, those variables no longer exist. You can, however, use a Property that is created in kv (or python) as an attribute of a class (or the App itself). Such Properties continue to exist while the App is running, and kivy recognizes such Properties and will automatically handle changes to those Properties. An example is to create a new class that extends Button and has Properties like you want:
<-MyButton#Button>:
# create the desired properties
btn_color_not_pressed: [.5, .5, .5,1]
btn_color_pressed: [.25, .25, .25, 1]
canvas:
Color:
# reference the above properties
rgba: self.btn_color_not_pressed if self.state=='normal' else self.btn_color_pressed
RoundedRectangle:
size: self.size
pos: self.pos
radius: [40]
# this is copied from style.kv to show the Button text
Color:
rgba: 1, 1, 1, 1
Rectangle:
texture: self.texture
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
# actually make an instance of the new MyButton class
MyButton:
btn_color_not_pressed: [1,0,0,1]
btn_color_pressed: [0,1,0,1]
text: 'Button Test'
The <-MyButton#Button> creates a new class (MyButton) that extends Button. The prepended - indicates that the default canvas instructions for Button are not to be used and the provided instructions are used instead. Those new Properties can be changed in python code as usual. You can use a similar approach for the source property.

That brought me a step closer.
My problem now is that the colors only change the button itself or togglebutton-group, but only when you click on them. It only reacts to the new colors when activated (button og group).
The design is not updated
I tried the solution with the - but it made no difference
main.py
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.behaviors import ToggleButtonBehavior
from kivy.factory import Factory
kivy.require("1.11.1")
class Controller(BoxLayout):
def __init__(self):
super(Controller, self).__init__()
ToggleButtonBehavior.allow_no_selection = False
def change_button_color(self,theme):
if theme == "red":
Factory.My_tgl_btn.btn_color_pressed = (1,0,0,.7)
Factory.My_tgl_btn.btn_color_not_pressed = (1,0,0,.5)
else: # theme == "blue":
Factory.My_tgl_btn.btn_color_pressed = (0,0,1,.7)
Factory.My_tgl_btn.btn_color_not_pressed = (0,0,1,.5)
class mainApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
mainApp().run()
main.kv
#:set bg_color 1,1,1,.7
#:set txt_color 0,0,0,1
#:import Factory kivy.factory.Factory
<Controller>
BoxLayout:
orientation: "vertical"
background_color: 1,1,1,.5
background_normal: ""
Label:
text: "THIS IS THE MAIN TEKST"
color: txt_color
size_hint_y:.7
BoxLayout:
size_hint_y: .15
My_tgl_btn:
text: "RED theme"
group: 1
state: "down"
on_press: root.change_button_color("red")
on_release: root.change_button_color("red")
My_tgl_btn:
text: "Blue theme"
group: 1
on_press: root.change_button_color("blue")
on_release: root.change_button_color("blue")
BoxLayout:
size_hint_y: .15
My_tgl_btn:
text: "Option1"
group: 2
state: "down"
My_tgl_btn:
text: "Option2"
group: 2
state: "normal"
<My_tgl_btn#ToggleButton>
btn_color_pressed: 1,0,0,.7
btn_color_not_pressed: 1,0,0,.5
color: txt_color
background_color: 0,0,0,0
background_normal: ""
canvas.before:
Color:
rgba:self.btn_color_not_pressed if self.state=='normal' else self.btn_color_pressed
RoundedRectangle:
size: self.size
pos: self.pos
radius: [40]

Found a solution (here: Kivy: resetting toggle buttons to "normal" on re-entering screen)
It's kind'a ugly, but it works..
Give every button an id ... and then use on_enter for each button and set and change the state.
In the code above it would mean:
on_enter:
button1.state = "down"
button1.state = "normal"
button2.state = "down"
button2.state = "normal"
button3.state = "down"
button3.state = "normal"
button4.state = "down"
button4.state = "normal"
It works ... but it is not pretty :|

Related

AttributeError: 'kivy.graphics.context_instructions.Color' object has no attribute 'fbind' when I run my kivy code

What is wrong with the following code?
# Program to Show how to create a switch
# import kivy module
import kivy
# base Class of your App inherits from the App class.
# app:always refers to the instance of your application
from kivy.app import App
# this restrict the kivy version i.e
# below this kivy version you cannot
# use the app or software
kivy.require('1.9.0')
# Builder is used when .kv file is
# to be used in .py file
from kivy.lang import Builder
# The screen manager is a widget
# dedicated to managing multiple screens for your application.
from kivy.uix.screenmanager import ScreenManager, Screen
# You can create your kv code in the Python file
Builder.load_string("""
<ScreenOne>:
BoxLayout:
canvas.before:
Color:
rgba: (.4, .6, 9, 1) #(0,0,0,1)
Rectangle:
size: self.size
pos: self.pos
canvas:
Color:
rgba: (1, 1, 1,1)
RoundedRectangle:
size : self.width/2, self.height/3
pos : self.center_x - self.w
idth/4 , self.center_y - self.height/6
radius: [(40, 40), (40, 40), (40, 40), (40, 40)]
Button:
text: "Go to Screen 2"
background_color : 0, 0, 1, 1
on_press:
# You can define the duration of the change
# and the direction of the slide
root.manager.transition.direction = 'left'
root.manager.transition.duration = 1
root.manager.current = 'screen_two'
<ScreenTwo>:
BoxLayout:
Button:
text: "Go to Screen 3"
background_color : 1, 1, 0, 1
on_press:
root.manager.transition.direction = 'left'
root.manager.transition.duration = 1
root.manager.current = 'screen_three'
<ScreenThree>:
BoxLayout:
Button:
text: "Go to Screen 4"
background_color : 1, 0, 1, 1
on_press:
root.manager.transition.direction = 'left'
root.manager.transition.duration = 1
root.manager.current = 'screen_four'
<ScreenFour>:
BoxLayout:
Button:
text: "Go to Screen 5"
background_color : 0, 1, 1, 1
on_press:
root.manager.transition.direction = 'left'
root.manager.transition.duration = 1
root.manager.current = 'screen_five'
<ScreenFive>:
BoxLayout:
Button:
text: "Go to Screen 1"
background_color : 1, 0, 0, 1
on_press:
root.manager.transition.direction = 'right'
root.manager.current = 'screen_one'
""")
# Create a class for all screens in which you can include
# helpful methods specific to that screen
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
pass
class ScreenThree(Screen):
pass
class ScreenFour(Screen):
pass
class ScreenFive(Screen):
pass
# The ScreenManager controls moving between screens
screen_manager = ScreenManager()
# Add the screens to the manager and then supply a name
# that is used to switch screens
screen_manager.add_widget(ScreenOne(name="screen_one"))
screen_manager.add_widget(ScreenTwo(name="screen_two"))
screen_manager.add_widget(ScreenThree(name="screen_three"))
screen_manager.add_widget(ScreenFour(name="screen_four"))
screen_manager.add_widget(ScreenFive(name="screen_five"))
# Create the App class
class ScreenApp(App):
def build(self):
return screen_manager
# run the app
sample_app = ScreenApp()
sample_app.run()
I am new to kivy and got this code online which works perfect. I now tried to alter it by putting some canvas and shapes in it but I get the error:
AttributeError: 'kivy.graphics.context_instructions.Color' object has no attribute 'fbind'
what does this mean and how can I alter this again to achieve my goal of having my 2 rectangles in place and the button on them?
Assuming you copied and pasted your code correctly, the indentation is wrong, you've put a Color instruction at child widget level rather than indented under the canvas.before:. This is quite possibly causing your error.

Dropdown menu not working using Kivy Language

I am trying to make a simple dropdown menu using only Kivy Language.
This program is a simple image that the user can resize, with a button that brings up a dropdown menu. When the program starts, part of the dropdown menu is appearing near the bottom. Other than that, everything looks right. When clicked, nothing happens, except the part of the dropdown menu that's visible (that I didn't want visible yet) disappears.
# .py file
import kivy
from kivy.app import App
# kivy.require('1.9.0')
from kivy.uix.scatter import Scatter
from kivy.uix.widget import Widget
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
# Creating widget class
class SquareWidget(Widget):
pass
# Creating Scatter Class
class ScatterWidget(Scatter):
do_rotation=False
# Create the layout class
class Scatter_App(RelativeLayout):
pass
class ScatterApp(App):
def build(self):
return Scatter_App()
if __name__=='__main__':
ScatterApp().run()
# .kv file
# Create the scatter properties
<SquareWidget>:
size: self.parent.size
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'image.jpg'
<Scatter_App>:
canvas:
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
id: square_widget_id
SquareWidget:
DropDown:
id: cdd
Button:
text: 'Item 1'
Label:
text: 'Item 2'
Label:
text: 'Item 3'
Button:
background_normal: ''
background_color: 1, .2, .3, .85
text: 'Choose'
text_size: self.size
text_pos: self.height/2,self.width/2
size_hint: .15,.15
pos: (self.parent.width-self.width)/2,self.parent.height-self.height
on_release: cdd.open
A few problems:
If you add a DropDown directly in kv, it will become a child of Scatter_App, just like any other Widget. Then, trying to call open() on it will fail, because open() tries to do an add_widget (but the DropDown already has a parent).
Your rule for the DropDown does not specify the height of each of the added Widgets. From the documentation:
When adding widgets, we need to specify the height manually
When calling open() on a DropDown, you must include an argument that specifies the Widget that the DropDown should attach to.
So, taking all this into account, I have created a slightly modified version of your kv file:
# .kv file
# Create the scatter properties
#:import Factory kivy.factory.Factory
<SquareWidget>:
size: self.parent.size
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'image.jpg'
# add a dynamic class that extends DropDown
<MyDropDown#DropDown>:
Button:
text: 'Item 1'
size_hint_y: None
height: 40
Label:
text: 'Item 2'
size_hint_y: None
height: 40
Label:
text: 'Item 3'
size_hint_y: None
height: 40
<Scatter_App>:
canvas:
Rectangle:
size: self.size
pos: self.pos
ScatterWidget:
id: square_widget_id
SquareWidget:
Button:
background_normal: ''
background_color: 1, .2, .3, .85
text: 'Choose'
text_size: self.size
text_pos: self.height/2,self.width/2
size_hint: .15,.15
pos: (self.parent.width-self.width)/2,self.parent.height-self.height
on_release: Factory.MyDropDown().open(self)
I have added a Factory import to the kv file, so that you can create the MyDropDown in the kv file. I have also added height specifications to the Widgets added to the DropDown. The MyDropDown.open() call now includes self (the Button).

can't position label with round button to top corner kivy

I am trying to get a label with in it a rounded button to be placed in top left corner, but can't seem to get it done.
tried pos_hint in both < BackLabel > and < SubScreen >-->backlabel.
excerpts from my code:
tryout.py:
class RoundedButton(Button):
pass
class BackLabel(Label):
def __init__(self, **kwargs):
super(BackLabel, self).__init__(**kwargs)
self.RoundedButton=RoundedButton()
self.add_widget(self.RoundedButton)
class SubScreen(Screen):
def __init__(self, **kwargs):
super(SubScreen, self).__init__(**kwargs)
self.BackLabel = BackLabel(id='bl')
self.add_widget(self.BackLabel)
self.OpenLink = Button(id= 'forward')
self.add_widget(self.OpenLink)
self.Footer = Label(id='footer')
self.add_widget(self.Footer)
.kv file:
<BackLabel#Label>:
background_color: 0,0,0,0
canvas.before:
Color:
rgb: 0, 1, 0
RoundedRectangle:
size: [100,100]
radius: [50,]
<RoundedButton#Button>:
background_color: 0,0,0,0 # the last zero is the critical on, make invisible
canvas.before:
Color:
rgba: (.4,.4,.4,1) if self.state=='normal' else (0,.7,.7,1) # visual feedback of press
RoundedRectangle:
size: [100,100]
radius: [50,]
text:'<--Back'
on_release: app.root.current = 'mainmenu'
<SubScreen>
name: 'submenu'
BackLabel:
id: 'bl'
It is difficult to interpret exactly what you are attempting to do. For example, why are you adding a RoundedButton to a BackLabel in the __init__() method of your BackLabel? Anyway, here is a simpler version of your code that does what I think you want. A couple things to note: In order to position a Widget meaningfully, it must have a reasonable size. The default size of any Widget is the same as its parent, so positioning it doesn't really make sense. So my code below sets the size of your RoundButton (which requires size_hint to be (None, None). Finally, the position can be set (as in the SubScreen rule in the kv below):
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
class SubScreen(Screen):
pass
Builder.load_string('''
<RoundedButton#Button>:
size_hint: (None, None)
size: (100, 100)
background_color: 0,0,0,0 # the last zero is the critical on, make invisible
canvas.before:
Color:
rgba: (.4,.4,.4,1) if self.state=='normal' else (0,.7,.7,1) # visual feedback of press
RoundedRectangle:
pos: (self.center_x - 50, self.center_y - 50)
size: self.size
radius: [50,]
text:'<--Back'
on_release: app.root.current = 'mainmenu'
<SubScreen>
name: 'submenu'
RoundedButton:
pos: (0, root.height-self.height)
''')
class TheApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(SubScreen())
return sm
TheApp().run()

Kivy behavior of drawing in canvas, to change it's background

I'm trying to set the background of my layout with the following kv language:
<RootWidget>:
Menu:
orientation: 'vertical'
Button:
text: 'btn1'
on_release: print('btn1')
Button:
text: 'btn'
on_release: print('btn2')
BoxLayout:
BoxLayout:
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'main_background.png'
ToggleMenuButton:
on_release: root.toggle_state()
size_hint: None, None
size: 60, 60
background_color: 0, 0, 0, 0
Image:
size: self.parent.size
pos: self.parent.pos
allow_stretch: True
source: './data/images/white_menu.png'
And this is my code:
from kivy.app import App
from kivy.garden.navigationdrawer import NavigationDrawer
from layouts.menu import Menu
from layouts.menu import ToggleMenuButton
from layouts.content import Content
class RootWidget(NavigationDrawer):
pass
class MainApp(App):
def build(self):
self.root = RootWidget()
return self.root
if __name__ == '__main__':
MainApp().run()
The Menu and Content classes are Kivy BoxLayout, the ToggleMenuButton is a Kivy Button.
When I run this code I get the following screen:
The background should be my image, not black. If I but the white_menu.png as my background, I also get a black braground. But, if put the following code:
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'vertical'
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: 'main_background.png'
I get the following:
Like you can notice on the bottom, the image is transparent, with 3 white rectangles, but on the main layout the image is red. I kwnow that it is red, because the rgba code, but why the image changes it's color?
If I put the background on canvas.after it works correctly, I can see my background, but I can't see the widgets.
Summing up, I want to understand what is happening? Why I cant see the changes on canvas.before? Why/How putting a color on my canvas changes my image?
If I change, the RootWidget to be a BoxLayout instead of a NavigationDrawer, it works correctaly. Why? Why changing the canvas of a child of a child of a NavigationDrawer does not work?

How to unbind a property automatically binded in Kivy language?

The Kivy Language automatically creates internal binds in properties. For example, if we assign the position of the parent to the position of the child, then the position of the child is going to be updated automatically:
Widget:
Label:
pos: self.parent.pos
In this case, if we move the parent Widget, then the child is also going to move. How do I unbind the property pos from the child? I know how to unbind (properties)[http://kivy.org/docs/api-kivy.uix.widget.html#using-properties] that I bind myself but how do I unbind them if I don't know the name of the method it is bound.
Here is a small example to show what I mean. The Button Up moves the GridLayout to the top and Down to the Bottom. The Button Center center itself in the middle of the screen. My problem is that when I click Up or Down my Centered button is not anymore.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
GridLayout:
id: _box
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.y = 0
text: "Down"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: self.center_y = root.height/2
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _box.top = root.height
text: "Up"
""")
class Example(FloatLayout):
pass
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Why do I want to do that in any case? I am using an animations on the GridLayout that constantly updates the position. The normal position of the buttons should be inside the gridlayout but once in a while one of the buttons flies over the screen and come back to the same position. The problem is that I cannot make them fly while my gridlayout is also moving because the property is bound and as soon as the button try to fly it goes back to the grid. That also means that the binding is sometimes desirable. What I want is have control of the bind and unbind.
Comments don't seem to be working right now so I'll post this as a answer.
You already have a FloatLayout(your root widget). Use that instead of
creating a new FloatLayout.
Before removing the widget from the grid.
store it's size,
set size_hint to None, None
set pos_hint to position the widget in the center.
When adding the widget to grid do the reverse.
Here's your code with these fixes::
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
_size = self.center_button.size
self.center_widget.remove_widget(self.center_button)
self.center_button.size_hint = (None, None)
self.add_widget(self.center_button)
self.center_button.pos_hint = {'center_x': .5, 'center_y':.5}
else:
self.remove_widget(self.center_button)
self.center_button.size_hint = (1, 1)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Update 1:
If for whatever reason you still need to unbind the properties bound by kvlang you can do so using introspection to get a list of observers for the property. so for your case it would be something like this::
observers = self.center_widget.get_property_observers('pos')
print('list of observers before unbinding: {}'.format(observers))
for observer in observers:
self.center_widget.unbind(pos=observer)
print('list of observers after unbinding: {}'.format(self.center_widget.get_property_observers('pos')))
You would need to use the latest master for this. I should fore-warn you to be extremely careful with this though you'd need to reset the bindings set in kvlanguage, but then you loose the advantage of kv language... Only use this If you really understand what you are doing.
Following #qua-non suggestion, I am temporarily moving the child to another parent. It actually unbinds it, or maybe, rebinds it to the new parent. This is a partial solution because of whatever reason, it doesn't update the position automatically when I took it out of the GridLayout (i.e. when I press enter) and put it into the new parent. I need to press 'Up' (or 'Down') after the 'Out of the Box' button.
However, it does go back immediately. When you click again on the 'Out of the box' button the 2nd time, it goes back to its original position. This part works perfectly. And it continue obeying to its parent instructions.
In other words, it doesn't work immediately when I take out of the grid but it does when I put it back.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
center_widget: _center_widget
float: _float
grid:_grid
GridLayout:
id: _grid
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
x: 0
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.y = 0
text: "Down"
Widget:
id: _center_widget
Button:
id: _center_button
pos: self.parent.pos
size: self.parent.size
on_press: root.centerize(*args)
text: "Out of the Grid"
Widget:
Button:
pos: self.parent.pos
size: self.parent.size
on_press: _grid.top = root.height
text: "Up"
FloatLayout:
id: _float
size_hint: None,None
""")
class Example(FloatLayout):
def centerize(self, instance):
if self.center_button.parent == self.center_widget:
self.center_widget.remove_widget(self.center_button)
self.float.add_widget(self.center_button)
self.float.size = self.center_button.size
self.float.x = self.center_button.x
self.float.center_y = self.center_y
else:
self.float.remove_widget(self.center_button)
self.center_widget.add_widget(self.center_button)
self.center_button.size = self.center_widget.size
self.center_button.pos = self.center_widget.pos
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()
Here is something very similar to what I was trying. The difference is that I ended binding the properties manually so I can unbind them. Basically if I uncomment the line #pos: self.parent.pos of the 'out of the box' button, then I cannot unbind them unless I assign the Button to another parent as #qua-non suggested.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Example>:
center_button: _center_button
GridLayout:
cols: 3
size_hint: .7, .3
pos_hint: {'center_x': .5}
Button:
on_press: self.parent.y = 0
text: "Down"
Widget:
Button:
id: _center_button
size: self.parent.size
#pos: self.parent.pos
on_press: root.centerize(*args)
text: "Out of the Grid"
Button:
on_press: self.parent.top = root.height
text: "Up"
""")
class Example(FloatLayout):
def __init__(self, **kwargs):
super(Example, self).__init__(**kwargs)
self.center_button.parent.bind(pos=self.binder)
self.centered = False
def binder(self, instance, value):
self.center_button.pos = instance.pos
def centerize(self, instance):
if self.centered:
self.center_button.parent.bind(pos=self.binder)
self.center_button.y = self.center_button.parent.y
self.centered = False
else:
self.center_button.parent.unbind(pos=self.binder)
self.center_button.center_y = self.height/2
self.centered = True
class ExampleApp(App):
def build(self):
return Example()
if __name__ == "__main__":
ExampleApp().run()

Categories