How to switch a widget's Animation transition in Kivy - python

Summary:
I would like to test all the possible transitions of Kivy's Animation function.
In my code, I call a method to switch the buttons' animations. The method is successfully called because I used print to confirm the change; however, the buttons' transitions don't accept the change. They keep using the first animation but do not take on the next animation in the list.
What am I doing wrong?
Code:
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from random import random
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.clock import Clock
class LittleButtons(Button):
transition = 'in_back'
dur = 2
num = 0
def change_transition(self):
list = ['in_back','in_bounce','in_circ','in_cubic','in_elastic','in_expo','in_out_back',
'in_out_bounce','in_out_cubic','in_out_elastic','in_out_expo',
'in_out_quad','in_out_quart','in_out_quint','in_out_sine','in_quad','in_quart',
'in_quint','in_sine','linear','out_back','out_bounce','out_circ','out_cubic',
'out_elastic','out_expo','out_quad','out_quart','out_quint','out_sine']
self.num += 1
self.transition = list[self.num]
self.reup()
print(self.transition)
if self.num == len(list) - 1:
self.num = -1
def reup(self, *args):
Animation.cancel_all(self)
anim = Animation(pos_hint = {'center_x': random(), 'center_y': random()},
duration = self.dur, t = self.transition)
anim.start(self)
def __init__(self, **kwargs):
super(LittleButtons, self).__init__(**kwargs)
self.pos_hint = {'center_x': random(), 'center_y': random()}
self.size_hint = None, None
self.width = random() * (Window.width / 20)
self.height = random() * (Window.width / 20)
self.background_color = [0,0,0,.05]
Animation(pos_hint = {'center_x': random(), 'center_y': random()},
duration = self.dur).start(self)
Clock.schedule_interval(self.reup, self.dur)
KV = Builder.load_string("""
#:import Factory kivy.factory.Factory
Screen:
FloatLayout:
on_parent:
(lambda ltext: [self.add_widget(Factory.LittleButtons(text=ltext)) for i in range (150)])('hi!')
LittleButtons:
id: base
Button:
background_color: 0,0,0,0
canvas:
Color:
rgba: 0,1,1,1
Rectangle:
pos: self.pos
size:self.size
on_release:
base.change_transition()
""")
class MyApp(App):
def build(self):
return KV
if __name__ == '__main__':
MyApp().run()

Your code is working, but not doing what you expect. Your on_release calls base.change_transition(), which refer to the base id. That id is the one LittleButtons in your KV string that is not built by the on_parent event. The animation transition of that one LittleButtons is successfully modified, but the others are unaffected. Also, that particular LittleButtons is unseen, since the big Button hides it. So, you are successfully changing the transition of one LittleButtons, but it doesn't show.

Related

How to handle clock module with multiple screens in Kivy?

