How to manage kivy file? - python

when we use kivy to create interface , the kivy file content of many lines of code,
For example we have button with specific design, if I want use this button 10 times for each time should I write of properties of button ?
there is any way to manage the file? or we can make more than one kivy file to manage and easy understands code of kivy?

first define the class in the python file like
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior, RectangularRippleBehavior
from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.boxlayout import MDBoxLayout
class CustomButon(RoundedRectangularElevationBehavior, RectangularRippleBehavior, ButtonBehaviour, MDBoxLayout):
text = StringProperty()
def fxn_to_call_on_release(self):
pass
and your kv file should look somewhat like this when defining the properties of the button
<CustomButton>:
size_hint: None, None
width: dp(80)
height: dp(32)
elevation: 20
radius: [dp(15), dp(15), dp(15), dp(15)]
canvas.before:
Color:
rgba: gch("#f3a635")
RoundedRectangle:
size: self.size
pos: self.pos
radius: [dp(15), dp(15), dp(15), dp(15)]
MDLabel:
text: root.text,
halign: "center",
font_style: "Button",
opposite_colors: True,
bold: True
The properties defined are my preferences, you can edit as you see fit.
with these definitions you can call the button in other widgets;
CustomButton:
on_release: self.fxn_to_call_on_release()
text: "Button Text"
you can call the button like this as many times as you wish.

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 function scope on button release (widget tree)

I am new to Kivy and need some help in understanding function scope. I have built a simple app with two screens. The first screen has two buttons and the second screen has a text label at the centre. On the first screen, I have used app.root.current='new_screen_name' with the on_release attribute for one button and it works fine. It takes me to the next screen which is the intended function. For the second button, I have used a function call which has been defined in the Python file under the class definition of the first screen (the root widget of the button). However, this method does not work and the app window simply closes. I guess I am making a mistake in the function scope and call But I cannot figure out what. Any help would be greatly appreciated.
Python file:
from kivy.config import Config
# Config.set should be used before importing any other Kivy module.
Config.set('kivy','window_icon','sivaicon.png')
# Config set for resizing image button
Config.set('graphics', 'resizable', True)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.lang.builder import Builder
class SivaLoginScreen(Screen):
def func_authentication(self):
app.root.current='tabbed_screen'
class SivaTabbedScreen(Screen):
pass
class SivaScreenManager(ScreenManager):
pass
class ImageButton(ButtonBehavior, Image):
pass
# Tell Kivy to directly load a file. If this file defines a root widget, it will be returned by the method.
root_widget = Builder.load_file('siva.kv')
class SivaApp(App):
def build(self):
# Initialize root widget
return root_widget
if __name__ == '__main__':
# Run application
SivaApp().run()
Kivy file (.kv):
SivaScreenManager:
SivaLoginScreen:
SivaTabbedScreen:
<ImageButton>:
keep_ratio: True
<SivaLoginScreen>:
name: 'login_screen'
canvas.before:
Color:
rgba: 195/255, 60/255, 35/255, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
size: root.width, root.height
Image:
id: login_logo_siva
source: 'images/sivalogo4.png'
keep_ratio: True
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.75}
Label:
id: login_label_siva
pos: self.x*0.5-4, self.y*0.5+15
markup: True
font_name: 'roboto/Roboto-Medium.ttf'
text: '[color=#FDFD98]S.[/color][color=#B29DD9]I[/color][color=#FDFD98].[/color][color=#77DD77]V[/color][color=#FDFD98].[/color][color=#779ECB]A[/color]'
font_size: '50sp'
Label:
id: login_label_slogan1
pos: self.x*0.5-3, self.y*0.5-6
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]SLOGAN TEXT[/color]'
font_size: '15sp'
Label:
id: login_label_slogan2
pos: self.x*0.5-3, self.y*0.5-20
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]HEADLINE TEXT[/color]'
font_size: '15sp'
BoxLayout:
id:login_button_layout
orientation: 'horizontal'
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.25}
ImageButton:
id: first_button
source: {'normal': 'images/first.png', 'down': 'images/first-down.png'} [self.state]
on_release: app.root.current='tabbed_screen'
ImageButton:
id: second_button
source: {'normal': 'images/second.png', 'down': 'images/second-down.png'} [self.state]
on_release: app.root.func_authentication()
<SivaTabbedScreen>:
name: 'tabbed_screen'
FloatLayout:
size: root.width, root.height
Label:
pos: self.x*0.5, self.y*0.5
text: 'SECOND SCREEN'
font_size: '50sp'
In your case, app.root link to SivaScreenManager which is the root widget of your application. And in these class, there is not a func_authenticationfunction, why you app crashed.
To refer a class itself in a KV definition, you must just use root, so right code must be :
on_release: root.func_authentication()
see Kivy Language - Reserved Keywords
Definition of func_authentication is not correct also, app is unknown. Use either :
App.get_running_app().root.current='tabbed_screen' or
self.manager.current='tabbed_screen'

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

How to restrict movement to a single folder in FileChooser?

