community
community copied to clipboard
The `size_hint_min_x` parameter is placed in the parent `BoxLayout` widget of vertical orientation does not work
Software Versions
- Python: 3.10.10
- OS: Mac OS Somona 14.1
- Kivy: 2.2.1
Describe the bug
I noticed a strange behavior when using the size_hint_min_x
parameter: if the child widget with the size_hint_min_x
parameter is placed in the parent BoxLayout
widget of vertical orientation, then the size_hint_min_x
parameter does not work.
To Reproduce (normal behavior)
from kivy.app import App
from kivy.lang import Builder
KV = '''
Screen:
BoxLayout:
padding: 24
Button:
size_hint: 1, None
height: "40dp"
size_hint_min_x: "400dp"
'''
class Example(App):
def build(self):
return Builder.load_string(KV)
Example().run()
Preview (normal behavior)
https://github.com/kivy/kivy/assets/16930280/4d615726-7ef7-4609-8ff9-8b33a35ccd60
Now let's change the orientation of the parent BoxLayout
to vertical
:
from kivy.app import App
from kivy.lang import Builder
KV = '''
Screen:
BoxLayout:
orientation: "vertical"
padding: 24
Button:
size_hint: 1, None
height: "40dp"
size_hint_min_x: "400dp"
'''
class Example(App):
def build(self):
return Builder.load_string(KV)
Example().run()
Preview (strange behavior)
https://github.com/kivy/kivy/assets/16930280/865a7277-a308-4670-bc4b-c01987bb7a0e
Expected behavior
I say this is strange behavior because I, for example, want to place two buttons in a vertical BoxLayout
so that one of the buttons has the size_hint_min_x
parameter:
from kivy.app import App
from kivy.lang import Builder
KV = '''
Screen:
BoxLayout:
orientation: "vertical"
padding: 24
Button:
size_hint: 1, None
height: "40dp"
Button:
size_hint: 1, None
height: "40dp"
size_hint_min_x: "400dp"
'''
class Example(App):
def build(self):
return Builder.load_string(KV)
Example().run()
But as I said above with vertical orientation, the size_hint_min_x
parameter does not work properly.
https://github.com/kivy/kivy/assets/16930280/358b612f-4ddb-4bc1-9e49-7b74d41394f0
Are you allowed to set a size hint to a string like "400dp"
? It is a NumericProperty. Try 400
or dp(400)
.
@Julian-O No, in Kivy, the string "...dp"
is automatically converted to the number dp(400)
. This does not apply to the problem :)
number = NumericProperty("20dp")
This is valid code
This is not a bug. The hint provides a hint for the layout sizing. The vertical layout is not sizing the width.
To deliver the desired result you can nest the boxlayouts to control the vertical and horizontal direction.
from kivy.app import App
from kivy.lang import Builder
KV = '''
Screen:
BoxLayout:
orientation: "vertical"
padding: 24
Button:
size_hint: 1, None
height: "40dp"
BoxLayout:
size_hint: 1, None
height: "40dp"
Button:
size_hint_min_x: "400dp"
'''
class Example(App):
def build(self):
return Builder.load_string(KV)
Example().run()
@ElliotGarbus Most likely, this is a bug. Or this behavior is very not obvious. For example:
from kivy.app import App
from kivy.lang import Builder
KV = '''
Screen:
BoxLayout:
orientation: "vertical"
padding: 24
Button:
size_hint: 1, None
height: "40dp"
BoxLayout:
Button:
size_hint: 1, None
height: "40dp"
size_hint_min_x: dp(400)
'''
class Example(App):
def build(self):
return Builder.load_string(KV)
Example().run()
And it works:
https://github.com/kivy/kivy/assets/16930280/3aaa8877-e6de-403b-b599-73c21a897e5a
But as you can see, I have to use one extra container to place the button. It's just, again, if this is not a bug, then this behavior is not entirely obvious.
I'd agree the behavior is not obvious - but as you use the framework you learn how the hints work. The behavior of pos_hints is perhaps even more non-obvious. It is important to think about how the Layout is sizing and positioning the widget, and how the layout will respond to the hints.
@Julian-O This is not a bug.
@ElliotGarbus: You understand this better than me, and if you say it isn't a bug, I believe you.
But it is confusing.
The vertical layout is not sizing the width.
I don't get it. Can we point to some documentation and say "It is a simple consequence of that"? If not, then we should add some.
@Julian-O Adding some documentation is a good idea. I'll give it some thought on the best way to represent this across the layouts.
@HeaTTheatR Here is a way to think about it. The layouts are tools that position and size widgets. The hints are directions from the child widget to the parent Layout. Depending on the Layout not all of the hints are honored. It is natural to nest layouts to get the desired end result.
In a vertical BoxLayout the layout is controlling the vertical position and size of the widget. It is not controlling the horizontal size. A horizontal BoxLayout controls the horizontal position and size of the widget. If you want to control both, as in the use case you shared, you nest the Layouts. In a vertical BoxLayout, because the Layout is not controlling the width, a hint about the width is not honored. There are also similar limitations on position hints.
Here is an example using BoxLayouts:
from kivy.app import App
from kivy.lang import Builder
kv = """
BoxLayout:
orientation: 'vertical'
Button:
text: 'size_hint_max_x is ignored'
size_hint_max_x: dp(100)
Button:
text: 'size_hint_max_y is honored'
size_hint_max_y: dp(100)
BoxLayout:
size_hint_y: None
height: dp(48)
Button:
text: 'size_hint_max_x is honored'
size_hint_max_x: dp(300)
Button:
text: 'size_hint_max_y is ignored'
size_hint_max_y: dp(10)
Button:
text: 'fixed size in vertical layout, x pos hint honored'
size_hint: None, None
size: dp(400), dp(48)
pos_hint: {'center_x': 0.5}
BoxLayout
size_hint_y: None
height: dp(48 * 2)
Button:
text: 'fixed size in horizontal boxlayout, x pos_hint ignored, y pos_hint honored'
size_hint: None, None
padding: dp(10)
size: self.texture_size[0], dp(48)
pos_hint: {'center_x': 0.5, 'top': 1}
"""
class ButtonBoxLayoutExamples(App):
def build(self):
return Builder.load_string(kv)
ButtonBoxLayoutExamples().run()
I know I am taking us backwards here, but I want to revisit the design decision for:
The vertical layout is not sizing the width.
I answered a support chat the other day, where someone was confused that a horizontal layout didn't permit them to use hints to set the x position of a widget. I understood exactly why that wouldn't work. A horizontal layout's job is to set the x position and width of a widget. The documentation explain how only some pos hints are used:
If the orientation is horizontal: y, top and center_y will be used
But why shouldn't a horizontal box layout respect the height hints (and in this case, a vertical box layout respect width hints).
I get that I am being told "they don't; it is confusing; we'll document it" but I want to push back and ask "Why not change it so they do?" I am keeping an open mind in case there is a good reason.
TLDR: The element that is missing in the docs is not what hints are ignored in a given layout, but how to use multiple layouts together to create a desired result.
Lets consider what a vertical Boxlayout does. It controls the height and position of each of the widgets in the layout. By definition it does not control the width. This provides a tool that works in vertical direction that can be composed with other Layouts.
The BoxLayout documentation contains some documentation about the limitation: https://kivy.org/doc/stable/api-kivy.uix.boxlayout.html#module-kivy.uix.boxlayout:~:text=Position%20hints%20are%20partially,center_y%20will%20be%20used.
When I was first learning Kivy I read, "Creating Apps in Kivy", by Dusty Phillips. I found it's discussion of Layouts and of KV very helpful. It is now out of print but you can find pdfs on line. In was written in 2014, so there are elements that have been removed from kivy. (Notable is the Listview that was replaced with RecycleView). A small excerpt:
Consider this simple example:
from kivy.app import App
from kivy.lang import Builder
kv = """
BoxLayout:
orientation: 'vertical'
Button:
size_hint: .5, .5
"""
class PosBoxApp(App):
def build(self):
return Builder.load_string(kv)
PosBoxApp().run()
The result:
The size_hint_y has been ignored. Consider it is the role of this layout to position and size in the vertical direction, so it ignores the hints in that direction. If you had multiple widgets in the layout the hints would be used to set their height relative to each other.
The reason they are called hints is that they can be ignored. The element that is missing in the docs is not what hints are ignored, but how to use multiple layouts together to create a desired result. When you learn where to use a BoxLayout the design becomes intuitive.
Now consider I have a row of buttons and I want them all the same height. I would create a horizontal boxlayout, set the height of the layout, the layout would control the height of the buttons, and position them as desired.
If you want all the hints honored use a FloatLayout.
When learning layouts it is useful to use Buttons, the Button shows you the size of the widget. Avoid using the Labels there is not connection between text size and the label size, this can cause significant confusion. There are also a few useful tools.
- In the kivy-examples directory the Kivy Catalog see: venv/share/kivy-examples/demo/kivycatalog provides an interactive environment for exploring layouts and widgets in kv.
- The Inspector Module provides an interactive tool for inspecting information about widgets in a running app. See: https://kivy.org/doc/stable/api-kivy.modules.inspector.html#module-kivy.modules.inspector
In real programs, layouts almost never exist standalone - they are always nested to deliver a specific outcome. If you consider what a layout does, calculating the size and pos of widgets, nesting layouts is reusing the code to make those calculations.
Another Example:
from kivy.app import App
from kivy.lang import Builder
kv = """
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: dp(48)
Button:
text: 'horizontal'
Button:
text: 'in'
Button:
text: 'a vertical'
Button:
text: 'layout'
AnchorLayout:
Button:
size_hint: None, None
size: dp(200), dp(48)
text: 'centered with an anchor'
Button:
size_hint_y: None
height: dp(40)
text: 'Mixing fixed sizes and hinted sizes - The hinted layout (Anchor) sizes with the window'
"""
class PosBoxApp(App):
def build(self):
return Builder.load_string(kv)
PosBoxApp().run()
A bit rambling. I hope that was helpful.
I definitely want this documented.
I definitely needed a document when I started, and when I came back a year later, and when I came back a year later again, which told me how sizes and hints flow through the layouts. I think I was always wondering whether it was top-down (layouts knew their size, and split their space amongst their children, and told them their size) or bottom-up (widgets knew their size, and told the layouts to grow to fit). I knew it was more complicated than that with hints and sizes, but not even being able to figure out what would happen if there were no hints was confusing.
I understand that Layouts can choose to ignore hints where they are impossible to implement, or don't make sense... ... but why would they, if they could comply with them, as in the BoxLayout? Why force a nested BoxLayout just to deal with an issue that the top level BoxLayout could simply support with no skin off its nose.
"Why" How much complexity to you want to add, how many new corner cases do you want to create? What is the problem you are trying to solve? I have not found a real use case where the way BoxLayout works created a constraint. In fact the nesting of Layouts becomes intuitive with use. I don't believe adding more features will shorten the learning curve.
I do agree that a more comprehensive document on layouts and adding more details per layout on hints honored would be useful.
I'm the one quoted here: I responded to a support chat the other day
and after seeing this discussion (I look at the entire repository because I think this framework has a lot of potential), I think a misunderstanding has been created.
I have fallen too into the idea of thinking that BoxLayout should allow its children to choose its size and position, as @Julian-O says. But as @ElliotGarbus rightly said, there is already a widget that CAN do that (FloatLayout). Perhaps the problem that many newbies to Kivy (like me) have is using BoxLayout instead of FlexLayout. But why does it happen?
In my experience with other frameworks, "floating" layouts are avoided because they place elements just as you place your code, preventing you from controlling the position of each control.
Furthermore, I think that the BoxLayout is used because the most common thing is to start Kivy with examples, and the BoxLayout I would say (maybe I'm wrong) is the most used in examples because it is handled easily and quickly, therefore, it is understood." " quickly.
Should the layout behavior be changed? I don't think so. Although this creates a dilemma for me when I compare it with Xamarin's StackLayout.
But perhaps it would be a good idea to specify in the BoxLayout suggestions section that if you want full control of the layout's children, take a look at the FloatLayout or the RelativeLayout.