Kivy scrollview with the kivy language and a custom widget - python

I'm using Kivy, a Python Library, and i'm having trouble getting a scroll view to work, and organizing it into a .kv file.
I have a big system that's broken up into many .py and .kv files.
I am also using kivy's screen manager
ui_manager.py
#ui_manager.py
class UIManager():
_ScreenManager = None
def __init__(self, inScreenManager):
self.ScreenManager = inScreenManager # The main app constructs the manager, sends to me, then returns it as the root widget.
main.py
#main.py
class MyApp(App):
def build(self):
screenManager = ScreenManager()
uiManager = ui_manager.UIManager(screenManager)
return screenManager
Obviously I ommited a little code, but this is almost exactly what happens.
One of the screns needs to scroll. It will have a long graphic and many buttons.
The Kivy documentation, as usual, only tells me how to do it via code, NOT via the kivy language (.kv files)
http://kivy.org/docs/api-kivy.uix.scrollview.html
I want a class, OrangeWidget, to hold the scrolling widget, so I can play with its data later on.
Here is my best attempt at getting an organized scrollview working
orange_widget.py
#orange_widget.py
class OrangeWidget(Screen):
pass
class OrangeGraphic(Widget):
pass
Orange.kv
<OrangeLineWidget>
ScrollView:
size_hint: (None, None)
size: (400, 1200)
OrangeLineGraphic:
pos: root.pos
<OrangeGraphic>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 0
size: 30, self.height * 2
Label:
text: "Hello"
I know it's a lot, and it's not looking well organized already, but i'm just trying to figure out how to get the dang thing working properly.
Here is the current result: It won't scroll, and for whatever reason it's been "boxed" to the right, instead of taking up the full screen

as answered on the ML, here you don't define the Label pos, your OrangeGraphic being a simple widget, not a layout, you need to do it:
<OrangeGraphic>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 0
size: 30, self.height * 2
Label:
text: "Hello"
pos: root.pos
size: root.size

Looks like the trick, for this particular "sliding" screen, was to use a "Relative Layout"
https://groups.google.com/forum/#!topic/kivy-users/RwuI8QGm3fw
Here is the updated code:
orange_widget.py
class OrangeWidget(Screen):
def __init__(self, **kwargs):
super(OrangeWidget, self).__init__(**kwargs)
scrollView = ScrollView(size_hint=(1, 1))
# add custom widget into that layout
customWidget = OrangeGraphicWidget(height=1200, size_hint_y=None)
#layout.bind(minimum_height=layout.setter('height'))
scrollView.add_widget(customWidget)
self.add_widget(scrollView)
class OrangeGraphicWidget(RelativeLayout):
pass
OrangeWidget.kv
<OrangeWidget>
<OrangeGraphicWidget>
canvas:
Color:
rgba: 1, .5, 0, 1
Rectangle:
pos: self.center_x - 15, 20
size: 30, self.height - (self.height / 10)
Button:
text: "Button 1"
pos: root.pos
size_hint: (None, None)
Button:
text: "Button 2"
pos_hint: {'center_x': .5, 'center_y': .95}
size_hint: (None, None)

Related

How can I restart kivy animation every time its called upon?

