community icon indicating copy to clipboard operation
community copied to clipboard

The `size_hint_min_x` parameter is placed in the parent `BoxLayout` widget of vertical orientation does not work

Open HeaTTheatR opened this issue 1 year ago • 12 comments

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

HeaTTheatR avatar Nov 18 '23 11:11 HeaTTheatR

Are you allowed to set a size hint to a string like "400dp"? It is a NumericProperty. Try 400 or dp(400).

Julian-O avatar Nov 18 '23 11:11 Julian-O

@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

HeaTTheatR avatar Nov 18 '23 12:11 HeaTTheatR

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 avatar Nov 18 '23 14:11 ElliotGarbus

@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.

HeaTTheatR avatar Nov 18 '23 16:11 HeaTTheatR

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 avatar Nov 18 '23 22:11 ElliotGarbus

@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 avatar Nov 19 '23 01:11 Julian-O

@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()

ElliotGarbus avatar Nov 19 '23 05:11 ElliotGarbus

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.

Julian-O avatar Nov 19 '23 05:11 Julian-O

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:

image

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: image 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()

image

A bit rambling. I hope that was helpful.

ElliotGarbus avatar Nov 19 '23 13:11 ElliotGarbus

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.

Julian-O avatar Nov 19 '23 14:11 Julian-O

"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.

ElliotGarbus avatar Nov 19 '23 14:11 ElliotGarbus

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.

RafaRioFal4699 avatar Nov 20 '23 08:11 RafaRioFal4699