I'm struggling to understand layout positioning in Kivy.
My aim is to create a simple form containing 3 rows, each one consisting of a label and a text input.
For example, like on this wireframe drawn with PlantUML, Salt (source):
What I tried
I've tried combining different layouts but cannot achieve the right alignment and I also don't understand what's going wrong.
First attempt:
KV file:
<CustomLabel#Label>:
size_hint: .2, .1
text_size: self.size
<CustomTextField#TextInput>:
size_hint: .2, .1
<CustomFloatLayout#FloatLayout>:
<TestLayout>:
orientation: 'horizontal'
CustomFloatLayout:
CustomLabel:
text: 'Field 1:'
pos_hint: {'x': .1, 'center_y': .95}
CustomTextField:
hint_text: 'hint 1'
pos_hint: {'x': .1, 'center_y': .92}
CustomFloatLayout:
CustomLabel:
text: 'Field 2:'
pos_hint: {'x': .1, 'center_y': .75}
CustomTextField:
hint_text: 'hint 2'
pos_hint: {'x': .1, 'center_y': .72}
CustomFloatLayout:
CustomLabel:
text: 'Field 3:'
pos_hint: {'x': .1, 'center_y': .55}
CustomTextField:
hint_text: 'hint 3'
pos_hint: {'x': .1, 'center_y': .52}
Python code:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class TestLayout(BoxLayout):
pass
class TestApp(App):
def build(self):
return TestLayout()
if __name__ == '__main__':
TestApp().run()
Issue
The y-center of the labels is almost at the bottom of the y-center of the text input fields instead of being aligned at the same y-value. And what is even more puzzling: Each row has another offset from the left border of the window.
Second attempt:
KV file:
<CustomLabel#Label>:
size_hint: .2, .1
text_size: self.size
<CustomTextField#TextInput>:
size_hint: .2, .1
<CustomBoxLayout#BoxLayout>:
orientation: 'horizontal'
pos_hint: {'x': .1}
<TestLayout>:
CustomBoxLayout:
CustomLabel:
text: 'Field 1:'
pos_hint: {'x': .1, 'center_y': .95}
CustomTextField:
hint_text: 'hint 1'
pos_hint: {'x': .5, 'center_y': .95}
CustomBoxLayout:
CustomLabel:
text: 'Field 2:'
pos_hint: {'x': .1, 'center_y': .75}
CustomTextField:
hint_text: 'hint 2'
pos_hint: {'x': .5, 'center_y': .75}
CustomBoxLayout:
CustomLabel:
text: 'Field 3:'
pos_hint: {'x': .1, 'center_y': .55}
CustomTextField:
hint_text: 'hint 3'
pos_hint: {'x': .5, 'center_y': .55}
Python code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
class TestLayout(FloatLayout):
pass
class TestApp(App):
def build(self):
return TestLayout()
if __name__ == '__main__':
TestApp().run()
Issue
Looks better, but label and text input are still not aligned at the same center y value. In addition, if I change the pos_hint of e.g. the second CustomLabel to pos_hint: {'x': .1, 'bottom': .75}, then this label appears at the very bottom of the entire window. I would have expected it to be at the bottom of the current BoxLayout row.
Question
How can I achieve a proper aligning of the labels and the text input fields?
Why are the labels/input fields of the first attempt located on the diagonal of the window?
Why do the labels appear at the bottom of the entire window when
changing their bottom value within the second attempt code?
For your desired layout as Grid (arranging 3 rows with 2 columns) use GridLayout:
GridLayout:
Widgets are arranged in a grid defined by the rows and cols properties.
The order in automatic arrangement of widgets (using add_widget) is controlled by property orientation:
orientation is an OptionProperty and defaults to ‘lr-tb’.
Python code (including widget declaration)
Adjusted from the examples in documentation of GridLayout:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
class TestApp(App):
def build(self):
return layouted_widgets() # replace this by `pass` for trying with KV
def layouted_widgets():
layout = GridLayout(rows=3, cols=2, row_force_default=True, row_default_height=40)
# 1st row
layout.add_widget(Label(text='Label 1'))
layout.add_widget(TextInput(text='Value 1', multiline=False, width=100))
# 2nd row
layout.add_widget(Label(text='Label 2'))
layout.add_widget(TextInput(text='Value 2', multiline=False, width=100))
# 3rd row
layout.add_widget(Label(text='Label 3'))
layout.add_widget(TextInput(text='Value 3', size_hint_x=None, width=100))
return layout
if __name__ == '__main__':
TestApp().run()
See how the 3rd text-input is reduced in size to fit the text-value. This is because of the property size_hint_x=None which overrides the given width=100.
For specific positions inside each cell:
you can not use property pos_hint
for Label which is inherited from Text, same as Button: use valign, halign together with text_size. See the Text-based components' alignment explained in:
Kivy button text alignment issue
for TextInput use padding as explained in Kivy TextInput horizontal and vertical align (centering text)
The original Kivy Guide: Widgets, Organize with Layouts is a bit vague on that.
KV file
Using both, your Custom.. inherited widgets (1st and 2nd row) and the basic widgets (in 3rd row only):
<CustomLabel#Label>:
# to add space from left and top-bottom, effects position x,y
text_size: self.width - dp(20), self.height - dp(10)
halign: 'left'
valign: 'center'
<CustomTextField#TextInput>:
size_hint: self.width - dp(20), self.height - dp(10)
# pos_hint: not supported as expected, thus use padding
# left, right
padding_x: dp(20)
# top, bottom
padding_y: dp(5)
GridLayout:
rows: 3
cols: 2
row_force_default: True
row_default_height:40
CustomLabel:
text: 'Field 1:'
CustomTextField:
hint_text: 'hint 1'
CustomLabel:
text: 'Field 2:'
CustomTextField:
hint_text: 'hint 2'
Label:
text: 'Field 3:'
TextInput:
hint_text: 'hint 3'
Renders the UI in 3 rows and 2 cols - almost all labels and text-inputs left-aligned. Only the 3rd label looks different to demonstrate the alignment-effect on others.
Related
I'm creating a kivy app right now. I'm quite new to Kivy and kv lang and there doesn't seam to be much rumour about it although i find it great to seperate code logic and layout, especially after some pygame development.
So to my actuall problem: I have a wiki-style screen for screenmanager, consisting out of a BoxLayout:
Title as Label
Scrollable Label for the Text (later , there shall be displayed a nested kv file)
Buttons for Navigation (scroll up and go back to main screen)
Now I'm recreating the Navigation buttons to be floating type as known from many webpages and apps. Problem is, I suddendly cant reference my ScrollView anymore. Any help or suggestions?
<Wiki>:
name: "wiki"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "picture.jpg"
BoxLayout:
id: box
orientation: "vertical"
padding: 10
spacing: 10
Label:
font_size: 20
size_hint_y: .1
text: root.country_text
ScrollView:
id: scrlvw
BackgroundLabel:
background_color: 220,220,220, 0.5
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
halign: "left"
valign: "top"
text: root.wiki_text
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
FloatButton:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
widget = BoxLayout()
widget.ids.scrlvw.scroll_y = 0
Before, when it worked, it was:
BoxLayout:
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
Button:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
scrlvw.scroll_y = 0
Well as its just a design question, I guess i temporary have to dismiss the floating design. But I would be so pleased if you could help me understand this language better.
As long as the 'kv' code described as "when it worked" is still in the same <Wiki> rule, it should still work. The newer kv code will never work as you are trying to create a new BoxLayout and reference an id in it. That has two problems:
The newly created BoxLayout is not the BoxLayout instance that appears in your GUI, so any changes to it will have no effect on what appears in the display.
Unless you have a <BoxLayout> rule in your kv, the newly created BoxLayout will not have a scrlvw id.
The ids defined within a kv rule are available for use only within that rule. See the documntation.
I'm currently working on building a digital planner app, where I want to include some statistics-related features. Obviously, matplotlib is an optimal means of doing that, but I have two problems with it when I try to add more than one plot in a a Kivy ScrollView:
Each plot decreases in size so much that you cannot see what's
actually being displayed;
Kivy ScrollView is not scrolling - unfortunately, very common.
I've tried setting ScrollView's height equal to ScrollView.minimum_height and yet I get no result.
Here's a bit of my Python code:
class StatsWindow(FloatLayout, MDTabsBase):
dates_from, dates_to, plot_scroll = [str(datetime.today()).split()[0]], [str(datetime.today()).split()[0]], ObjectProperty(None)
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
Here's a bit of my Kivy code:
<StatsWindow>:
name: "stats"
text: "STATS"
icon: "thumbs-up-down"
plot_scroll: plot_scroll
choose_date_to: choose_date_to
choose_date_from: choose_date_from
FloatLayout:
MDLabel:
halign: "center"
size_hint: 1, .1
text: "Choose the dates to view your stats"
font_size: self.width / 17
color: app.theme_cls.primary_color
pos_hint: {"top": .98, "center_x": .5}
BoxLayout:
MDFlatButton:
id: choose_date_from
pos_hint: {"center_x": .25, "top": .88}
text: "from"
size_hint: .4, .1
font_size: self.width / 11
on_release: root.open_date_picker(root.dates_from, self)
MDFlatButton:
text: "|"
size_hint: .1, .1
pos_hint: {"center_x": .5, "top": .88}
MDFlatButton:
id: choose_date_to
font_size: self.width / 11
pos_hint: {"center_x": .75, "top": .88}
text: "to"
size_hint: .4, .1
font_size: self.width / 11
on_release:
root.open_date_picker(root.dates_from, self)
BoxLayout:
MDFlatButton:
size_hint: 1, .1
text: "Load the statistics"
pos_hint: {"center_x": .5, "top": .78}
on_release: root.add_sp_plot(choose_date_from.text, choose_date_to.text)
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
size: root.width, root.height
GridLayout:
id: plot_layout
cols: 1
height: self.minimum_height
Here's the picture of what I get when I run the program:
The ScrollView will only scroll if its child (the GridLayout) is larger than the ScrollView. Also, the line:
height: self.minimum_height
will have no effect unless you add the line:
size_hint_y: None
You can increase the size of the plots by specifying a row_default_height for the GridLayout and eliminating the size_hint_y=None for the FigureCanvasKivyAgg. So, I would suggest specifying your ScrollView as:
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
GridLayout:
id: plot_layout
cols: 1
size_hint_y: None
row_default_height: 500 # any default row height that you desire
height: self.minimum_height
Then, add the plots using:
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
Removing the size_hint_y=None leaves the default value of size_hint_y as 1, so that the plot will take up all the space that the GridLayout allocates to it (the row_default_height).
I am currently working on an app for students, which describes various practical experiments.
The app consists of several AccordionItems.
The problem arises with one of them, where I want to embed an image, which keeps its aspect ratio but adapts to be as big as possible. To explain parts of the image, I would like to put semi-transparent buttons on interesting devices/objects that provide information in text form at on_release.
By stretching the app window in x or y direction and the fact that stretching the image is not allowed, there are areas above and below or to the right and left of the window that do not belong to the actual image.
How can scale and position the buttons depending on the current size of the image?
I tried to work with RelativeLayout, but the buttons seem to be oriented to the whole window, which I can't understand.
I also tried to work with ids, but I don't have the understanding to use it effectively.
Here some current code:
class LaserApp(App):
pass
if __name__ == '__main__':
#Config.set('graphics', 'fullscreen', 'auto')
Config.set('graphics', 'fullscreen', '0')
Config.set('graphics', 'window_state', 'windowed')
Config.write()
LaserApp().run()
#:import ScrollEffect kivy.effects.scroll.ScrollEffect
#:import Button kivy.uix.button.Button
Accordion:
AccordionItem:
title: 'titel1'
collapse: False
AccordionItem:
title: 'titel2'
AccordionItem:
title: 'relevant content'
RelativeLayout:
canvas:
Image
size_hint: 1, 1
pos: self.pos
size: self.texture_size
source: 'background.png'
canvas.after:
RelativeLayout:
Button: #Button i want to align and resize depending on Image: / 'background.png'
AccordionItem:
title: 'titel4'
Any comments and help are very welcome.
Many Thanks in advance
P.S.: Please excuse the bad description.
For your problem it sounds like you want something like this?:
_____________
| | | |
_____________
| | | |
_____________
| | | |
_____________
You have to create multiple buttons with:
RelativeLayout:
size_hint: 1,1
pos: self.pos
Button:
background_color: 0,0,0,.25 #rgba only, this makes the button semi-transparent
pos_hint: {'x': .66, 'y': 0}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': .33, 'y': 0}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': 0, 'y': 0}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': .66, 'y': .33}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': .33, 'y': .33}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': 0, 'y': .33}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': .66, 'y': .66}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': .33, 'y': .66}
size_hint: .33, .33
on_release: print/function
Button:
background_color: 0,0,0,.25
pos_hint: {'x': 0, 'y': .66}
size_hint: .33, .33
on_release: print/function
As long as you have a Layout attached to your image with size_hint: 1,1 and pos: self.pos this will bind the Layout to the same size and position of your image widget. The buttons in the example code should populate tiles that are 1/9 the size of the Layout as 9 are set in increments of .33 if you want more, just add more with the correct size_hint and pos_hint values. If RelativeLayout is an issue try using FloatLayout or BoxLayout, FloatLayout is similar to putting a blank canvas up and you can fill this widget anyway you see fit and BoxLayout functions by filling itself with children vertically or horizontally depending on how you set the orientation:. If this didnt fully answer your question or you need more help please let me know.
I have built a user interface with Kivy that appears correctly when the program starts up, but if I plug in a new monitor (or drag to an extended monitor), everything disappears under the window, but I can see part of my work when I maximize the window. How do I position my program to the start of the window?
I have tried to match the main layout top value to the window top value every second, and I have tried to reset the position of the main layout every second.
My kivy file
#:import inch kivy.metrics.inch
<Questionaire>:
#column_gl: ColumnGL
outer_column_gl: OuterColumnGL
cols: 2
GridLayout:
id: OuterColumnGL
cols: 1
size_hint_x: None
width: inch(2)
GridLayout:
id: ColumnGL
cols: 2
size_hint_y: .9
Button:
size_hint_x: None
width: inch(.1)
Button:
text: 'stuff'
Button:
size_hint_y: .1
text: 'more stuff'
My attempt to fix it
def fix_screen(*args):
self.y = 0
Clock.schedule_interval(fix_screen, .5)
This is what comes up when I first open the program.
This is what comes up as soon as I drag my window to an extended monitor (Everything appears to have disappeared).
Here is my maximized window on the extended monitor (The y position is wrong).
Using size_hint_x or size_hint_x allows you to set the size of your widgets relative to their parent widgets. Using ''pos_hint: {'x': val, 'y': val}'' will set the position of the widget to their parent widgets. Since size_hint, and pos_hint both are relative to their parent widgets, the values for the x and y are in a range from 0-1.
I would recommend utilizing size_hint_x: 0-1 rather than width for your buttons so they will automatically adjust to a changing resolution. I will come up with a sample to help you out.
Sizing and position are best done in the kv language whether you use builder or a .kv file. The added class and function are not needed to have reactive widgets. Let me know if you need more clarification or have any other questions about kivy.
Again I would recommend this following your widgets if you would like them to autoscale:
size_hint_x: 0.00-1.00
size_hint_y: 0.00-1.00
pos_hint: {'x': 0.00-1.00, 'y': 0.00-1.00}
An example that should show you how it works, although I doubt you need all the extra imports as this was to show consistency when changing resolutions:
from kivy.app import App
from kivy.lang import Builder
from kivy.config import Config
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
Config.set('input', 'mouse', 'mouse,multitouch_on_demand') #Prevent red dots from populating the screen with right clicks
Config.set('graphics','resizable',0) #Self-explanatory, prevents you from resizing the window
Config.write() #Writes the Config settings for the application
Builder.load_string("""
<One>:
GridLayout:
cols: 2
size_hint_x: .5
pos_hint: {'x': 0, 'y': 0}
GridLayout:
id: OuterColumnGL
cols: 1
size_hint_x: .2
GridLayout:
id: ColumnGL
cols: 2
size_hint_y: .9
Button:
size_hint_x: .3
Button:
text: 'stuff'
Button:
size_hint_y: .1
text: 'more stuff'
Button:
text: 'Change Res'
size_hint_x:.2
size_hint_y: .2
pos_hint: {'x': .75, 'y': .05}
on_release: root.enlarge()
<Two>:
GridLayout:
cols: 2
size_hint_x: .5
pos_hint: {'x': 0, 'y': 0}
GridLayout:
id: OuterColumnGL
cols: 1
size_hint_x: .2
GridLayout:
id: ColumnGL
cols: 2
size_hint_y: .9
Button:
size_hint_x: .3
Button:
text: 'stuff'
Button:
size_hint_y: .1
text: 'more stuff'
Button:
text: 'Change Res'
size_hint_x:.2
size_hint_y: .2
pos_hint: {'x': .75, 'y': .05}
on_release: root.shrink()
""")
class One(Screen):
def enlarge(self):
Window.size = (500, 600)
sm.current = 'two'
class Two(Screen):
def shrink(self):
Window.size = (300, 400)
sm.current = 'one'
sm = ScreenManager()
sm.add_widget(One(name='one'))
sm.add_widget(Two(name='two'))
class SampleApp(App):
def build(self):
Window.size = (300, 400)
Window.left = 750
Window.top = 40
return sm
if __name__ == '__main__':
SampleApp().run()
This is my test.kv file:
BoxLayout:
BoxLayout:
orientation: 'vertical'
size_hint: None, None
height: '160sp'
width: '380sp'
pos_hint: {'center_x': .5, 'center_y': .5}
BoxLayout:
Label:
text: 'UserName'
TextInput:
id: user_name
text: ''
BoxLayout:
Label:
text: 'Password'
TextInput:
id: password
password: True
text: ''
BoxLayout:
Label:
text: 'Domain'
TextInput:
id: domain
text: 'howefarmingco.local'
Button:
text: 'Login'
size_hints: None, 1
width: .6
pos_hint: {'center_x': .5}
on_press: app.do_login()
The idea is to have the login fields appear centred both vertically and horizontally. The vertical works as I expect it to, but the fields and button all display on the left edge of the window instead of the middle.
Am I missing something very basic here or just going about it in entirely the wrong way?
pos_hint is not used by all layouts!
From FloatLayout docs: FloatLayout honors the pos_hint and the size_hint properties of its children.
Changed my test.kv file so outer BoxLayout is now a FloatLayout and everything works as expected.