Skip to content

#1397: Fix fill + max #1569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

mtkennerly
Copy link
Contributor

This adds a pass so that, for example, when a container is both .width(Length::Fill) and .max_width(200), if it ends up being capped by its max, then its sibling containers can fill the unused space.

This fixes #1397.

Before

before.mp4

After

after.mp4

Comment on lines +153 to +200
for (i, child) in items.iter().enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();

if fill_factor != 0 {
let max_main = available * fill_factor as f32 / fill_sum as f32;
let min_main = if max_main.is_infinite() {
0.0
} else {
max_main
};

let (min_width, min_height) = if align_items == Alignment::Fill {
axis.pack(min_main, cross)
} else {
axis.pack(min_main, axis.cross(limits.min()))
};

let (max_width, max_height) = if align_items == Alignment::Fill {
axis.pack(max_main, cross)
} else {
axis.pack(max_main, max_cross)
};

let child_limits = Limits::new(
Size::new(min_width, min_height),
Size::new(max_width, max_height),
);

let layout = child.as_widget().layout(renderer, &child_limits);
let size = layout.size();

let unused = match axis {
Axis::Horizontal => max_width - size.width,
Axis::Vertical => max_height - size.height,
};
if unused > 0.0 {
capped[i] = true;
fill_sum -= fill_factor;
available -= axis.main(size);
nodes[i] = layout;
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really really woud like to avoid introducing additional cost to this function. It's a very critical path for our layout performance.

Instead of introducing additional logic, I'd like to explore other options first. Like maybe changing Widget::width and Widget::height to return further information, enhancing our Limits type, etc.

The most important shortcoming of the current layout logic here is that a Fill inside a Shrink will cause the Shrink to fill all the remaining space in the parent, independently of anything. A container with a Fill child should somehow be Fill as well.

I believe we should focus on rethinking our layout engine to deal with this basic shortcoming first, as doing so may shine a light on other inconsistencies (like this one).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of introducing additional logic, I'd like to explore other options first. Like maybe changing Widget::width and Widget::height to return further information, enhancing our Limits type, etc.

Please correct me if I'm mistaken, but wouldn't any Widget changes still be dependent on receiving the available space from resolve? Like in this specific example, the widget width/height either needs to act like Length::Units(max) (if available space > max) or Length::Fill (if available space <= max), and the outcome would affect the available space for any remaining Fill widgets. I'm not quite sure how that would be possible without changing this function (or some equivalent of it in an updated layout engine).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it would make sense to have Length variants like this? (just FillMax would solve #1397, but the others are for the sake of example)

pub enum Length {
    // Either a few very specific combinations:
    FillMin(u16),
    FillMax(u16),
    FillWithin {
        min: u16,
        max: u16,
    },

    // Or something more generic that may allow unwanted combinations:
    FillAll {
        portion: Option<u16>,
        min: Option<u16>,
        max: Option<u16>,
    }
}

That way, resolve would know if any of the widgets have a max set, and it could only trigger the extra code/cost if there are widgets that need it.

If it doesn't belong in Length directly, then maybe have Widget::width return a Width like this?

struct Width {
    length: Length,
    max: Option<u16>,
}

@hecrj hecrj added the bug Something isn't working label Dec 12, 2022
@hecrj hecrj added this to the 0.7.0 milestone Dec 12, 2022
@hecrj hecrj added the layout label Dec 12, 2022
@hecrj hecrj modified the milestones: 0.7.0, 0.8.0 Jan 14, 2023
@hecrj hecrj modified the milestones: 0.8.0, 0.9.0 Feb 18, 2023
@hecrj
Copy link
Member

hecrj commented Apr 11, 2023

As I mentioned in #1722, I think we should explore the limits of a single pass layout engine a bit more before we decide to introduce more complexity.

On a related note, I am working on fixing some layout inconsistencies in the fix/layout-inconsistencies branch. That will hopefully make reasoning about layout a bit easier.

Let's close this for now, and revisit it in the future!

@hecrj hecrj closed this Apr 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working layout
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Problems with Container when combining width(Length::Fill) and max_width
2 participants