Skip to content
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

Layout consistency #2192

Merged
merged 26 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0655a20
Make `Shrink` have priority over `Fill` in layout
hecrj Mar 16, 2023
ed3b393
Fix needless borrow in `row::layout`
hecrj Mar 16, 2023
89418c1
Determine cross-axis max length based on contents if `Shrink`
hecrj Mar 23, 2023
aa3c956
Fix available space provided to children with non-fill main axis but …
hecrj Mar 24, 2023
fd8f980
Use `max_cross` if all elements are fluid in `layout::flex`
hecrj Mar 27, 2023
0322e82
Create `layout` example
hecrj Mar 27, 2023
2222639
Introduce `Widget::size_hint` and fix further layout inconsistencies
hecrj Jan 5, 2024
d278bfd
Replace `width` and `height` with `Widget::size`
hecrj Jan 5, 2024
4bdd8a6
Fix `cross` axis calculation in `flex` layout
hecrj Jan 5, 2024
d24e50c
Reduce `padding` of `scrollable` example
hecrj Jan 9, 2024
d62bb81
Introduce useful helpers in `layout` module
hecrj Jan 9, 2024
e710e76
Fix `size_hint` for `keyed_column`
hecrj Jan 9, 2024
67277fb
Make `column` and `row` take an `IntoIterator`
hecrj Jan 9, 2024
ecf571d
Fix unnecessary `into` call in `Container::new`
hecrj Jan 9, 2024
025064c
Fix broken doc links in `layout::Node` API
hecrj Jan 9, 2024
88f8c34
Fix `cross` calculation in `layout::flex`
hecrj Jan 9, 2024
a79b2ad
Use first-class functions in `layout` example
hecrj Jan 10, 2024
81ecc4a
Add basic controls to `layout` example
hecrj Jan 10, 2024
5dbded6
Use `flatten` instead of `filter_map` in `layout` example
hecrj Jan 10, 2024
d76705d
Add `explain` toggle to `layout` example
hecrj Jan 10, 2024
3850a46
Add `Theme` selector to `layout` example
hecrj Jan 10, 2024
a6cbc36
Showcase more layouts in `layout` example
hecrj Jan 10, 2024
2262711
Use multiple squares instead of `vertical_space` in `layout` example
hecrj Jan 10, 2024
fa53d9a
Loosen cross axis constraint for main axis fills in `flex` layout
hecrj Jan 11, 2024
03c901d
Make `Button` sizing strategy adaptive
hecrj Jan 11, 2024
11474bd
Fix `websocket` example
hecrj Jan 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions core/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::renderer;
use crate::widget;
use crate::widget::tree::{self, Tree};
use crate::{
Clipboard, Color, Layout, Length, Rectangle, Shell, Vector, Widget,
Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, Widget,
};

use std::any::Any;
Expand Down Expand Up @@ -296,12 +296,8 @@ where
self.widget.diff(tree);
}

fn width(&self) -> Length {
self.widget.width()
}