I tried to move a ball around an origin (like the moon in its orbit around the earth). At first I created the ball object inside GameScreen. Then I got the AttributeError that my object does not have "move" attribute. So i created another widget called MainGame as a parent of the ball object. Now the error is gone but the ball does not actively moving. I could not figure out what i'm missing since I'm pretty new to Kivy. I guess it's about Clock module and my custom update function. All answers and helps are appreciated, thanks!
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
import math
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.clock import Clock
class GameBall(Widget):
org_x, org_y = 300, 300
dist = 100
ang_deg = 0
pos_x = NumericProperty(org_x+dist)
pos_y = NumericProperty(org_y)
pos = ReferenceListProperty(pos_x, pos_y)
def move(self):
self.ang_deg += 5
self.ang_rad = math.radians(self.ang_deg)
self.pos_x, self.pos_y = self.org_x + self.dist*math.cos(self.ang_rad), self.org_y + self.dist*math.sin(self.ang_rad)
class MainGame(Widget):
game_ball = GameBall()
def update(self, dt):
self.game_ball.move()
class ScreenManagement(ScreenManager):
pass
class MenuScreen(Screen):
pass
class GameScreen(Screen):
pass
GUI = Builder.load_file("gui.kv")
class MobileGameApp(App):
def build(self):
game = MainGame()
Clock.schedule_interval(game.update, 1.0/60.0)
return GUI
if __name__ == "__main__":
MobileGameApp().run()
---- Kv file:
ScreenManagement:
MenuScreen:
GameScreen:
<MenuScreen>:
name: "menu_screen"
Button:
size_hint: .2, .2
pos: root.width/2 - self.width/2, root.height/2 - self.height/2
text: "Play Game"
on_release:
root.manager.transition.direction = "left"
root.manager.current = "game_screen"
<GameScreen>:
game_ball: main_game.game_ball
name: "game_screen"
Button:
size_hint: .2, .2
pos_hint: {'top': 1}
text: "Go to menu"
on_release:
root.manager.transition.direction = "right"
root.manager.current = "menu_screen"
MainGame:
id: main_game
GameBall:
id: game_ball
center: self.parent.center
canvas:
Ellipse:
size: 50, 50
pos: self.pos
Several problems with your code. One important thing I see in your code is creating new object instances when you actually want to reference existing instances:
game = MainGame()
and
game_ball = GameBall()
are creating new instances of MainGame and GameBall. Neither of those new instances are in your GUI, so any changes you make to those new instances will have no effect on your GUI.
So, you need to get references to the existing MainGame and GameBall. Those are the instances created by your kv file. One way to do that is to make the following changes to your code.
You can simplify GameBall as:
class GameBall(Widget):
org_x, org_y = 300, 300
dist = 100
ang_deg = 0
def move(self):
self.ang_deg += 5
self.ang_rad = math.radians(self.ang_deg)
self.pos = (self.org_x + self.dist*math.cos(self.ang_rad), self.org_y + self.dist*math.sin(self.ang_rad))
And MainGame can become:
class MainGame(Widget):
game_ball = ObjectProperty(None)
def update(self, dt):
self.game_ball.move()
And starting the animation now looks like:
class MobileGameApp(App):
def build(self):
Clock.schedule_once(self.start_updates)
return GUI
def start_updates(self, dt):
Clock.schedule_interval(self.root.get_screen('game_screen').ids.main_game.update, 1.0/60)
And then, to simplify accessing the GameBall, I added a game_ball to the kv as:
MainGame:
game_ball: game_ball
id: main_game
GameBall:
id: game_ball
center: self.parent.center
canvas:
Ellipse:
size: 50, 50
pos: self.pos

How to create labels using for loop in Kivy's .kv file?