I am making a simple weather app. The user enters a location and gets results in plain text. I have made a loading screen that has a spinning animation. When the program is loading these weather results, it changes to the loading screen and the animation spins well.
I have the animation set for a duration of 20 seconds for now. This allows the first instance of my load screen display more than enough time to yield results and then change to the results screen (normally about 5 seconds). That seemingly works well.
My issue is: Once the animation has completed its 20 second run (in the background) it doesn't re start when called for again. It seems the app will allow it to be called once only. For it to animate again, the app must be closed completely and restarted.
Ideally I'd like the animation to stop spinning and be reset as soon as the load screen has been exited (so once the results have been delivered). The user can get weather multiple times, so I need it to start again every time the load screen is displayed.
I have tried the undesired solution of having it constantly spinning in the background so that when the load screen is shown, its spinning.
But, even then I am having trouble getting it to repeat... (anim.repeat = True doesn't work for me)
Here is a working example that has been stripped back as much as possible to still behave the same as my real app.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.animation import Animation
import time
from threading import Thread
class HomeScreen(Screen):
pass
class Results1Screen(Screen):
pass
class LoadScreen(Screen):
pass
GUI = Builder.load_file("main.kv")
class TestApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids['screen_manager']
screen_manager.current = screen_name
def spin_widget(self):
widget = self.root.ids["load_screen"].ids["spinning_widget"]
anim = Animation(angle=-2000, duration=20)
anim.repeat = True # this doesn't seem to work?
anim.start(widget)
def thread1(self): # this starts the actual "work" of the app by calling get_briefing() below
p2 = Thread(target=lambda: self.get_briefing())
p2.start()
def get_briefing(self):
time.sleep(5)# I have added a 5sec sleep to emulate work being done
self.change_screen("results_1_screen") # screen change from loading screen to results screen after work complete
TestApp().run()
main.kv (just for screen managing)
#:import utils kivy.utils
#:include kv/homescreen.kv
#:include kv/results1screen.kv
#:include kv/loadscreen.kv
GridLayout:
cols: 1
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
pos_hint: {"top": 1, "left": 1}
size_hint: 1, 0
ScreenManager:
id: screen_manager
HomeScreen:
name: "home_screen"
id: home_screen
Results1Screen:
name: "results_1_screen"
id: results_1_screen
LoadScreen:
name: "load_screen"
id: load_screen
homescreen.kv
<HomeScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Button:
text: "GO!"
pos_hint: {"top": .2, "center_x": .5}
size_hint: .5, .08
padding: 20, 20
on_release:
app.spin_widget()
app.change_screen("load_screen") # widget spinning and going to load screen
app.thread1() # start the app working in background
results1screen.kv (in real app, this has desired results displayed)
<Results1Screen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
text: "RESULTS"
pos_hint: {"top": .9, "center_x": .5}
size_hint: 1, .1
Button:
text: "back"
size_hint: .1, .1
on_release:
app.change_screen("home_screen")
loadscreen.kv
<LoadScreen>:
FloatLayout:
canvas:
Color:
rgb: utils.get_color_from_hex("#000523")
Rectangle:
size: self.size
pos: self.pos
Label:
text: "loading"
SpinningWidget:
id: spinning_widget
angle: 0
size_hint: .1, .1
pos_hint: {"center_x": .5, "center_y": .6}
<SpinningWidget#Image>
angle: 0
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: root.center
canvas.after:
PopMatrix
I have tried a couple of ways of exiting or resetting the spin widget function after the get_briefing function has finished but to no avail. I have tried anim.cancel() and things like exit(self.spin_widget()) in an attempt to reset it, ready for its next call up.
Can someone please help me out, I can't seem to find an answer in Kivy docs or anywhere else online for that matter. Thank you.
Try resetting the angle to 0 before each Animation:
def spin_widget(self):
widget = self.root.ids["load_screen"].ids["spinning_widget"]
widget.angle = 0
anim = Animation(angle=-2000, duration=20)
anim.repeat = True # this doesn't seem to work?
anim.start(widget)
The problem is that after the first Animation, the angle value is -2000, so any Animation after that will not actually change the angle. Same problem with repeat.

Adding a kivy custom widget only adds the first layout

I am trying to add a custom widget for each item in a list.
The first one works fine, but every on following the first, only the first Layout is shown. (yellow square)
I have already tried to add_widget(Builder.load_string the entire Custom Widget, leading to the same result.
The Layout class referenced to the custom widget:
class StockPortfolio(FloatLayout):
pass
The function to add the widgets:
portfolio_list = [1, 2, 3]
def add_stock_portfolio(self, layout):
layout.clear_widgets()
for i in self.portfolio_list:
layout.add_widget(StockPortfolio())
And the beginning of the custom widget kv:
<StockPortfolio#FloatLayout>
id: stockportfolio
size_hint: None, None
height: app.root.height * .18
width: app.root.width -10
canvas:
Color:
rgba: .98, .98, 0, .5
Rectangle:
size: self.size
pos: self.pos
Button:
background_normal: ''
background_down: ''
background_color: [0, 0, 0, 0]
pos: 5, 5
on_release:
app.go_screen(4)
app.load_popup2()
app.update_current(portfolioticker1.text, portfoliocompany.text)
BoxLayout:
orientation: "vertical"
pos: 5, 5
size_hint: None, None
height: app.root.height * .18
width: app.root.width -10
canvas:
Color:
rgba: .98, .98, .98, 1
Rectangle:
size: self.size
pos: self.pos
Problems
In your Kivy App, there are two different classes of StockPortfolio(). The one declared in the kv file is a Dynamic Class whereas the other one declared in Python script is a class Rule. Dynamic class does not require an implementation in Python script.
StockPortfolio objects added are stacked on top of each other.
Solution
In the kv file, change the Dynamic class to class rule by removing #FloatLayout.
Provide button's size
Provide new position for each stock portfolio added into layout.
Snippets - kv file
<StockPortfolio>:

kivy scrollview inside gridlayout on label

I trying to create a one screen app with one root rule set. by pressing a button i should see a scrollable text in the grid next to the button. In all the example solutions I see that scrollview is working in similar KV files that I have seen in other questions. Could someone please identify what I have missed in KV file.
my .py file:
import kivy
import string
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.properties import ObjectProperty
from kivy.properties import StringProperty
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
class RootContainer(BoxLayout):
instance = ObjectProperty(None)
def __init__(self, **kwargs):
super(RootContainer, self).__init__(**kwargs)
def clickAction1(self, instance):
#identify the button pressed
buttonText = instance.text
self.lbl1.text = instance.text + " some text goes here ... "
myresult = " this is scrolling text.\n " * 30
self.lbl5.text = myresult
class MBApp(App):
def build(self):
return RootContainer()
if __name__ == '__main__':
MBApp().run()
my KV file:
#:kivy 1.0.9
<RootContainer>:
id: theRoot
lbl1: my_labelC
lbl5: my_labelCS
BoxLayout:
orientation: 'vertical'
spacing: 20
padding: 20
canvas:
Color:
rgb: 0, .33, 0
Rectangle:
pos: self.pos
size: self.size
Button:
text: "This is 1st button"
text_size: self.size
size_hint: (.5,1)
on_press: theRoot.clickAction1(self)
Button:
text: "This is 2nd button"
text_size: self.size
size_hint: (.5,1)
on_press: root.clickAction1(self)
GridLayout:
rows: 2
cols: 1
spacing: 10
padding: 10
canvas:
Color:
rgb: .7, .63, 0
Rectangle:
pos: self.pos
size: self.size
Label:
id: my_labelC
canvas.before:
Color:
rgb: 0,0,0
Rectangle:
pos: self.pos
size: self.size
text: "Header text for button clicked ......."
text_size: self.size
ScrollView:
GridLayout:
cols:1
rows:1
height: self.minimum_height
Label:
id: my_labelCS
text: "Scrolling text goes here ....."
I hope this is not a duplicate. Any other suggestions to code are also welcome. Thank you.
You don't set the size of your Label, so the default applies, as any widget, the defaults are
size: 100, 100
size_hint: 1, 1
since size_hintis 1, 1, and the parent is a layout, size itself is overriden by the parent's available space
since you set size: minimum_size in the parent layout, it'll give the minimum size its children require, but the Label doesn't ask for any space, it's size_hint of 1, 1 means that it's happy to take all the available space. Kivy solve this situation by not giving it any space, so the Label size ends up being 0, 0, and the GridLayout's size as well.
So you want to disable size_hint (at least for the height, of the Label, and instead set it to the texture heights
size_hint_y: None
height: self.texture_size[1]
Which is usually sided with setting the texture width to the available width, by setting text_size.
text_size: self.width, None
size_hint_x: 1 # this is the default, so this line is not required, but you want to be sure not to disable this default
Do you really need the GridLayout inside your scrollview? This works for me:
ScrollView:
Label:
id: my_labelCS
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: "Scrolling text goes here ....."

kivy - Bind button to spawn additional instance of widget?

I've created two Scatter widgets that can be resized, what I'm looking for is a way to spawn more Scatter widgets (potentially up to 20-ish) each time a button is pressed. I tried to write and bind an addfunction() to a Button, but it doesn't seem to work.
I'm looking for a way to spawn a new instance of the Scatterer widget (along with the child widget Drawer which has a canvas to show it's occupied space) when prompted.
Here's my python code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
class Scatterer(Scatter):
pass
class Drawer(FloatLayout):
pass
class MainWindowWidget(FloatLayout):
def addfunction(*args):
root = MainWindowWidget()
s = Scatterer()
d = Drawer()
root.add_widget(s)
s.add_widget(d)
print("button is pressed")
class TestApp(App):
def build(self):
return MainWindowWidget()
if __name__ == '__main__':
TestApp().run()
and my .kv file:
#:kivy 1.0.9
<MainWindowWidget>
Scatterer:
id: scatter2
size: 80, 80
Drawer:
size: scatter2.size
Scatterer:
id: scatter1
size: 80, 80
Drawer:
size: scatter1.size
Button:
text: 'press'
size_hint: None, None
size: 100, 100
pos: 100, 0
on_release: root.addfunction()
<Scatterer>:
do_rotation: False
size_hint: None, None
size: self.size
canvas.after:
Color:
rgba: 1, 1, 0, 0.5
Rectangle:
size: self.size
pos: self.pos
<Drawer>:
size: self.size
canvas:
Color:
rgba: 0, 1, 0, 0.3
Rectangle:
pos: self.pos
size: self.size
root = MainWindowWidget()
Your problem is that you write this, then add the new Scatter to root, but then never do anything with root so it never gets displayed.
You don't want to add the Scatterer to a new MainWindowWidget, but to the current one whose method is running to make the new Scatterer. So replace the declaration of your method with
def addfunction(self, *args):
s = Scatterer()
d = Drawer()
self.add_widget(s)
s.add_widget(d)
print("button is pressed")

Centering TextInput within a BoxLayout in Kivy

Here is a screenshot of my kivy app. I am trying to get the TextInput in the bottom left to be centered in the BoxLayout which it is in, and I don't want it to be the same size as the layout, I want it to be much smaller. This BoxLayout in question resides in the bottom half of the screen. I have tried setting the TextInputs propertycenter:self.parent.center but this doesn't work. As you can see, I have printed the center coords from the BoxLayout into the TextInput using that very line, self.parent.center, with the correct result. Yet, setting the TextInputs center or position to these coords is not centering it, it doesn't move... what am I doing wrong?
py file:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
class TimeTabler(Widget):
pass
class TimerApp(App):
def build(self):
return TimeTabler()
if __name__ == "__main__":
TimerApp().run()
****kv file:****
#:kivy 1.0
BoxLayout:
orientation: 'vertical'
size: root.size
BoxLayout:
orientation: 'vertical'
Label:
text: 'TimeTabler'
BoxLayout:
TextInput:
text: '%s' % (self.parent.center) # why does this work here
size_hint: None, None
width: sp(200)
height: sp(30)
center: self.parent.center # but not here
You gave the TextInput size_hint: None, None, therefore the BoxLayout doesn't try to manually give it the right size, and it assumes the default size of 100, 100. Just delete the size_hint line to fix it.
Also, several widgets have lines like size: self.size. This is meaningless, self refers to the widget itself, and clearly the line does nothing since it just tries to set the size to what it already is.
Things would also be simpler if you made your TimeTabler inherit from BoxLayout instead of Widget. That way you wouldn't need to manually set it's child BoxLayout's size.
Edit: It looks like I misunderstood what you wanted, here's an example that uses an AnchorLayout to center the TextInput:
<TimeTabler>
BoxLayout:
orientation: 'vertical'
size: root.size
on_touch_down: print self.pos, self.size
canvas:
Color:
rgba: 0, 1, 1, .3
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
orientation: 'vertical'
size: self.size
Label:
text: 'TimeTabler'
BoxLayout:
id: bl
on_touch_down: print 'center', self.center
canvas:
Color:
rgb: 1,1,1
Line:
rectangle: self.x, self.y, self.width, self.height
AnchorLayout:
TextInput:
size_hint: None, None
text: '%s, %s' % (self.get_center_x(), self.get_center_y())
I think your problem was the BoxLayout automatically sets the position of the TextInput even when it is setting its own size. A simple way around this is to just next the TextInput in another widget, in this case an AnchorLayout that takes care of the centering for you. You could also just use a Widget and your previous mechanism of setting the TextInput center.

Categories

Resources