How can a dynamic class access another class' attributes such as root.width/root.height? - kivy

Here is a class NewWindow with a GridLayout in a FloatLayout, a dynamic class NewButton inheriting from ToggleButton, and a class in the Python file that receives the GridProperty in order to add_widget NewButton into it.
The overall goal is to have a button that adds a toggle button into the grid layout, and for all button/toggle buttons to be able to dynamically change size for different screen resolutions. The issue I'm having is that I can't simply write (root.width**2 + root.height**2) / 12**4 for NewButton. I think it's because "root" in NewWindow is not the same as "root" in NewButton, but I do not have a solution.
Is there a way for me to access NewWindow's "root" inside of NewButton? I ultimately just want all my buttons to match each other's sizes and for it to be dynamic, so I don't want to simply write a static "30" for font_size.
# File: main.kv
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
GridLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7
size_hint: .4, .5
<NewButton#ToggleButton>:
text: "BUTTON"
size_hint_y: ???
#height: ???
font_size: ???
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
# File: MainApp.py
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def AddButton(self):
self.parentGrid.add_widget(NewButton())

You are right the two roots are different. One is as you say the size of the new window, but the other would only be the size of GridLayout. The problem you have with the size_hint is because you are using a GridLayout. If you can change to example a BoxLayout that would work if not the size_hint: None, None and then set the size: would work:
To get the the NewWindow Root use app.root.width and app.root.heigth
main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.togglebutton import ToggleButton
class NewButton(ToggleButton):
pass
class NewWindow(Screen):
parentGrid = ObjectProperty(None)
def __init__(self, **kwargs):
super(NewWindow, self).__init__(**kwargs)
def addButton(self):
print('test')
self.parentGrid.add_widget(NewButton())
class Manager(ScreenManager):
pass
class TestApp(App):
def build(self):
return Builder.load_file('test.kv')
if __name__ == '__main__':
TestApp().run()
.kv:
Manager:
NewWindow:
name: 'new'
<NewButton#ToggleButton>:
text: "BUTTON18"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint_x: None
size_hint: 1, 1
#size: 10, 10
font_size: (app.root.width**2 + app.root.height**2) / 12**4
<NewWindow>:
parentGrid: parentGrid
FloatLayout:
Button:
text: "Add New Button"
pos_hint: {"x": 0.05, "top": 0.7}
size_hint: .4, 0.05
font_size: (root.width**2 + root.height**2) / 12**4
on_release:
root.addButton()
BoxLayout:
id: parentGrid
cols: 1
pos_hint: {"x": 0.5, "top": 0.7}
size_hint: .4, .5
canvas:
Color:
rgba: 1,1,1,1
Rectangle:
size: self.size
pos: self.pos
# Cannot simply use root.width or root.height, also size_hint: .4, 0.05 doesn't work
I did also add the screenmanager to be able to access the width and height
Hope this helps

Related

Kivy AnchorPoint is not in the right place

I am trying to just put a piece of text centred in the top on the window but it ends up truncated in the bottom left.
I have the following python code:
# anchortest.py
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.widget import Widget
class Root(Widget):
pass
class TextTitle(AnchorLayout):
pass
class AnchorTestApp(App):
def build(self):
return Root()
AnchorTestApp().run()
and it's associated kv file.
# anchortest.kv
<Root>
TextTitle:
<TextTitle>
anchor_x: 'center'
anchor_y: 'top'
Label:
id: score
text: 'Hello World'
font_name: 'Courier New'
font_size: 40
pad_x: 10
pad_y: 10
When I run the app I get this:
Don't use AnchorLayout for this case, just use BoxLayout with
pos_hint: {'top': 1}
size_hint_y: None
from kivy.app import App
from kivy.lang.builder import Builder
KV = """
Screen:
BoxLayout:
orientation: 'vertical'
BoxLayout:
pos_hint: {'top': 1}
size_hint_y: None
canvas.before:
Color:
rgba: [0.5, 0, 1, 1]
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Hello World'
font_size: sp(60)
BoxLayout:
Label:
text: 'Screen text'
"""
class TestApp(App):
def build(self):
return Builder.load_string(KV)
TestApp().run()
You also can use Label parametrs valign and halign:
KV = """
Screen:
Label:
text: 'Hello World'
font_size: sp(60)
text_size: self.size
halign: 'center'
valign: 'top'
"""
In order to control sizing, you must specify text_size to constrain
the text and/or bind size to texture_size to grow with the text.
If you really want to use AnchorLayout for some reason, it's done this way. Following the kivy documentation, in order for the AnchorLayout rules to apply, the Label must be set with
size_hint_y: None
height: self.height
The size_hint is a tuple of values used by layouts to manage the sizes
of their children. It indicates the size relative to the layout’s size
instead of an absolute size (in pixels/points/cm/etc)
KV = """
AnchorLayout:
anchor_x: 'center'
anchor_y: 'top'
Label:
text: 'Hello World'
font_size: sp(60)
size_hint_y: None
height: self.height
"""

