Make grid sizing strategy explicit and more intuitive

This commit is contained in:
Héctor Ramón Jiménez 2025-04-10 15:39:10 +02:00
parent 504d9c2959
commit 687750e026
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
4 changed files with 74 additions and 28 deletions

View file

@ -22,6 +22,10 @@ use std::collections::HashMap;
fn main() -> iced::Result { fn main() -> iced::Result {
iced::application(Gallery::new, Gallery::update, Gallery::view) iced::application(Gallery::new, Gallery::update, Gallery::view)
.window_size((
Preview::WIDTH as f32 * 4.0,
Preview::HEIGHT as f32 * 2.5,
))
.subscription(Gallery::subscription) .subscription(Gallery::subscription)
.theme(Gallery::theme) .theme(Gallery::theme)
.run() .run()
@ -183,7 +187,7 @@ impl Gallery {
let gallery = grid(images) let gallery = grid(images)
.fluid(Preview::WIDTH) .fluid(Preview::WIDTH)
.ratio(Preview::WIDTH as f32 / Preview::HEIGHT as f32) .height(grid::aspect_ratio(Preview::WIDTH, Preview::HEIGHT))
.spacing(10); .spacing(10);
let content = container(scrollable(gallery).spacing(10)).padding(10); let content = container(scrollable(gallery).spacing(10)).padding(10);
@ -225,7 +229,7 @@ fn card<'a>(
horizontal_space().into() horizontal_space().into()
}; };
let card = mouse_area(container(image).height(Fill).style(container::dark)) let card = mouse_area(container(image).style(container::dark))
.on_enter(Message::ThumbnailHovered(metadata.id, true)) .on_enter(Message::ThumbnailHovered(metadata.id, true))
.on_exit(Message::ThumbnailHovered(metadata.id, false)); .on_exit(Message::ThumbnailHovered(metadata.id, false));
@ -245,10 +249,7 @@ fn card<'a>(
} }
fn placeholder<'a>() -> Element<'a, Message> { fn placeholder<'a>() -> Element<'a, Message> {
container(horizontal_space()) container(horizontal_space()).style(container::dark).into()
.height(Fill)
.style(container::dark)
.into()
} }
enum Preview { enum Preview {

View file

@ -15,7 +15,7 @@ pub struct Grid<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer> {
spacing: f32, spacing: f32,
columns: Constraint, columns: Constraint,
width: Option<Pixels>, width: Option<Pixels>,
ratio: Option<Pixels>, height: Sizing,
children: Vec<Element<'a, Message, Theme, Renderer>>, children: Vec<Element<'a, Message, Theme, Renderer>>,
} }
@ -55,7 +55,7 @@ where
spacing: 0.0, spacing: 0.0,
columns: Constraint::Amount(3), columns: Constraint::Amount(3),
width: None, width: None,
ratio: None, height: Sizing::AspectRatio(1.0),
children, children,
} }
} }
@ -76,6 +76,14 @@ where
self self
} }
/// Sets the height of the [`Grid`].
///
/// By default, a [`Grid`] uses a cell aspect ratio of `1.0` (i.e. squares).
pub fn height(mut self, height: impl Into<Sizing>) -> Self {
self.height = height.into();
self
}
/// Sets the amount of columns in the [`Grid`]. /// Sets the amount of columns in the [`Grid`].
pub fn columns(mut self, column: usize) -> Self { pub fn columns(mut self, column: usize) -> Self {
self.columns = Constraint::Amount(column); self.columns = Constraint::Amount(column);
@ -89,12 +97,6 @@ where
self self
} }
/// Sets the amount of horizontal pixels per each vertical pixel of a cell in the [`Grid`].
pub fn ratio(mut self, ratio: impl Into<Pixels>) -> Self {
self.ratio = Some(ratio.into());
self
}
/// Adds an [`Element`] to the [`Grid`]. /// Adds an [`Element`] to the [`Grid`].
pub fn push( pub fn push(
mut self, mut self,
@ -166,7 +168,10 @@ where
.width .width
.map(|pixels| Length::Fixed(pixels.0)) .map(|pixels| Length::Fixed(pixels.0))
.unwrap_or(Length::Fill), .unwrap_or(Length::Fill),
height: Length::Shrink, height: match self.height {
Sizing::AspectRatio(_) => Length::Shrink,
Sizing::EvenlyDistribute(length) => length,
},
} }
} }
@ -194,22 +199,23 @@ where
- self.spacing * (cells_per_row - 1) as f32) - self.spacing * (cells_per_row - 1) as f32)
/ cells_per_row as f32; / cells_per_row as f32;
let cell_height = if let Some(ratio) = self.ratio { let cell_height = match self.height {
cell_width / ratio.0 Sizing::AspectRatio(ratio) => Some(cell_width / ratio),
} else if available.height.is_finite() { Sizing::EvenlyDistribute(Length::Shrink) => None,
available.height / total_rows as f32 Sizing::EvenlyDistribute(_) => {
} else { Some(available.height / total_rows as f32)
f32::INFINITY }
}; };
let cell_limits = layout::Limits::new( let cell_limits = layout::Limits::new(
Size::new(cell_width, 0.0), Size::new(cell_width, cell_height.unwrap_or(0.0)),
Size::new(cell_width, cell_height), Size::new(cell_width, cell_height.unwrap_or(available.height)),
); );
let mut nodes = Vec::new(); let mut nodes = Vec::new();
let mut x = 0.0; let mut x = 0.0;
let mut y = 0.0; let mut y = 0.0;
let mut row_height = 0.0f32;
for (i, (child, tree)) in for (i, (child, tree)) in
self.children.iter().zip(&mut tree.children).enumerate() self.children.iter().zip(&mut tree.children).enumerate()
@ -219,11 +225,15 @@ where
.layout(tree, renderer, &cell_limits) .layout(tree, renderer, &cell_limits)
.move_to((x, y)); .move_to((x, y));
x += node.size().width + self.spacing; let size = node.size();
x += size.width + self.spacing;
row_height = row_height.max(size.height);
if (i + 1) % cells_per_row == 0 { if (i + 1) % cells_per_row == 0 {
y += cell_height + self.spacing; y += cell_height.unwrap_or(row_height) + self.spacing;
x = 0.0; x = 0.0;
row_height = 0.0;
} }
nodes.push(node); nodes.push(node);
@ -232,7 +242,7 @@ where
if x == 0.0 { if x == 0.0 {
y -= self.spacing; y -= self.spacing;
} else { } else {
y += cell_height; y += cell_height.unwrap_or(row_height);
} }
layout::Node::with_children(Size::new(available.width, y), nodes) layout::Node::with_children(Size::new(available.width, y), nodes)
@ -356,3 +366,38 @@ where
Self::new(row) Self::new(row)
} }
} }
/// The sizing strategy of a [`Grid`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Sizing {
/// The [`Grid`] will ensure each cell follows the given aspect ratio and the
/// total size will be the sum of the cells and the spacing between them.
///
/// The ratio is the amount of horizontal pixels per each vertical pixel of a cell
/// in the [`Grid`].
AspectRatio(f32),
/// The [`Grid`] will evenly distribute the space available in the given [`Length`]
/// for each cell.
EvenlyDistribute(Length),
}
impl From<f32> for Sizing {
fn from(height: f32) -> Self {
Self::EvenlyDistribute(Length::from(height))
}
}
impl From<Length> for Sizing {
fn from(height: Length) -> Self {
Self::EvenlyDistribute(height)
}
}
/// Creates a new [`Sizing`] strategy that maintains the given aspect ratio.
pub fn aspect_ratio(
width: impl Into<Pixels>,
height: impl Into<Pixels>,
) -> Sizing {
Sizing::AspectRatio(width.into().0 / height.into().0)
}

View file

@ -10,7 +10,6 @@ pub use iced_runtime::core;
mod action; mod action;
mod column; mod column;
mod grid;
mod mouse_area; mod mouse_area;
mod pin; mod pin;
mod space; mod space;
@ -21,6 +20,7 @@ pub mod button;
pub mod checkbox; pub mod checkbox;
pub mod combo_box; pub mod combo_box;
pub mod container; pub mod container;
pub mod grid;
pub mod keyed; pub mod keyed;
pub mod overlay; pub mod overlay;
pub mod pane_grid; pub mod pane_grid;

View file

@ -461,7 +461,7 @@ where
limits.max().width limits.max().width
}, },
if self.direction.vertical().is_some() { if self.direction.vertical().is_some() {
f32::MAX f32::INFINITY
} else { } else {
limits.max().height limits.max().height
}, },