fn height(&self) -> Length {
self.widget.height()
fn size(&self) -> Size<Length> {
self.widget.size()
}

fn layout(
Expand Down Expand Up @@ -466,12 +462,8 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
where
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
self.element.widget.width()
}

fn height(&self) -> Length {
self.element.widget.height()
fn size(&self) -> Size<Length> {
self.element.widget.size()
}

fn tag(&self) -> tree::Tag {
Expand Down
106 changes: 99 additions & 7 deletions core/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub mod flex;
pub use limits::Limits;
pub use node::Node;

use crate::{Point, Rectangle, Size, Vector};
use crate::{Length, Padding, Point, Rectangle, Size, Vector};

/// The bounds of a [`Node`] and its children, using absolute coordinates.
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -71,12 +71,12 @@ pub fn next_to_each_other(
left: impl FnOnce(&Limits) -> Node,
right: impl FnOnce(&Limits) -> Node,
) -> Node {
let mut left_node = left(limits);
let left_node = left(limits);
let left_size = left_node.size();

let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));

let mut right_node = right(&right_limits);
let right_node = right(&right_limits);
let right_size = right_node.size();

let (left_y, right_y) = if left_size.height > right_size.height {
Expand All @@ -85,14 +85,106 @@ pub fn next_to_each_other(
((right_size.height - left_size.height) / 2.0, 0.0)
};

left_node.move_to(Point::new(0.0, left_y));
right_node.move_to(Point::new(left_size.width + spacing, right_y));

Node::with_children(
Size::new(
left_size.width + spacing + right_size.width,
left_size.height.max(right_size.height),
),
vec![left_node, right_node],
vec![
left_node.move_to(Point::new(0.0, left_y)),
right_node.move_to(Point::new(left_size.width + spacing, right_y)),
],
)
}

/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and no intrinsic size.
pub fn atomic(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
) -> Node {
let width = width.into();
let height = height.into();

Node::new(limits.resolve(width, height, Size::ZERO))
}

/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and a closure that produces
/// the intrinsic [`Size`] inside the given [`Limits`].
pub fn sized(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
f: impl FnOnce(&Limits) -> Size,
) -> Node {
let width = width.into();
let height = height.into();

let limits = limits.width(width).height(height);
let intrinsic_size = f(&limits);

Node::new(limits.resolve(width, height, intrinsic_size))
}

/// Computes the resulting [`Node`] that fits the [`Limits`] given
/// some width and height requirements and a closure that produces
/// the content [`Node`] inside the given [`Limits`].
pub fn contained(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
f: impl FnOnce(&Limits) -> Node,
) -> Node {
let width = width.into();
let height = height.into();

let limits = limits.width(width).height(height);
let content = f(&limits);

Node::with_children(
limits.resolve(width, height, content.size()),
vec![content],
)
}

/// Computes the [`Node`] that fits the [`Limits`] given some width, height, and
/// [`Padding`] requirements and a closure that produces the content [`Node`]
/// inside the given [`Limits`].
pub fn padded(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
padding: impl Into<Padding>,
layout: impl FnOnce(&Limits) -> Node,
) -> Node {
positioned(limits, width, height, padding, layout, |content, _| content)
}

/// Computes a [`padded`] [`Node`] with a positioning step.
pub fn positioned(
limits: &Limits,
width: impl Into<Length>,
height: impl Into<Length>,
padding: impl Into<Padding>,
layout: impl FnOnce(&Limits) -> Node,
position: impl FnOnce(Node, Size) -> Node,
) -> Node {
let width = width.into();
let height = height.into();
let padding = padding.into();

let limits = limits.width(width).height(height);
let content = layout(&limits.shrink(padding));
let padding = padding.fit(content.size(), limits.max());

let size = limits
.shrink(padding)
.resolve(width, height, content.size());

Node::with_children(
size.expand(padding),
vec![position(content.move_to((padding.left, padding.top)), size)],
)
}
117 changes: 87 additions & 30 deletions core/src/layout/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::Element;

use crate::layout::{Limits, Node};
use crate::widget;
use crate::{Alignment, Padding, Point, Size};
use crate::{Alignment, Length, Padding, Point, Size};

/// The main axis of a flex layout.
#[derive(Debug)]
Expand All @@ -47,7 +47,7 @@ impl Axis {
}
}

fn pack(&self, main: f32, cross: f32) -> (f32, f32) {
fn pack<T>(&self, main: T, cross: T) -> (T, T) {
match self {
Axis::Horizontal => (main, cross),
Axis::Vertical => (cross, main),
Expand All @@ -63,6 +63,8 @@ pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
limits: &Limits,
width: Length,
height: Length,
padding: Padding,
spacing: f32,
align_items: Alignment,
Expand All @@ -72,26 +74,64 @@ pub fn resolve<Message, Renderer>(
where
Renderer: crate::Renderer,
{
let limits = limits.pad(padding);
let limits = limits.width(width).height(height).shrink(padding);
let total_spacing = spacing * items.len().saturating_sub(1) as f32;
let max_cross = axis.cross(limits.max());

let mut fill_sum = 0;
let mut cross = axis.cross(limits.min()).max(axis.cross(limits.fill()));
let mut fill_main_sum = 0;
let mut cross = match axis {
Axis::Horizontal => match height {
Length::Shrink => 0.0,
_ => max_cross,
},
Axis::Vertical => match width {
Length::Shrink => 0.0,
_ => max_cross,
},
};

let mut available = axis.main(limits.max()) - total_spacing;

let mut nodes: Vec<Node> = Vec::with_capacity(items.len());
nodes.resize(items.len(), Node::default());

for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();

axis.pack(size.width.fill_factor(), size.height.fill_factor())
};

if fill_main_factor == 0 {
if fill_cross_factor == 0 {
let (max_width, max_height) = axis.pack(available, max_cross);

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

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

available -= axis.main(size);
cross = cross.max(axis.cross(size));

nodes[i] = layout;
}
} else {
fill_main_sum += fill_main_factor;
}
.fill_factor();
}

if fill_factor == 0 {
let (max_width, max_height) = axis.pack(available, max_cross);
for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() {
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();

axis.pack(size.width.fill_factor(), size.height.fill_factor())
};

if fill_main_factor == 0 && fill_cross_factor != 0 {
let (max_width, max_height) = axis.pack(available, cross);

let child_limits =
Limits::new(Size::ZERO, Size::new(max_width, max_height));
Expand All @@ -101,34 +141,47 @@ where
let size = layout.size();

available -= axis.main(size);
cross = cross.max(axis.cross(size));
cross = cross.max(axis.cross(layout.size()));

nodes[i] = layout;
} else {
fill_sum += fill_factor;
}
}

let remaining = available.max(0.0);
let remaining = match axis {
Axis::Horizontal => match width {
Length::Shrink => 0.0,
_ => available.max(0.0),
},
Axis::Vertical => match height {
Length::Shrink => 0.0,
_ => available.max(0.0),
},
};

for (i, (child, tree)) in items.iter().zip(trees).enumerate() {
let fill_factor = match axis {
Axis::Horizontal => child.as_widget().width(),
Axis::Vertical => child.as_widget().height(),
}
.fill_factor();
let (fill_main_factor, fill_cross_factor) = {
let size = child.as_widget().size();

axis.pack(size.width.fill_factor(), size.height.fill_factor())
};

if fill_main_factor != 0 {
let max_main =
remaining * fill_main_factor as f32 / fill_main_sum as f32;

if fill_factor != 0 {
let max_main = remaining * 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) =
axis.pack(min_main, axis.cross(limits.min()));
let max_cross = if fill_cross_factor == 0 {
max_cross
} else {
cross
};

let (min_width, min_height) = axis.pack(min_main, 0.0);
let (max_width, max_height) = axis.pack(max_main, max_cross);

let child_limits = Limits::new(
Expand All @@ -154,18 +207,18 @@ where

let (x, y) = axis.pack(main, pad.1);

node.move_to(Point::new(x, y));
node.move_to_mut(Point::new(x, y));

match axis {
Axis::Horizontal => {
node.align(
node.align_mut(
Alignment::Start,
align_items,
Size::new(0.0, cross),
);
}
Axis::Vertical => {
node.align(
node.align_mut(
align_items,
Alignment::Start,
Size::new(cross, 0.0),
Expand All @@ -178,8 +231,12 @@ where
main += axis.main(size);
}

let (width, height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(Size::new(width, height));
let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(
width,
height,
Size::new(intrinsic_width, intrinsic_height),
);

Node::with_children(size.pad(padding), nodes)
Node::with_children(size.expand(padding), nodes)
}
Loading
Loading