Move search items more to the top

I have the following code snippet displaying a popup Window.
class SearchBar(TextInput):
articles = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(text=self.on_text)
self.bind(articles=self.on_articles)
def on_text(self, *args):
WikiSearcher().get_search_results(self.text, self)
def on_articles(self, *args):
self.parent.parent.children[0].update_recommendations(self.articles)
class SearchItem(ButtonBehavior, Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.url = ''
def on_release(self):
print (self.url)
class Recommendations(BoxLayout):
def update_recommendations(self, recommendations: list):
for (search_item, recommendation) in zip(reversed(self.children), recommendations):
search_item.text = recommendation
try:
search_item.url = wikipedia.page(recommendation).url
except:
search_item.url = 'https://en.wikipedia.org/wiki/' + recommendation.replace(" ", "_")
Builder.load_string('''
<SearchItem>:
font_size: self.height * 0.4
canvas.before:
Color:
rgba: [0.8, 0.8, 0.8, 1] if self.state == 'normal' else [30/255, 139/255, 195/255, 1]
Rectangle:
#pos: self.pos
#size: self.size[0], self.size[1]
size: root.size[0], root.size[1]
pos: self.pos[0], self.pos[1]
Color:
rgba: 0, 0, 0, 1
Line:
rectangle: self.x, self.y, self.width, self.height
color: 0, 0, 0, 1
<Urlpup>:
size_hint: 1, 1
auto_dismiss: False
title: 'Enter URL or keywords'
BoxLayout:
canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
padding: self.width * 0.1
spacing: self.height * 0.1
BoxLayout:
orientation: 'horizontal'
Spinner:
id: spinner
size_hint: 0.5, 0.4
pos_hint: { 'top' : 1 }
text: 'en'
values: 'en', 'fr', 'de', 'it'
SearchBar:
id: url_input
size_hint: 1, 0.4
pos_hint: { 'center_x' : 0.5, 'top' : 1 }
multiline: False
font_size: self.height*0.8
Button:
text: 'OK'
size_hint: 0.5, 0.4
pos_hint: { 'top' : 1 }
on_press: root.dismiss()
on_release: app.create_new_article(url_input.text)
Recommendations:
id: recommendations
orientation: 'vertical'
SearchItem
SearchItem
SearchItem
SearchItem
''')
class Urlpup(Popup):
pass
Here is a picture of the popup Window
You can see that the SearchItems are not tightly below the SearchBar, where I'm struggling to position it. Can you fix it? On the picture there are 4 SearchItems but I plan to have 10 Searchitems so it should work for 10 SearchItems or be flexible when I decide to change the number of SearchItems
The BoxLayout tries to distribute its available space equally among its children, unless you tell it otherwise. So, your top level BoxLayout in the Urlpup class evenly divides its space between the BoxLayout containing the TextInput and the Recommendations widget. You can reduce the space between those two widgets by limiting the space given to the horizontal BoxLayout. You can do that by specifying a height for that BoxLayout, like this:
BoxLayout:
size_hint_y: None
height: 50
orientation: 'horizontal'
Spinner:
id: spinner
size_hint: 0.5, 1
pos_hint: { 'top' : 1 }
text: 'en'
values: 'en', 'fr', 'de', 'it'
SearchBar:
id: url_input
size_hint: 1, 1
pos_hint: { 'center_x' : 0.5, 'top' : 1 }
multiline: False
font_size: self.height*0.8
Button:
text: 'OK'
size_hint: 0.5, 1
pos_hint: { 'top' : 1 }
on_press: root.dismiss()
on_release: app.create_new_article(url_input.text)
Note that the children of this BoxLayout all have a size_hint_y of 1.0, so they will all be the height that is specified for their container.
The Recommendations widget gets the remaining vertical space (minus the spacing). You can move the Recommendations even closer by reducing the spacing.

Kivy: RecycleView not updating

I have two screens in my Kivy-based app, each with a RecycleView to display lists. Both RVs should update when I press a button (add_button_clicked()) on one screen. Currently, the first RV (AddRecipe screen) works mostly as intended. However, the RV on the ViewList screen does not update with new data.
I am new to Python and even newer to Kivy - what am I missing here?
.py:
#! python3
# GroceryList.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
from kivy.properties import ListProperty
from kivy.properties import ObjectProperty
from kivy.uix.recycleview import RecycleView
selectedMeals = []
ingredients = []
class ViewList(Screen):
def updateList(self, portions, recipe):
ingredients.append((portions, recipe))
print(ingredients) # This proves updateList is getting called
##THE FOLLOW RV THINGS DON'T WORK:
self.ids.shoplist.data = [{'text': '%s (%s)' %(ingredients[i][0], ingredients[i][1])}
for i in range(len(ingredients))]
self.ids.shoplist.refresh_from_data()
##
class AddRecipe(Screen):
recipes = ListProperty()
recipes = {'Guacarole':5, 'Salsa':3, 'Chips':1} # Sample dict for demo
def add_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value+1)
def subtract_one(self):
if self.addportions.text != '':
value = int(self.addportions.text)
self.addportions.text = str(value-1)
def add_button_clicked(self, recipe, portions):
if recipe != '':
selectedMeals.append((recipe, portions))
self.ids.mealslist.data = [{'text': '%s (%s)' %(selectedMeals[i][0], selectedMeals[i][1])}
for i in range(len(selectedMeals))]
self.ids.mealslist.refresh_from_data()
ViewList().updateList(portions, recipe)
def spinner_clicked(self, val):
self.addportions.text = str(self.recipes[val])
class WindowManager(ScreenManager):
pass
class GroceryList(App):
mealsRVdata = ListProperty()
shoppingRVdata = ListProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
def build(self):
Builder.load_file("grocerylist.kv")
screens = [ViewList(name='viewlist'), AddRecipe(name='addrecipe')]
for screen in screens:
self.sm.add_widget(screen)
self.sm.current = "addrecipe"
return self.sm
if __name__ == '__main__':
GroceryList().run()
And .kv:
#:kivy 1.11.1
# GroceryList.kv
# Used by GroceryList.py
WindowManager:
AddRecipe:
ViewList:
<ViewList>:
name: "viewlist"
shoplist: shoplist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.8)
RecycleView:
id: shoplist
data: app.shoppingRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.2)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
root.manager.transition.direction = "left"
<AddRecipe>:
name: "addrecipe"
addportions: addportions
mealslist: mealslist
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1, 0.08)
Label:
size_hint: (0.64, 1)
font_size: 20
text: "Select a meal to add"
Label:
size_hint: (0.36, 1)
font_size: 20
text: "Select portions"
BoxLayout:
size_hint: (1, 0.08)
Spinner:
id: add_spinner
size_hint: (0.64, 1)
text: ""
values: root.recipes.keys()
on_text:
root.spinner_clicked(add_spinner.text)
Button:
size_hint: (0.12, 1)
font_size: 36
text: "-"
on_release: root.subtract_one()
Label:
id: addportions
size_hint: (0.12, 1)
font_size: 24
text: ''
Button:
size_hint: (0.12, 1)
font_size: 36
text: "+"
on_release: root.add_one()
FloatLayout:
size_hint: (1, 0.08)
Button:
size_hint: (0.4, 1)
pos_hint: {"x": 0.3, "top": 1}
text: "Add to shopping list"
on_release:
root.add_button_clicked(add_spinner.text, addportions.text)
BoxLayout:
size_hint: (1, 0.68)
RecycleView:
id: mealslist
data: app.mealsRVdata
viewclass: 'RVLabel'
RecycleGridLayout:
cols: 1
size_hint: None, None
default_size: sp(200), sp(25)
height: self.minimum_height
width: self.minimum_width
BoxLayout:
size_hint: (1, 0.08)
Button:
text: "View shopping list"
on_release:
app.root.current = "viewlist"
root.manager.transition.direction = "right"
Button:
text: "Add recipes"
on_release:
app.root.current = "addrecipe"
<RVLabel#Label>:
text_size: self.size
I stumbled across the fix while browsing other questions - specify the RV more fully:
App.get_running_app().root.get_screen('viewlist').ids.shoplist.data = ....
I'm going to leave the question up, though, because now I want to know why this RV needs to be specified so completely, while the one in the AddRecipe screen doesn't.

