diff options
author | 2024-01-05 17:24:43 +0100 | |
---|---|---|
committer | 2024-01-10 10:01:49 +0100 | |
commit | 22226394f7b1a0e0205b9bb5b3ef9b85a3b406f5 (patch) | |
tree | 22214a4ac6bca5033f6d5a227934288019f6ca60 | |
parent | 0322e820eb40d36a7425246278b7bcb22b7010aa (diff) | |
download | iced-22226394f7b1a0e0205b9bb5b3ef9b85a3b406f5.tar.gz iced-22226394f7b1a0e0205b9bb5b3ef9b85a3b406f5.tar.bz2 iced-22226394f7b1a0e0205b9bb5b3ef9b85a3b406f5.zip |
Introduce `Widget::size_hint` and fix further layout inconsistencies
-rw-r--r-- | core/src/layout/flex.rs | 72 | ||||
-rw-r--r-- | core/src/length.rs | 6 | ||||
-rw-r--r-- | core/src/widget.rs | 10 | ||||
-rw-r--r-- | examples/download_progress/src/main.rs | 19 | ||||
-rw-r--r-- | examples/events/src/main.rs | 3 | ||||
-rw-r--r-- | examples/layout/src/main.rs | 2 | ||||
-rw-r--r-- | examples/lazy/src/main.rs | 46 | ||||
-rw-r--r-- | examples/loading_spinners/src/main.rs | 11 | ||||
-rw-r--r-- | examples/scrollable/src/main.rs | 23 | ||||
-rw-r--r-- | examples/tour/src/main.rs | 1 | ||||
-rw-r--r-- | examples/websocket/src/main.rs | 11 | ||||
-rw-r--r-- | widget/src/column.rs | 39 | ||||
-rw-r--r-- | widget/src/container.rs | 15 | ||||
-rw-r--r-- | widget/src/helpers.rs | 22 | ||||
-rw-r--r-- | widget/src/lazy.rs | 7 | ||||
-rw-r--r-- | widget/src/lazy/component.rs | 7 | ||||
-rw-r--r-- | widget/src/row.rs | 41 |
17 files changed, 211 insertions, 124 deletions
diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index 9a4b2cbf..67cc7f2a 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -91,8 +91,46 @@ where child.as_widget().height().fill_factor(), ); - if fill_main_factor == 0 && fill_cross_factor == 0 { - let (max_width, max_height) = axis.pack(available, max_cross); + 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; + } + } + + let intrinsic_cross = match axis { + Axis::Horizontal => match height { + Length::Shrink => cross, + _ => max_cross, + }, + Axis::Vertical => match width { + Length::Shrink => cross, + _ => max_cross, + }, + }; + + for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { + let (fill_main_factor, fill_cross_factor) = axis.pack( + child.as_widget().width().fill_factor(), + child.as_widget().height().fill_factor(), + ); + + if fill_main_factor == 0 && fill_cross_factor != 0 { + let (max_width, max_height) = axis.pack(available, intrinsic_cross); let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); @@ -102,11 +140,8 @@ where let size = layout.size(); available -= axis.main(size); - cross = cross.max(axis.cross(size)); nodes[i] = layout; - } else { - fill_main_sum += fill_main_factor; } } @@ -121,24 +156,13 @@ where }, }; - let max_cross = match axis { - Axis::Horizontal => match height { - Length::Shrink if cross > 0.0 => cross, - _ => max_cross, - }, - Axis::Vertical => match width { - Length::Shrink if cross > 0.0 => cross, - _ => max_cross, - }, - }; - for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let (fill_main_factor, fill_cross_factor) = axis.pack( child.as_widget().width().fill_factor(), child.as_widget().height().fill_factor(), ); - if fill_main_factor != 0 || fill_cross_factor != 0 { + if fill_main_factor != 0 { let max_main = if fill_main_factor == 0 { available.max(0.0) } else { @@ -151,6 +175,12 @@ where max_main }; + let max_cross = if fill_cross_factor == 0 { + max_cross + } else { + intrinsic_cross + }; + let (min_width, min_height) = axis.pack(min_main, axis.cross(limits.min())); @@ -203,8 +233,12 @@ where main += axis.main(size); } - let (width, height) = axis.pack(main - pad.0, cross); - let size = limits.resolve(Size::new(width, height), width, height); + let (intrinsic_width, intrinsic_height) = axis.pack(main - pad.0, cross); + let size = limits.resolve( + Size::new(intrinsic_width, intrinsic_height), + width, + height, + ); Node::with_children(size.expand(padding), nodes) } diff --git a/core/src/length.rs b/core/src/length.rs index 3adb996e..6dc15049 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -36,6 +36,12 @@ impl Length { Length::Fixed(_) => 0, } } + + /// Returns `true` iff the [`Length`] is either [`Length::Fill`] or + // [`Length::FillPortion`]. + pub fn is_fill(&self) -> bool { + self.fill_factor() != 0 + } } impl From<Pixels> for Length { diff --git a/core/src/widget.rs b/core/src/widget.rs index 294d5984..890b3773 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -15,7 +15,7 @@ use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Length, Rectangle, Shell}; +use crate::{Clipboard, Length, Rectangle, Shell, Size}; /// A component that displays information and allows interaction. /// @@ -49,6 +49,14 @@ where /// Returns the height of the [`Widget`]. fn height(&self) -> Length; + /// Returns a [`Size`] hint for laying out the [`Widget`]. + /// + /// This hint may be used by some widget containers to adjust their sizing strategy + /// during construction. + fn size_hint(&self) -> Size<Length> { + Size::new(self.width(), self.height()) + } + /// Returns the [`layout::Node`] of the [`Widget`]. /// /// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index a2fcb275..675e9e26 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -73,16 +73,15 @@ impl Application for Example { } fn view(&self) -> Element<Message> { - let downloads = Column::with_children( - self.downloads.iter().map(Download::view).collect(), - ) - .push( - button("Add another download") - .on_press(Message::Add) - .padding(10), - ) - .spacing(20) - .align_items(Alignment::End); + let downloads = + Column::with_children(self.downloads.iter().map(Download::view)) + .push( + button("Add another download") + .on_press(Message::Add) + .padding(10), + ) + .spacing(20) + .align_items(Alignment::End); container(downloads) .width(Length::Fill) diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 334b012d..fc51ac4a 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -82,8 +82,7 @@ impl Application for Events { self.last .iter() .map(|event| text(format!("{event:?}")).size(40)) - .map(Element::from) - .collect(), + .map(Element::from), ); let toggle = checkbox( diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs index 1b0c0c94..eeaa76b6 100644 --- a/examples/layout/src/main.rs +++ b/examples/layout/src/main.rs @@ -106,7 +106,7 @@ impl Example { column![text("Original text")].padding(10), |quotes, i| { column![ - row![vertical_rule(2), quotes], + row![vertical_rule(2), quotes].height(Length::Shrink), text(format!("Reply {i}")) ] .spacing(10) diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs index 01560598..04df0744 100644 --- a/examples/lazy/src/main.rs +++ b/examples/lazy/src/main.rs @@ -178,35 +178,23 @@ impl Sandbox for App { } }); - column( - items - .into_iter() - .map(|item| { - let button = button("Delete") - .on_press(Message::DeleteItem(item.clone())) - .style(theme::Button::Destructive); - - row![ - text(&item.name) - .style(theme::Text::Color(item.color.into())), - horizontal_space(Length::Fill), - pick_list( - Color::ALL, - Some(item.color), - move |color| { - Message::ItemColorChanged( - item.clone(), - color, - ) - } - ), - button - ] - .spacing(20) - .into() - }) - .collect(), - ) + column(items.into_iter().map(|item| { + let button = button("Delete") + .on_press(Message::DeleteItem(item.clone())) + .style(theme::Button::Destructive); + + row![ + text(&item.name) + .style(theme::Text::Color(item.color.into())), + horizontal_space(Length::Fill), + pick_list(Color::ALL, Some(item.color), move |color| { + Message::ItemColorChanged(item.clone(), color) + }), + button + ] + .spacing(20) + .into() + })) .spacing(10) }); diff --git a/examples/loading_spinners/src/main.rs b/examples/loading_spinners/src/main.rs index a78e9590..93a4605e 100644 --- a/examples/loading_spinners/src/main.rs +++ b/examples/loading_spinners/src/main.rs @@ -96,15 +96,14 @@ impl Application for LoadingSpinners { container( column.push( - row(vec![ - text("Cycle duration:").into(), + row![ + text("Cycle duration:"), slider(1.0..=1000.0, self.cycle_duration * 100.0, |x| { Message::CycleDurationChanged(x / 100.0) }) - .width(200.0) - .into(), - text(format!("{:.2}s", self.cycle_duration)).into(), - ]) + .width(200.0), + text(format!("{:.2}s", self.cycle_duration)), + ] .align_items(iced::Alignment::Center) .spacing(20.0), ), diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index 1042e7a4..249bc2a5 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -172,23 +172,21 @@ impl Application for ScrollableDemo { ] .spacing(10); - let scroll_alignment_controls = column(vec![ - text("Scrollable alignment:").into(), + let scroll_alignment_controls = column![ + text("Scrollable alignment:"), radio( "Start", scrollable::Alignment::Start, Some(self.alignment), Message::AlignmentChanged, - ) - .into(), + ), radio( "End", scrollable::Alignment::End, Some(self.alignment), Message::AlignmentChanged, ) - .into(), - ]) + ] .spacing(10); let scroll_controls = row![ @@ -226,6 +224,7 @@ impl Application for ScrollableDemo { .padding([40, 0, 40, 0]) .spacing(40), ) + .width(Length::Fill) .height(Length::Fill) .direction(scrollable::Direction::Vertical( Properties::new() @@ -251,6 +250,7 @@ impl Application for ScrollableDemo { .padding([0, 40, 0, 40]) .spacing(40), ) + .width(Length::Fill) .height(Length::Fill) .direction(scrollable::Direction::Horizontal( Properties::new() @@ -293,6 +293,7 @@ impl Application for ScrollableDemo { .padding([0, 40, 0, 40]) .spacing(40), ) + .width(Length::Fill) .height(Length::Fill) .direction({ let properties = Properties::new() @@ -333,19 +334,11 @@ impl Application for ScrollableDemo { let content: Element<Message> = column![scroll_controls, scrollable_content, progress_bars] - .height(Length::Fill) .align_items(Alignment::Center) .spacing(10) .into(); - Element::from( - container(content) - .width(Length::Fill) - .height(Length::Fill) - .padding(40) - .center_x() - .center_y(), - ) + Element::from(container(content).padding(40).center_x().center_y()) } fn theme(&self) -> Self::Theme { diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index b9ee1e61..8633bc0a 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -509,7 +509,6 @@ impl<'a> Step { ) }) .map(Element::from) - .collect() ) .spacing(10) ] diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 5fdf6657..59488e69 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -3,7 +3,7 @@ mod echo; use iced::alignment::{self, Alignment}; use iced::executor; use iced::widget::{ - button, column, container, row, scrollable, text, text_input, Column, + button, column, container, row, scrollable, text, text_input, }; use iced::{ Application, Color, Command, Element, Length, Settings, Subscription, Theme, @@ -108,13 +108,8 @@ impl Application for WebSocket { .into() } else { scrollable( - Column::with_children( - self.messages - .iter() - .cloned() - .map(text) - .map(Element::from) - .collect(), + column( + self.messages.iter().cloned().map(text).map(Element::from), ) .spacing(10), ) diff --git a/widget/src/column.rs b/widget/src/column.rs index 80327458..52cf35ce 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -22,16 +22,12 @@ pub struct Column<'a, Message, Renderer = crate::Renderer> { children: Vec<Element<'a, Message, Renderer>>, } -impl<'a, Message, Renderer> Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Column<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ /// Creates an empty [`Column`]. pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Column`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { Column { spacing: 0.0, padding: Padding::ZERO, @@ -39,10 +35,17 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { height: Length::Shrink, max_width: f32::INFINITY, align_items: Alignment::Start, - children, + children: Vec::new(), } } + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl Iterator<Item = Element<'a, Message, Renderer>>, + ) -> Self { + children.fold(Self::new(), |column, element| column.push(element)) + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this @@ -88,12 +91,26 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { mut self, child: impl Into<Element<'a, Message, Renderer>>, ) -> Self { - self.children.push(child.into()); + let child = child.into(); + let size = child.as_widget().size_hint(); + + if size.width.is_fill() { + self.width = Length::Fill; + } + + if size.height.is_fill() { + self.height = Length::Fill; + } + + self.children.push(child); self } } -impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Column<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ fn default() -> Self { Self::new() } diff --git a/widget/src/container.rs b/widget/src/container.rs index b41a6023..fbc68db7 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -46,11 +46,22 @@ where where T: Into<Element<'a, Message, Renderer>>, { + let content = content.into(); + let size = content.as_widget().size_hint(); + Container { id: None, padding: Padding::ZERO, - width: Length::Shrink, - height: Length::Shrink, + width: if size.width.is_fill() { + Length::Fill + } else { + Length::Shrink + }, + height: if size.height.is_fill() { + Length::Fill + } else { + Length::Shrink + }, max_width: f32::INFINITY, max_height: f32::INFINITY, horizontal_alignment: alignment::Horizontal::Left, diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 115198fb..6eaf3392 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -34,7 +34,7 @@ macro_rules! column { $crate::Column::new() ); ($($x:expr),+ $(,)?) => ( - $crate::Column::with_children(vec![$($crate::core::Element::from($x)),+]) + $crate::Column::with_children([$($crate::core::Element::from($x)),+].into_iter()) ); } @@ -47,7 +47,7 @@ macro_rules! row { $crate::Row::new() ); ($($x:expr),+ $(,)?) => ( - $crate::Row::with_children(vec![$($crate::core::Element::from($x)),+]) + $crate::Row::with_children([$($crate::core::Element::from($x)),+].into_iter()) ); } @@ -65,9 +65,12 @@ where } /// Creates a new [`Column`] with the given children. -pub fn column<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> Column<'_, Message, Renderer> { +pub fn column<'a, Message, Renderer>( + children: impl Iterator<Item = Element<'a, Message, Renderer>>, +) -> Column<'a, Message, Renderer> +where + Renderer: core::Renderer, +{ Column::with_children(children) } @@ -84,9 +87,12 @@ where /// Creates a new [`Row`] with the given children. /// /// [`Row`]: crate::Row -pub fn row<Message, Renderer>( - children: Vec<Element<'_, Message, Renderer>>, -) -> Row<'_, Message, Renderer> { +pub fn row<'a, Message, Renderer>( + children: impl Iterator<Item = Element<'a, Message, Renderer>>, +) -> Row<'a, Message, Renderer> +where + Renderer: core::Renderer, +{ Row::with_children(children) } diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 167a055d..4f6513db 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -150,6 +150,13 @@ where self.with_element(|element| element.as_widget().height()) } + fn size_hint(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: Length::Shrink, + } + } + fn layout( &self, tree: &mut Tree, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index ad0c3823..0aff7485 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -252,6 +252,13 @@ where self.with_element(|element| element.as_widget().height()) } + fn size_hint(&self) -> Size<Length> { + Size { + width: Length::Shrink, + height: Length::Shrink, + } + } + fn layout( &self, tree: &mut Tree, diff --git a/widget/src/row.rs b/widget/src/row.rs index 50fc4de0..ef371ddb 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -21,26 +21,31 @@ pub struct Row<'a, Message, Renderer = crate::Renderer> { children: Vec<Element<'a, Message, Renderer>>, } -impl<'a, Message, Renderer> Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Row<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ /// Creates an empty [`Row`]. pub fn new() -> Self { - Self::with_children(Vec::new()) - } - - /// Creates a [`Row`] with the given elements. - pub fn with_children( - children: Vec<Element<'a, Message, Renderer>>, - ) -> Self { Row { spacing: 0.0, padding: Padding::ZERO, width: Length::Shrink, height: Length::Shrink, align_items: Alignment::Start, - children, + children: Vec::new(), } } + /// Creates a [`Row`] with the given elements. + pub fn with_children( + children: impl Iterator<Item = Element<'a, Message, Renderer>>, + ) -> Self { + children + .into_iter() + .fold(Self::new(), |column, element| column.push(element)) + } + /// Sets the horizontal spacing _between_ elements. /// /// Custom margins per element do not exist in iced. You should use this @@ -80,12 +85,26 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { mut self, child: impl Into<Element<'a, Message, Renderer>>, ) -> Self { - self.children.push(child.into()); + let child = child.into(); + let size = child.as_widget().size_hint(); + + if size.width.is_fill() { + self.width = Length::Fill; + } + + if size.height.is_fill() { + self.height = Length::Fill; + } + + self.children.push(child); self } } -impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> { +impl<'a, Message, Renderer> Default for Row<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, +{ fn default() -> Self { Self::new() } |