// 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 }