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

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?

Related

How to target canvas.before in kv

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 :|

Kivy moving label inside FloatLayout

Hello I want to place a welcome message at the top of the app (Like the photo this)
I use a FloatLayout for my entire screen (since I want to add some other widgets later) but the problem is the label won't position itself at the center but like this
Here is my python code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
class MainPage(FloatLayout):
pass
class SmartMirrorApp(App):
def build(self, **kwargs):
return MainPage()
if __name__ == "__main__":
SmartMirrorApp().run()
And this is my kv file:
#:kivy 1.10.1
<MainPage>:
canvas:
Color:
rgba: 1,0,1,0.5
Rectangle:
size: self.size
pos: self.pos
Label:
canvas:
Color:
rgba: 1,0,0,0.5
Rectangle:
size: self.size
pos: self.pos
text: "Welcome, you look beautiful today!"
font_size: 20
size_hint: None, None
size: self.texture_size
pos_hint: {'x': 0.5, 'y': 0.9}
Now if instead of putting 'x':0.5 inside the pos_hint dictionary I use center_x: root.center_x
the image moves to the desired position ONLY if I resize the window but it starts at the position of the second image.
For your pos_hint, use:
pos_hint: {'center_x': 0.5, 'top': 0.9}
That will center the Label horizontally, and the top of the Label will be at 90% of the MainPage height.

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).

Kivy - Update Canvas background with value passed from Python

Struggling to update the image source of a canvas from the python:
This is the .KV section:
BoxLayout:
orientation: "vertical"
padding: "5dp"
size_hint_y: 0.70
canvas.before:
Color:
rgb: .6, .6, .6
Rectangle:
pos: self.pos
size: self.size
source: "image.png"
On the python side I have a function returning the new image name which I would like to pass to source in order to update "image.png" to "image2.png"
I tried to add an ID within the .kv but unfortunately it does not work for the "Rectangle"
Any wonderful ideas?
Variables are accessible perfectly fine from outside with e.g builtin names (app for App instance, root for main rule/class, self for current widget's instance). Ids will not work because canvas (before, classic, after) is constructed before anything else is, therefore you won't be able to access ids in a canvas of a widget you build the canvas for.
After the canvas is constructed, then such things as ids work in the way you expect them to work.
from kivy.app import App
from kivy.lang import Builder
kv = '''
BoxLayout:
variable: 'blob'
orientation: 'vertical'
canvas:
Color:
rgb: 1, 1, 1
Rectangle:
source: app.test # self.variable
'''
class TestApp(App):
def build(self):
self.test = 'path'
return Builder.load_string(kv)
if __name__ == '__main__':
TestApp().run()

How to correctly use kivy graphics context instruction

Hi I recently tried my hands on kivy graphics, context instructions(rotate, ranslate etc). So i tried to implement an animated spinning wheel(used for example to show loading screen). I used garden.iconfonts package for this purpose following closely the example implemented in the package. Heres my code
.kv
<Convert>:
pos_hint: {'center_x':.5, 'center_y':.5}
size_hint: .8,.4
auto_dismiss: False
on_open:
self.load()
loading:loading
BoxLayout:
pos: root.pos
canvas.before:
Color:
rgba: 1,1,1,1
Rectangle:
pos: self.pos
size: self.size
source: 'icons/olivine.png'
orientation: 'vertical'
Label: #STATUS 20 PERCENT
text: 'Converting file...'
id: status
size_hint: 1,.2
markup: True
RelativeLayout: #picture or rolling 60 %
size_hint: 1,.6
Label: #SPINNER
text: '{}'.format(icon('icon-spin6', 32))
size_hint: 1,1
markup: True
p: 0
id: loading
canvas:
PushMatrix
Rotate:
angle: -self.p
origin: self.center
axis: 0,0,1
PopMatrix
.py
from kivy.uix.modalview import ModalView
from kivy.properties import ObjectProperty
from kivy.animation import Animation
class Convert(ModalView):
loading= ObjectProperty()
def load(self):
anim = Animation(p = 360, duration= 1) + Animation(p=0 , duration=0)
anim.repeat = True
anim.start(self.loading)
From my code Convert is a popup that shows up when a button is clicked, then as it opens, shows the spinning wheel.
But when i run the code it just shows the wheel(i.e the iconfont), but does not spin.
The code only works when i change the canvas class under the Label, to canvas.before. I assume that my understanding of how to use these tools is still poor. So im hoping someone can help clearify what im doing wrong, and how to make this work using canvas
canvas:
PushMatrix
Rotate:
angle: -self.p
origin: self.center
axis: 0,0,1
PopMatrix
Everything between Rotate and PopMatrix will be rotated - that's the point of PushMatrix and PopMatrix, they bound the region where any matrix transformations are applied.
In this case, you didn't put anything in beween them, so you don't see anything rotated.
You probably want to put PushMatrix and Rotate in the canvas.before, and PopMatrix in the canvas.after. Since the Label drawing occurs in canvas, this will then be in the rotated state.

Categories