slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
16.93k stars 568 forks source link

Document, how final element width, height and position are calculated #143

Closed matklad closed 8 months ago

matklad commented 3 years ago

Hi! This is going to be a bit of a whiney issue :)

I don't go gui on a daily basis, but I wrote a fair share of swing and html. The common problem with gui toolkits for me is that I don't understand how the final layout is computed from the properties I give. Specifically, I usually can specify sizes of everything, but sometimes they don't add up. In this case, the toolkit applies some algorithm to infer a consistent set of positions from my messy input. The user-visible behavior is that some user-specified constraints are ignored. As an end user, this is frustrating, because there's no mental model I can use to predict this things.

Today's case was like this: I had an 800x400 window, with 800x400 vertical layout in .60. The rendered window was tiny however. The reason for that was that my vertical layout contained a single element with explicitly specified dimensions. So size of a single child overrode the size of the parent.

This is a reasonable behavior if you know it, but i didn't :[

I wish 60fps included "layout algorithm" section in the docs, which specified such things.

ogoffart commented 3 years ago

Thanks for the report. We have some basic documentation for the layouts there, in case you missed it. https://sixtyfps.io/docs/rust/sixtyfps/docs/builtin_elements/index.html#verticallayout--horizontallayout But we clearly need to improve our documentation. It is true a specific layout.md documentation is probably in order to document all the special case.

matklad commented 3 years ago

Hey, I've just hit an interesting case of fighting with layout. I wanted to draw a main content in the center of the screen, and some auxilary content in center of the area above the main content.

I've started with this:

VerticalLayout {
    Rectangle {
        color: steelblue;
    }
    Rectangle {
        height: ...;
        width: ...;
        ...
    }
    Rectangle {
        color: steelblue;
    }
}

image

So far so good -- the explicitly sized content in the center, two identically sized rectangles fill the space.

Let's add some content:

VerticalLayout {
    Rectangle {
        color: steelblue;
        Text { text: "120 wpm, no errors"; font_size: 22px; }
    }
    Rectangle {
        height: ...;
        width: ...;
        ...
    }
    Rectangle {
        color: steelblue;
    }
}

image

Still good -- as expected, the layout is the same, we only see new text. Now, let's try to center the text in the top rectangle...

VerticalLayout {
    Rectangle {
        color: steelblue;
        VerticalLayout {
            alignment: center;
            Text { text: "120 wpm, no errors"; font_size: 22px; }
        }
    }
    Rectangle {
        height: ...;
        width: ...;
        ...
    }
    Rectangle {
        color: steelblue;
    }
}

...

...

image

What is especially annoying here is action at the distance -- I had a perfect layout and a seemingly unrelated change in one of the components changed the whole thing.

I think the underlying issue here is that some components has a fixed size -- either an explicit one, or computer from the children. And other components have the size determined by the parent (like the window who's size is determined by the user dragging the corners), no matter what you do inside the component, it won't affect layout outside. And I think what happens here is that the rectangle silently flips from the second mode (asking the parent about the size) into the first mode (telling the parent it's size). I wonder if there can be some way to surface this in the .60 language, a-la width: set-by-parent.

ogoffart commented 3 years ago

The Text { } try to limit itself to the size of its content. When in a layout, this will try to adjust the whole UI to fit this contraint which can easily be done because the down rectangle can grow.

The solution could be to

I see how this can be confusing, but i believe this is the right behavior. I guess I should improve the doc with more example to make this easier to understand.

matklad commented 3 years ago

I ended up doing a much simpler thing:

            Rectangle {
                Text {
                    height: 100%;
                    width: 100%;
                    vertical_alignment: align_center;
                    horizontal_alignment: align_center;

                    text: stats;
                    font_size: 30px;
                    color: #7F9F7F;
                }
            }

Still, I must admit I currently lack a mental model to explain the nested layouts behavior.

If just Rectangle { Text {} } shrunk to fit the text, that'd make sense to me. If neither Rectangle { Text { }} nor Rectangle { Layout { Text { } } } shrunk that'd also make sense. But why Text doesn't affect the behavior of outer rectangle, while Layout { Text } does is unclear. Additional docs would be appreciated!

matklad commented 3 years ago

Aha! I've found an example which completely shatters my wrong mental model to pieces:

        VerticalLayout {
            Rectangle { color:black; }
            VerticalLayout {
                Rectangle { color:red; }
                Rectangle { color:yellow; }
            }
        }

Here, all three rectangles will have the same height (while I wrongly assumed that the black one will be twice as large).

boogerlad commented 3 years ago

Why are the three rectangles the same height? I also thought the black one will be twice as tall compared to the red and yellow.

ogoffart commented 3 years ago

Why are the three rectangles the same height? I also thought the black one will be twice as tall compared to the red and yellow.

Because each rectangle has a default vertical-stretch of 1.0 And the layout sums the stretch of its children in the directon of the layout. so the inner VerticalLayout has a stretch of 2.0.

It is possible to override that:

       VerticalLayout {
            Rectangle { color:black; }
            VerticalLayout {
                vertical-stretch: 1;
                Rectangle { color:red; }
                Rectangle { color:yellow; }
            }
        }

Or like so:

       VerticalLayout {
            Rectangle { color:black;   vertical-stretch: 2; }
            VerticalLayout {
                Rectangle { color:red; }
                Rectangle { color:yellow; }
            }
        }
boogerlad commented 3 years ago

Just curious: What's the behavior with QML?

hunger commented 8 months ago

We have this documented now: https://slint.dev/releases/1.3.2/docs/slint/src/language/concepts/layouting#

hunger commented 8 months ago

We need to have more specialized issues for anything still missing.