I am working on a PDF parsing app in Kivy. I am using screen managers in .py file. In one of the screens, I select the pdf file and add it to a list, update a function in another screen ('Files') and then switch to that screen. My .py file is as under:-
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
kivy.require("1.11.1")
from kivy.uix.floatlayout import FloatLayout
from kivy.config import Config
from kivy.properties import ObjectProperty
from kivy.uix.widget import Widget
Config.set('graphics', 'resizable', True)
from PyPDF2 import PdfFileReader, PdfFileWriter
FILE_LIST = []
PAGE_LIST = []
OUTPUT_LIST = []
def add_output_list(page_name):
for item in PAGE_LIST:
if item[0] == page_name:
if item[-1] not in OUTPUT_LIST:
OUTPUT_LIST.append(item[-1])
print(OUTPUT_LIST)
class FinalPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
class ParsingPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 4
def update_pages(self):
for item in PAGE_LIST:
self.inside = GridLayout()
self.inside.cols = 3
self.inside.select = Button(text="sel")
self.inside.add_widget(self.inside.select)
self.inside.select.bind(on_press=lambda x: self.select_button(item[-1]))
self.inside.clock = Button(text="c")
self.inside.add_widget(self.inside.clock)
self.inside.anticlock = Button(text="ac")
self.inside.add_widget(self.inside.anticlock)
self.add_widget(self.inside)
self.add_widget(Label(text=f'{item[0]}'))
self.inside_2 = GridLayout()
self.inside_2.cols = 2
self.inside_2.done = Button(text="Done")
self.inside_2.add_widget(self.inside_2.done)
self.inside_2.done.bind(on_press=self.done_button)
self.inside_2.cancel = Button(text="Cancel")
self.inside_2.add_widget(self.inside_2.cancel)
self.inside_2.cancel.bind(on_press=self.cancel_button)
def select_button(self, page_name):
add_output_list(page_name)
def done_button(self):
pass
def cancel_button(self):
pass
class SelectionPage(Widget):
def select(self, *args):
try:
if args[1][0].split('.')[-1] != 'pdf':
self.label.text = 'You can only select a PDF File.'
else:
self.label.text = args[1][0]
except:
pass
def add_button(self):
FILE_LIST.append(self.label.text)
pdf_app.files_page.update_files(self.label.text)
pdf_app.screen_manager.current = 'Files'
def next_button(self):
pdf_app.screen_manager.current = 'Files'
class FilesPage(Widget):
def update_files(self):
return FILE_LIST
class BrowsePage(Widget):
def browse_button(self):
pdf_app.screen_manager.current = 'Selection'
class PdfParserApp(App):
FILE_LIST = []
def build(self):
self.screen_manager = ScreenManager()
self.browse_page = BrowsePage()
screen = Screen(name='Browse')
screen.add_widget(self.browse_page)
self.screen_manager.add_widget(screen)
# Info page
self.selection_page = SelectionPage()
screen = Screen(name='Selection')
screen.add_widget(self.selection_page)
self.screen_manager.add_widget(screen)
self.files_page = FilesPage()
screen = Screen(name='Files')
screen.add_widget(self.files_page)
self.screen_manager.add_widget(screen)
self.parsing_page = ParsingPage()
screen = Screen(name='Parsing')
screen.add_widget(self.parsing_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == '__main__':
pdf_app = PdfParserApp()
pdf_app.run()
FILE_LIST is a variable outside all the classes (so that it can be used among the classes.
Now, I want to loop through this FILE_LIST and create labels on the next screen. But I want to do it using .kv files, so that I can keep uniformity in design across the application. My .kv file is :-
#, kv file implementation
#:import Label kivy.uix.label.Label
<BrowsePage>:
GridLayout:
size : root.width-200, root.height-200
pos : 100, 100
cols : 1
Label :
text: "Welcome to PDF Parser"
color: [ 66/255, 103/255, 178/255, 1]
font_size: 38
size_hint : (0.2, 0.5)
Label :
text : "Select the file(s)."
color: [ 66/255, 103/255, 178/255, 1]
font_size: 20
size_hint : (0.2, 0.5)
AnchorLayout:
anchor_x : "center"
Button:
text : "Browse"
size_hint : (.15, .15)
on_press : root.browse_button()
<SelectionPage>:
label: label
GridLayout:
size : root.width, root.height
cols :1
FileChooserIconView:
pos_hint: {"x":0, "top" : 1}
on_selection: root.select(*args)
Label:
id: label
size_hint : (.1, .1)
GridLayout:
cols : 3
size_hint : (1, .15)
AnchorLayout:
anchor_x : "center"
Button:
text : "Cancel"
size_hint : (.15, .15)
AnchorLayout:
anchor_x : "center"
Button:
text : "Add"
size_hint : (.15, .15)
on_press: root.add_button()
AnchorLayout:
anchor_x : "center"
Button:
text : "Next"
size_hint : (.15, .15)
on_press: root.next_button()
<FilesPage>
GridLayout:
size : root.width, root.height
cols: 1
on_parent:
for i in root.update_files(): txt = "Label {0}".format(i); self.add_widget(Label(text = txt, text_size=(cm(2), cm(2)), pos=self.pos,
id=txt, color=(1,1,1,1)))
I assume that when I switch the screen, it somehow create a fresh empty list. The looping code doesn't have any error, as if I hard code a list in my FilePage, then the labels appear.
Any help will be appreciated.
I personally believe that it's a sign of a bad design to place too much code into KV files. There should be a clear separation between structure of the widgets (as described in KV file) and actual logic, which should be implemented in Python. In this case we could make use of on_enter event to create labels upon displaying FilesPage screen. It'd look as follows
class PdfParserApp(App):
def build(self):
# ...
self.files_page = FilesPage()
screen = Screen(name='Files', on_enter=self.enter_files_page)
screen.add_widget(self.files_page)
self.screen_manager.add_widget(screen)
# ...
def enter_files_page(self, files_page, *args):
for f in FILE_LIST:
files_page.add_widget(Label(text=f))
This response is based on the question title.
Also i do agree with #Nykakin that is a good practice to separate your UI logic from UI design.
I personally take a different approach on this.
add_label.kv
<MainView>:
orientation: 'vertical'
<MyLabel>:
text: root.label_text
color: root.label_color
then in python:
add_label.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.properties import StringProperty, ListProperty
MY_FILE_LIST = [
{
'label_text': 'foo label_0',
'label_color': [1, .5, .4, 1]
},
{
'label_text': 'bar label_1',
'label_color': [.2, .4, .3, 1]
},
{
'label_text': 'foobar label_2',
'label_color': [.2, .9, .1, 1]
},
]
Builder.load_file('add_label.kv')
class MainView (BoxLayout):
def __init__(self, **kwargs):
super(MainView, self).__init__(**kwargs)
for file in MY_FILE_LIST:
lbl = MyLabel()
lbl.label_text = file['label_text']
lbl.label_color = file['label_color']
self.add_widget(lbl)
class MyLabel(Label):
label_text = StringProperty()
label_color = ListProperty()
class LabelApp (App):
def build(self):
return MainView()
if __name__ == '__main__':
LabelApp().run()
This method will work if you have a small number of widgets to add on the view at once (i do not exceed about 10 ~ 30). If your data set is > than 30 points then your app might hang until is creating and adding all widget instances on the view.
Also with this method is harder to update MY_FILE_LIST, and that update to be reflected on UI. Is not impossible but is a little tricky to do it.
RecycledView, is a better suited solution for large data sets. Or if you expect that updates on MY_FILE_LIST to be reflected on UI.

Kivy: undesirable behavior of custom mouse cursor when crossing left or top edge of app window

I want to make a custom mouse cursor in kivy.
This is what I have at the moment:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scatter import Scatter
from kivy.core.window import Window
#Window.show_cursor = False
KV = """
FloatLayout
BoxLayout
MyTextInput
MyMouse
<MyTextInput>:
font_size: 40
text: 'Some text'
<MyMouse>:
mouse_im_size: mouse_im.size
auto_bring_to_front: True
do_rotation:False
do_scale:False
do_translation_y:False
Image
id: mouse_im
size: 100, 100 / self.image_ratio
source: 'cursor-pink.png'
"""
class MyTextInput(TextInput):
pass
class MyMouse(Scatter):
def __init__(self, **kwargs):
Window.bind(mouse_pos=self.on_mouse_pos)
super(MyMouse, self).__init__(**kwargs)
def on_touch_down(self, *touch):
return
def on_mouse_pos(self, *args):
x,y = args[1]
self.pos = [x,y-self.mouse_im_size[1]]
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
MyApp().run()
the problem is that when I move the mouse beyond the left or upper edge of application, the cursor image remains within the app, and I want the mouse image to disappear just like when I move the mouse beyond the right or lower edge.
It seems the problem is that on_mouse_pos() only works when the mouse is inside the window.
I found a way to get the position of the mouse when it is outside the window, but I do not know how this can be used in my task. And maybe there is a better way to do this.
You can accomplish this by using the Window events on_cursor_enter and on_cursor_leave and making the cursor visible/invisible by using the opacity property:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scatter import Scatter
from kivy.core.window import Window
#Window.show_cursor = False
KV = """
FloatLayout
BoxLayout
MyTextInput
MyMouse
id: themouse
<MyTextInput>:
font_size: 40
text: 'Some text'
<MyMouse>:
mouse_im_size: mouse_im.size
auto_bring_to_front: True
do_rotation:False
do_scale:False
do_translation_y:False
Image
id: mouse_im
size: 100, 100 / self.image_ratio
source: 'cursor-pink.png'
"""
class MyTextInput(TextInput):
pass
class MyMouse(Scatter):
def __init__(self, **kwargs):
Window.bind(mouse_pos=self.on_mouse_pos)
Window.bind(on_cursor_leave=self.on_cursor_leave)
Window.bind(on_cursor_enter=self.on_cursor_enter)
super(MyMouse, self).__init__(**kwargs)
def on_touch_down(self, *touch):
return
def on_mouse_pos(self, *args):
x,y = args[1]
self.pos = [x,y-self.mouse_im_size[1]]
def on_cursor_leave(self, *args):
App.get_running_app().root.ids.themouse.opacity = 0
def on_cursor_enter(self, *args):
App.get_running_app().root.ids.themouse.opacity = 1
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
MyApp().run()
I added the themouse id to the MyMouse widget to accomplish this.
Here is another approach, but it requires a border around your basic layout:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.scatter import Scatter
from kivy.core.window import Window
#Window.show_cursor = False
KV = """
FloatLayout
canvas.before:
Color:
rgba: (1, 0, 0, 1)
Rectangle:
size: self.size
pos: self.pos
FloatLayout
id: mainlayout
size_hint: (None, None)
#pos: (50, 50)
#pos_hint: {'center_x': 0.5, 'center_y': 0.5}
canvas.before:
Color:
rgba: (0, 1, 0, 1)
Rectangle:
size: self.size
pos: self.pos
BoxLayout
size_hint: (1.0, 0.2)
pos_hint: {'center_x': 0.5, 'top': 1.0}
MyTextInput
StencilView:
size_hint: (1.0, 1.0)
pos_hint: {'x': 0, 'y': 0}
MyMouse
id: themouse
<MyTextInput>:
font_size: 40
text: 'Some text'
<MyMouse>:
mouse_im_size: mouse_im.size
auto_bring_to_front: True
do_rotation:False
do_scale:False
do_translation_y:False
Image
id: mouse_im
size: 100, 100 / self.image_ratio
source: 'cursor-pink.png'
"""
class MyTextInput(TextInput):
pass
class MyMouse(Scatter):
def __init__(self, **kwargs):
Window.bind(mouse_pos=self.on_mouse_pos)
super(MyMouse, self).__init__(**kwargs)
def on_touch_down(self, *touch):
return
def on_mouse_pos(self, *args):
x,y = args[1]
self.pos = [x,y-self.mouse_im_size[1]]
class MyApp(App):
def build(self):
self.mainlayout = None
self.mymouse = None
self.root = Builder.load_string(KV)
Window.bind(size=self.do_size)
Clock.schedule_once(self.do_size)
def do_size(self, *args):
if self.mainlayout is None:
self.mainlayout = self.root.ids.mainlayout
if self.mymouse is None:
self.mymouse = self.root.ids.themouse
self.mainlayout.size = (self.root.width - 2.0 * self.mymouse.mouse_im_size[0], self.root.height - 2.0 * self.mymouse.mouse_im_size[1])
self.mainlayout.pos = self.mymouse.mouse_im_size
MyApp().run()
This uses a StencilView to clip the drawing of the cursor to the inside of the man layout.

How to refresh kivy RecycleView every time data changes?

I am trying to create a simple attendance app.
When program is launched all labels are in deselected list
Expected behavior: when any label is selected data moves to the selected list and now selected labels are in the end of the joined list. Then RecycleView refreshes to display this change.
So I managed to make the data to move from one list to another, but I can't make RecycleView to refresh
I tried using ids but failed
I hope someone can help me. I think this is a routine problem for people who are knowledgeable, but for noobs like me it is not.
I am asking question on this site for the first time so sorry in advance
here is the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from datetime import datetime
import kivy
from kivy.config import Config
Config.set('graphics', 'width', '300')
Config.set('graphics', 'height', '500')
importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']
deselected_list = importedlist[:]
selected_list = []
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view.
and add/remove items from lists
'''
self.selected = is_selected
if self.selected and self.text in deselected_list:
selected_list.append(self.text)
deselected_list.remove(self.text)
print(selected_list)
elif not self.selected and self.text in selected_list:
deselected_list.append(self.text)
selected_list.remove(self.text)
print(deselected_list)
class RV(RecycleView):
# this needs to be updated every time any label is selected or deselected
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = ([{'text': str(row)} for row in sorted(deselected_list)]
+ [{'text': str(row)} for row in sorted(selected_list)])
class Screen(BoxLayout):
now = datetime.now()
def nowdate(self):
return self.now.strftime('%d')
def nowmonth(self):
return self.now.strftime('%m')
def nowyear(self):
return self.now.strftime('%y')
def nowhour(self):
return self.now.strftime('%H')
def nowminute(self):
return self.now.strftime('%M')
Builder.load_string('''
#:import datetime datetime
<Screen>:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: 30
TextInput:
id: date
text: root.nowdate()
TextInput:
id: date
text: root.nowmonth()
TextInput:
id: date
text: root.nowyear()
TextInput:
id: date
text: root.nowhour()
TextInput:
id: date
text: root.nowminute()
Button:
RV:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(45)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
Button:
size_hint_y: None
height: 30
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
''')
class TestApp(App):
def build(self):
return Screen()
if __name__ == '__main__':
TestApp().run()
Alright, so I did actually stumble a few times trying to sort this out but I think I've got it. What I did was create two recycle views and a CustomLabel that has access to ButtonBehaviors so you can use 'on_press' instead of 'on_touch_down' (which propagates through the entire tree rather than the interacted with element).
Example Video
The py file:
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.floatlayout import FloatLayout
# Create a Custom ButtonLabel that can use on_press
class ButtonLabel(ButtonBehavior, Label):
# App.get_running_app() lets us traverse all the way through our app from
# the very top, which allows us access to any id. In this case we are accessing
# the content of our selected_list_view of our app
#property
def selected_list_content(self):
return App.get_running_app().root.ids.selected_list.ids.content
# And in this case, we're accessing the content of our deselected_list_view
#property
def deselected_list_content(self):
return App.get_running_app().root.ids.deselected_list.ids.content
# This is our callback that our Label's will call when pressed
def change_location(self):
# If the label's parent is equal* the selected list, we remove the label from its
# parent, and then we add it to the other list
if self.parent == self.selected_list_content:
self.parent.remove_widget(self)
self.deselected_list_content.add_widget(self)
# If the label's parent is not the selected list, then it is the deselected list
# so we remove it from its parent and add it to the selected list
else:
self.parent.remove_widget(self)
self.selected_list_content.add_widget(self)
#* Note: Kivy uses weak references. This is why we use ==, and not 'is'
# We create a CustomRecycleView that we will define in our kv file
class CustomRecycleView(RecycleView):
pass
class MainWindow(FloatLayout):
pass
class ExampleApp(App):
def build(self):
# We create an instance of the MainWindow class, so we can access its id
# to import our list. Otherwise we would have nothing to add the list too
main_window = MainWindow()
importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']
# We create a Label for each Name in our imported list, and then add it
# to the content of selected list as a default
# I'm sure you'll be importing our own lists in a different manner
# This is just for the example
for name in importedlist:
NameLabel = ButtonLabel(text=(name))
main_window.ids.selected_list.ids.content.add_widget(NameLabel)
return main_window
if __name__ == '__main__':
ExampleApp().run()
The kv file:
#:kivy 1.10.0
# We create a reference to the ButtonLabel class in our py file
<ButtonLabel>:
# We add our callback to our ButtonLabels on press event, on_press
on_press: root.change_location()
# We create a reference to our CustomRecycleView class in our py file
<CustomRecycleView>:
# We create a GridLayout to store all of the content in our RecycleView
GridLayout:
# We give it the id content so we can define the two property values in
# ButtonLabel class in the py file
id: content
size_hint_y: None
# One column because we want it to be vertical list list
cols: 1
# This set up, as well as size_hint_y set to None
# is so we can scroll vertically without issue
row_default_height: 60
height: self.minimum_height
<MainWindow>:
# We then create two instances of our CustomRecycleView, give them the ids
# referenced by the ButtonLabel methods as well as give them equal share of the
# screen space so they do not step on each others toes
# The canvas here is just for prototyping purposes to make sure they are the
# properly defined sizes. You can do whatever with them you would like tbh.
CustomRecycleView:
id: selected_list
size_hint: 1, .5
pos_hint: {'x': 0, 'y': .5}
canvas:
Color:
rgba: 100, 0, 0, .2
Rectangle:
size: self.size
pos: self.pos
CustomRecycleView:
id: deselected_list
size_hint: 1, .45
canvas:
Color:
rgba: 0, 0, 100, .2
Rectangle:
size: self.size
pos: self.pos

Kivy Python: Accordion inside an accordion, using a variable

I am trying to create an accordion menu (no.1), in which there is another accordion menu (no.2).
The size of accordion no.2 will be defined by the user (an example of the outcome is shown in this image).
The issue is that though I have managed to create a class that creates accordion no.2 following users input - I can`t seem to find the way to display it on the screen.
This is my py code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.properties import NumericProperty
wide = 0
long = 0
class AccordionClass():
def calc(val):
number = val
root = Accordion(size= (200,700), pos = (50,80), orientation= 'vertical')
for x in range(number):
print ('x = ',x)
item = AccordionItem(title='Title %d' % x)
item.add_widget(Label(text='Very big content\n' * 10))
root.add_widget(item)
print ('END')
return root
class LoginScreen(GridLayout):
numOfStories = NumericProperty()
def printTxt(self, text, TextInputId):
pass
def addFloor(self,text):
self.numOfStories = int(text)
print ('self.numOfStories = ', self.numOfStories)
rootAc = AccordionClass.calc(self.numOfStories)
return rootAc
pass
class screen2(App):
def build(self):
self.root = GridLayout()
return LoginScreen()
if __name__ == "__main__":
screen2().run()
and my kv code:
<TextInput>:
multiline: False
size:150,23
font_size: 12
padding: [5, ( self.height - self.line_height ) / 2]
<Label>:
size:120,18
font_size: 12
padding: [5, ( self.height - self.line_height ) / 2]
<LoginScreen>:
canvas:
Color:
rgb: (0.93, 0.93, 0.93,1)
Rectangle:
pos: self.pos
size: self.size
GridLayout:
size:root.size
cols:2
Accordion:
size_hint: (1.0,0.2)
orientation: 'vertical'
AccordionItem:
title: 'Plot'
GridLayout:
AccordionItem:
title: 'Number'
GridLayout:
Label:
text: "Number"
color: [0, 0, 0, 1]
pos:root.x, root.top-self.height-100
TextInput:
pos:root.x+120, root.top-self.height-100
id: NumOfStories
on_text_validate: root.addFloor(NumOfStories.text)
AccordionItem:
title: 'Another number'
Button:
background_color: (5,5,5,1)
Any idea how to solve this issue?
Thanks
It isn't displaying because of you returning an instance of Accordion into nothing in addFloor/calc in on_text_validate. To create a widget you have to call <parent>.add_widget(<widget>), so let's do that:
on_text_validate: root.add_widget(root.addFloor(NumOfStories.text))
Then there's the thing your calc() is a class method for now and you either need to use self as an additional parameter (and have even more mess), or use the #staticmethod decorator, which makes the calc() free of class stuff and let you use it this way Class.method(...)
#staticmethod
def calc(val):
After that a new Accordion will show up, but the sizing and positioning is up to you. Also, by default there's probably no background for that widget, so you'll end up with putting it there via canvas instructions.

Categories