diff --git a/src/widget.rs b/src/widget.rs index 089cfd2..114209c 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -39,6 +39,7 @@ use crate::utils::IgnorePoison; use crate::value::{Dynamic, Generation, IntoDynamic, IntoValue, Validation, Value}; use crate::widgets::checkbox::{Checkable, CheckboxState}; use crate::widgets::layers::{OverlayLayer, Tooltipped}; +use crate::widgets::list::List; use crate::widgets::{ Align, Button, Checkbox, Collapse, Container, Disclose, Expand, Layers, Resize, Scroll, Space, Stack, Style, Themed, ThemedMode, Validated, Wrap, @@ -2036,6 +2037,12 @@ impl WidgetList { Wrap::new(self) } + /// Returns `self` as an unordered [`List`]. + #[must_use] + pub fn into_list(self) -> List { + List::new(self) + } + /// Synchronizes this list of children with another collection. /// /// This function updates `collection` by calling `change_fn` for each diff --git a/src/widgets.rs b/src/widgets.rs index 5121144..5a6d55e 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -17,6 +17,7 @@ pub mod image; pub mod input; pub mod label; pub mod layers; +pub mod list; mod mode_switch; pub mod progress; pub mod radio; diff --git a/src/widgets/list.rs b/src/widgets/list.rs new file mode 100644 index 0000000..bf56faa --- /dev/null +++ b/src/widgets/list.rs @@ -0,0 +1,92 @@ +//! A list of elements with optional item indicators. + +use std::fmt::Debug; +use std::sync::Arc; + +use super::grid::GridWidgets; +use super::input::CowString; +use super::Grid; +use crate::value::{IntoValue, MapEach, Source, Value}; +use crate::widget::{MakeWidget, WidgetInstance, WidgetList}; + +/// A list of items displayed with an optional item indicator. +pub struct List { + style: Value, + children: Value, +} + +impl List { + /// Returns a new list with the default [`ListStyle`]/ + pub fn new(children: impl IntoValue) -> Self { + Self { + children: children.into_value(), + style: Value::Constant(ListStyle::default()), + } + } +} + +/// The style of a [`List`] widget's item indicators. +#[derive(Default, Debug, Clone)] +pub enum ListStyle { + #[default] + Disc, + Custom(Arc), +} + +impl PartialEq for ListStyle { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0), + _ => core::mem::discriminant(self) == core::mem::discriminant(other), + } + } +} + +/// A [`ListStyle`] implementation that provides an optional indicator for a +/// given list index. +pub trait ListIndicator: Debug + Sync + Send + 'static { + /// Returns the indicator to use at `index`. + fn list_indicator(&self, index: usize) -> Option; +} + +impl ListIndicator for ListStyle { + fn list_indicator(&self, index: usize) -> Option { + match self { + ListStyle::Disc => Some(CowString::new("\u{2022}")), + ListStyle::Custom(style) => style.list_indicator(index), + } + } +} + +impl MakeWidget for List { + fn make_widget(self) -> WidgetInstance { + let rows = match (self.children, self.style) { + (children, Value::Constant(style)) => { + children.map_each(move |children| build_grid_widgets(&style, children)) + } + (Value::Dynamic(children), Value::Dynamic(style)) => Value::Dynamic( + (&style, &children) + .map_each(|(style, children)| build_grid_widgets(style, children)), + ), + (Value::Constant(children), Value::Dynamic(style)) => { + Value::Dynamic(style.map_each(move |style| build_grid_widgets(style, &children))) + } + }; + Grid::from_rows(rows).make_widget() + } +} + +fn build_grid_widgets(style: &ListStyle, children: &WidgetList) -> GridWidgets<2> { + // This is horrible. We should be be using synchronize_with to avoid + // recreating the gridwidgets every time. + children + .iter() + .enumerate() + .map(|(index, child)| { + ( + style.list_indicator(index).unwrap_or_default(), + child.clone().align_left().make_widget(), + ) + }) + .collect() +}