Kivy. Strange view on android

On Android(only) device all widgets are situated on left-bottom corner and have small sizes.I tried use size_hint and pos_hint and FloatLayout, but there is no results.What's wrong?
This is .kv file
#:kivy 1.0.0
#:import win kivy.core.window
Widget:
Label:
id:TopLabel
text:'Eye verification app'
width: self.texture_size[0] + dp(40)
height: '48dp'
pos: 40,40
bold:True
color:1,0,0,1
Button:
id:registrateButton
on_release:
app.take_picture('registrate')
text: 'Registrate'
width: self.texture_size[0] + dp(40)
height: '48dp'
pos: 40,160
This is how your app looks like on my PC:
And this is how it looks on my Android device:
I'm guessing that's still too small for you, here's a way to make the buttons bigger:
from kivy.base import runTouchApp
from kivy.lang import Builder
runTouchApp(Builder.load_string('''
#:kivy 1.0.0
#:import win kivy.core.window
FloatLayout:
Label:
id:TopLabel
text:'Eye verification app'
size_hint: 0.8, 0.2 # to react to the screen's size
pos_hint: {"top": 0.4, "center_x": 0.5} # to place where we want it on the screen
bold:True
color:1,0,0,1
Button:
id:registrateButton
on_release:
app.take_picture('registrate')
size_hint: 0.8, 0.2 # the same as previous
pos_hint: {"top": 0.7, "center_x": 0.5} # top is a little lower
text: 'Registrate'
'''))
Here's how it looks now:
On Android:

Kivy app with .kv file doesn't display

I am trying to modify this example: https://github.com/inclement/kivycrashcourse/blob/master/video14-using_a_screenmanager/after.py to make it work with a .kv file. This is my myscreenmanager.py file:
from kivy.app import App
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
import time
import random
class FirstScreen(Screen):
pass
class SecondScreen(Screen):
pass
class ColourScreen(Screen):
colour = ListProperty([1., 0., 0., 1.])
class MyScreenManager(ScreenManager):
def new_colour_screen(self):
name = str(time.time())
s = ColourScreen(name=name,
colour=[random.random() for _ in range(3)] + [1])
self.add_widget(s)
self.current = name
class MyScreenManagerApp(App):
def build(self):
return MyScreenManager()
if __name__ == "__main__":
MyScreenManagerApp().run()
And this is my myscreenmanager.kv file:
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
MyScreenManager:
transition: FadeTransition()
FirstScreen:
SecondScreen:
<FirstScreen>:
name: 'first'
BoxLayout:
orientation: 'vertical'
Label:
text: 'first screen!'
font_size: 30
BoxLayout:
Button:
text: 'goto second screen'
font_size: 30
on_release: app.root.current = 'second'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
<SecondScreen>:
name: 'second'
BoxLayout:
orientation: 'vertical'
Label:
text: 'second screen!'
font_size: 30
BoxLayout:
Button:
text: 'goto first screen'
font_size: 30
on_release: app.root.current = 'first'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
<ColourScreen>:
BoxLayout:
orientation: 'vertical'
Label:
text: 'colour {:.2},{:.2},{:.2} screen'.format(*root.colour[:3])
font_size: 30
Widget:
canvas:
Color:
rgba: root.colour
Ellipse:
pos: self.pos
size: self.size
BoxLayout:
Button:
text: 'goto first screen'
font_size: 30
on_release: app.root.current = 'first'
Button:
text: 'get random colour screen'
font_size: 30
on_release: app.root.new_colour_screen()
After running the app nothing is displayed on the screen. No errors in console. Switching back to Builder.load_string displays the app as expected.
Found my mistake: when using a .kv file the root widget needs to be surrounded in <>, like this:
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<MyScreenManager>:
transition: FadeTransition()
FirstScreen:
SecondScreen:
Not sure why the discrepancy between load_string and .kv files, but it works now.

Resources