diff --git a/src/widget/flex_row.rs b/src/widget/flex_row.rs new file mode 100644 index 00000000..586d44ce --- /dev/null +++ b/src/widget/flex_row.rs @@ -0,0 +1,132 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use crate::Element; +use apply::Apply; +use derive_setters::Setters; +use iced::widget::{column, row}; +use iced_core::{alignment, Length, Size}; + +/// Responsively generates rows and columns of widgets based on its dimmensions. +#[derive(Setters)] +pub struct FlexRow<'a, Message> { + #[setters(skip)] + generator: Box (u16, Vec>) + 'a>, + /// Sets the space between each column of items. + column_spacing: u16, + /// Sets the space between each item in a row. + row_spacing: u16, + /// Sets the max number of items per row. + max_items: Option, + /// Sets the horizontal alignment of the [`FlexRow`]. + align_x: alignment::Horizontal, + /// Sets the vertical alignment of the [`FlexRow`]. + align_y: alignment::Vertical, + /// Sets the width of the [`FlexRow`]. + width: Length, + /// Sets the height of the [`FlexRow`]. + height: Length, +} + +/// Responsively generates rows and columns of widgets based on its dimmensions. +/// +/// The `generator` input is a closure which must return the max width of all +/// elements created, and a `Vec` containing the generated elements. +/// +/// ## Example +/// +/// Suppose that there is a `COLOR_VALUE` variable which contains an array of +/// color values, and a `color_button` function which creates an `Element` from +/// a color. +/// +/// We already know beforehand that our color buttons will have a fixed width +/// of `70`, so the `generator` closure returns this with a `Vec` of our color +/// button widgets. +/// +/// ```ignore +/// use iced_core::{alignment, Length}; +/// +/// let generator = |_size| { +/// let elements = COLOR_VALUES.iter() +/// .cloned() +/// .map(color_button) +/// .collect::>(); +/// +/// (70, elements) +/// }; +/// +/// cosmic::widget::flex_row(generator) +/// .column_spacing(12) +/// .row_spacing(16) +/// .width(Length::Fill) +/// .align_x(alignment::Horizontal::Center) +/// .into() +/// ``` +pub fn flex_row<'a, Message: 'static>( + generator: impl Fn(Size) -> (u16, Vec>) + 'a, +) -> FlexRow<'a, Message> { + FlexRow { + generator: Box::new(generator), + column_spacing: 4, + row_spacing: 4, + max_items: None, + align_x: alignment::Horizontal::Left, + align_y: alignment::Vertical::Top, + width: Length::Shrink, + height: Length::Shrink, + } +} + +impl<'a, Message: 'static> From> for Element<'a, Message> { + fn from(container: FlexRow<'a, Message>) -> Self { + iced::widget::responsive(move |size| { + let (item_width, mut elements) = (container.generator)(size); + + let mut items_per_row = flex_row_items( + size.width, + f32::from(item_width), + f32::from(container.row_spacing), + ) as usize; + + if let Some(max_items) = container.max_items { + items_per_row = items_per_row.max(max_items as usize); + } + + let mut elements_column = Vec::with_capacity(elements.len() / items_per_row); + + let mut iterator = elements.drain(..); + + while let Some(element) = iterator.next() { + let mut elements_row = Vec::with_capacity(items_per_row); + elements_row.push(element); + + for element in iterator.by_ref().take(items_per_row - 1) { + elements_row.push(element); + } + + elements_column.push(row(elements_row).spacing(container.row_spacing).into()); + } + + column(elements_column) + .spacing(container.column_spacing) + .apply(iced::widget::container) + .align_x(container.align_x) + .align_y(container.align_y) + .width(container.width) + .height(container.height) + .into() + }) + .into() + } +} + +#[allow(clippy::cast_precision_loss)] +fn flex_row_items(available: f32, item_width: f32, spacing: f32) -> u32 { + let mut items = 2; + + while available >= (item_width + spacing) * items as f32 - spacing { + items += 1; + } + + items - 1 +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index fab55020..1a6b3c17 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -8,6 +8,9 @@ pub mod aspect_ratio; mod button; pub use button::*; +pub mod flex_row; +pub use flex_row::{flex_row, FlexRow}; + mod header_bar; pub use header_bar::{header_bar, HeaderBar};