Started a List widget

a la HTML's <ul> tag.
This commit is contained in:
Jonathan Johnson 2024-01-13 06:04:41 -08:00
parent 1b97b39857
commit c2bd9911ca
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 100 additions and 0 deletions

View file

@ -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

View file

@ -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;

92
src/widgets/list.rs Normal file
View file

@ -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<ListStyle>,
children: Value<WidgetList>,
}
impl List {
/// Returns a new list with the default [`ListStyle`]/
pub fn new(children: impl IntoValue<WidgetList>) -> 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<dyn ListIndicator>),
}
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<CowString>;
}
impl ListIndicator for ListStyle {
fn list_indicator(&self, index: usize) -> Option<CowString> {
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()
}