I'm using python and Kivy for making a simple text editor almost like VSCode. I have come upon the amazing FileChooser widget (more specifically FileChooserListView) and have been using it for file navigation.
However I want to restrict movement to a single project file much like in VSCode, to ensure a safer experience.
I want to do this by making a terminal command at first (like in VSCode: code folder_name), but since that's another topic decided to just use a string main_path (in kv code which you can edit) to which the program must restrict itself to. I will add terminal functionality to choose paths later (if possible guides on that subject would be very helpful).
I really don't know much about Kivy and the documentation is pretty confusing. So I didn't know what all to try.
#######KV FILE name: gridlayout.kv#######
#:import isdir kivytest.GridLayoutApp
#:set main_path '~/Projects'
<Label>:
font_name:'Fura Code Retina Nerd Font Complete.otf'
font_size:'18sp'
<GridLayout>:
cols:3
rows:1
Label:
canvas.before:
Color:
rgb:0.2,0.2,0.2
Rectangle:
pos: self.pos
size: self.size
size_hint_x: None
width: 50
Splitter:
id:splitter
sizable_from: 'right'
min_size: 170
max_size: 400
on_release: app.ask_update()
width:335
FileChooserListView:
id:filechooser
path: main_path
size_hint_x: None
width: splitter.width-splitter.strip_size
font_name: 'Fura Code Retina Nerd Font Complete.otf'
font_size: '1sp'
canvas.before:
Color:
rgb: .17,.19,.19
Rectangle:
pos: self.pos
size: self.size
on_selection: root.select(*args)
background_color:(0.17,0.19,0.17,1)
ScrollView:
id:scroller
TextInput:
id:ti
size_hint: (None, None)
width: scroller.width
height:max(self.minimum_height, scroller.height)
font_size: '18sp'
cursor_color: [255,255,255,1]
background_color: (.17, .18, .17, 1)
foreground_color:[255,255,255,1]
font_name: 'Fura Code Retina Nerd Font Complete.otf'
selection_color: (1,1,1,0.125)
on_text: app.text_changed()
#######Python Code#######
import os
from pathlib import Path
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
class GridLayoutApp(App):
file = ''
dir = ''
text = ''
def build(self):
self.grid=GridLayout()
return self.grid
def text_changed(self, *args):
width_calc = self.grid.ids.scroller.width
for line_label in self.grid.ids.ti._lines_labels:
width_calc = max(width_calc, line_label.width + 20)
self.grid.ids.ti.width = width_calc
def ask_update(self, *args):
width_calc = self.grid.ids.splitter.width
self.grid.ids.filechooser.width = width_calc- self.grid.ids.splitter.strip_size
if __name__ == "__main__":
glApp = GridLayoutApp()
glApp.run()
Expected results: Restriction of file navigation outside of the chosen folder (main_path).
EDIT: You can change the font name as well in case you don't have the font.

Scroll contents of GridLayout in ScrollView - Kivy

I will say first off I have tried every single example on the web involving kv lang. Not once have I had any success.
The idea is pretty simple: As I swipe up/down/scroll the contents of GridLayout() within ScrollView() are scrolled up or down.
The best I have been able to do is have the scroll bar fade into view when running the program. Not able to scroll unfortunately.
<Root>
grid_layout: grid_layout
ScreenManager:
...
Screen:
...
ScrollView:
GridLayout:
id: grid_layout
size_hint_y: None
cols: 1
height: self.minimum_height
<list of buttons>
Binding minimum_heightin the __init__ method of the root class (RelativeLayout):
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
I have followed https://github.com/kivy/kivy/blob/master/examples/widgets/scrollview.py converting it to kv lang - scroll bar visible, unable to scroll. Also tried every example on Google Groups and here related to using kv lang. Still no scroll :\
Compiling using buildozer and running on Android fails for an unknown reason.
I would appreciate any assistance that can be given.. I am completely clueless at this point
This:
height: self.minimum_height
should be:
minimum_height: self.height
This is unnecessary:
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
It also won't scroll unless the contents are larger than the scrollview's height:
Full code:
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
Builder.load_string('''
<Root>:
ScrollView:
size_hint: 1, .1
# setting the width of the scrollbar to 50pixels
bar_width: 50
# setting the color of the active bar using rgba
bar_color: 5, 10, 15, .8
# setting the color of the inactive bar using rgba
bar_inactive_color: 5, 20, 10, .5
# setting the content only to scroll via bar, not content
scroll_type: ['bars']
GridLayout:
size_hint_y: None
cols: 1
minimum_height: self.height
Button
text: 'one'
Button:
text: 'two'
Button:
text: 'three'
Button:
text: 'four'
''')
class Root(FloatLayout):
pass
class DemoApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DemoApp().run()
Being unable to scroll was due to a misunderstanding of Kivy's touch handlers. Completely unrelated to the code mentioned in my question.
The key is to have GridLayout be larger than ScrollView, so GridLayout can be panned within ScrollView.
For those wanting to use ScrollView inside ScreenManager using kvlang only:
ScreenManager:
id: screen_manager
Screen:
manager: screen_manager
id: main_screen
name: 'main'
ScrollView:
bar_width: 4
# pos_hint defaults to 1,1 so no need to declare it
GridLayout:
size_hint_y: None
cols: 1
# you do not need to manually bind to setter('height') in
# python - perfectly possible with kv lang
# this allows for height to update depending on the
# collective heights of its child widgets
height: self.minimum_height
<----- widgets here ----->
# for scroll to show/work there must be more widgets
# then can fit root.height. If not there is no need
# for scrollview :)

Categories