use alloc::vec::Vec;
use waterui_core::AnyView;
use waterui_core::handler::AnyViewBuilder;
use waterui_controls::label::{IntoLabel, Label};
use waterui_layout::{
padding::EdgeInsets,
spacer::spacer,
stack::{HorizontalAlignment, hstack, vstack},
};
use waterui_text::{IntoText, Text};
use crate::Color;
use crate::view::ViewExt;
use super::{ListItem, ListSection};
pub trait ListContent {
fn collect_items(self, sink: &mut ListItemSink);
}
pub struct ListItemSink {
entries: Vec<(AnyViewBuilder<ListItem>, Option<ListSection>)>,
}
impl Default for ListItemSink {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for ListItemSink {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ListItemSink")
.field("len", &self.entries.len())
.finish()
}
}
impl ListItemSink {
#[must_use]
pub const fn new() -> Self {
Self {
entries: Vec::new(),
}
}
#[must_use]
pub const fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn push(&mut self, builder: AnyViewBuilder<ListItem>) {
self.entries.push((builder, None));
}
pub(crate) fn set_section(&mut self, index: usize, section: ListSection) {
if let Some(entry) = self.entries.get_mut(index) {
entry.1 = Some(section);
}
}
pub(crate) fn into_entries(self) -> Vec<(AnyViewBuilder<ListItem>, Option<ListSection>)> {
self.entries
}
}
impl ListContent for () {
fn collect_items(self, _sink: &mut ListItemSink) {}
}
impl<T: ListContent> ListContent for Option<T> {
fn collect_items(self, sink: &mut ListItemSink) {
if let Some(content) = self {
content.collect_items(sink);
}
}
}
impl<T: ListContent> ListContent for Vec<T> {
fn collect_items(self, sink: &mut ListItemSink) {
for item in self {
item.collect_items(sink);
}
}
}
impl<T: ListContent, const N: usize> ListContent for [T; N] {
fn collect_items(self, sink: &mut ListItemSink) {
for item in self {
item.collect_items(sink);
}
}
}
impl<F> ListContent for F
where
F: Fn() -> ListItem + 'static,
{
fn collect_items(self, sink: &mut ListItemSink) {
sink.push(AnyViewBuilder::new(self));
}
}
macro_rules! list_content_tuples {
($macro:ident) => {
$macro!(T0);
$macro!(T0, T1);
$macro!(T0, T1, T2);
$macro!(T0, T1, T2, T3);
$macro!(T0, T1, T2, T3, T4);
$macro!(T0, T1, T2, T3, T4, T5);
$macro!(T0, T1, T2, T3, T4, T5, T6);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
$macro!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
$macro!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14
);
};
}
macro_rules! impl_tuple_list_content {
($($T:ident),+) => {
#[allow(non_snake_case)]
impl<$($T: ListContent),+> ListContent for ($($T,)+) {
fn collect_items(self, sink: &mut ListItemSink) {
let ($($T,)+) = self;
$(
$T.collect_items(sink);
)+
}
}
};
}
list_content_tuples!(impl_tuple_list_content);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RowLayout {
Inline,
Detail,
}
#[derive(Clone)]
pub struct Row {
label: Label,
value: Text,
layout: RowLayout,
value_color: Option<Color>,
deletable: Option<bool>,
}
impl core::fmt::Debug for Row {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Row")
.field("layout", &self.layout)
.field("has_value_color", &self.value_color.is_some())
.field("deletable_override", &self.deletable)
.finish_non_exhaustive()
}
}
impl Row {
#[must_use]
pub const fn detail(mut self) -> Self {
self.layout = RowLayout::Detail;
self
}
#[must_use]
pub const fn inline(mut self) -> Self {
self.layout = RowLayout::Inline;
self
}
#[must_use]
pub fn value_color(mut self, color: impl Into<Color>) -> Self {
self.value_color = Some(color.into());
self
}
#[must_use]
pub const fn deletable(mut self, deletable: bool) -> Self {
self.deletable = Some(deletable);
self
}
fn build_item(&self) -> ListItem {
let label = self.label.clone();
let value = self.value.clone();
let value_view: AnyView = match self.value_color.clone() {
Some(color) => AnyView::new(value.foreground(color)),
None => AnyView::new(value),
};
ListItem::new(self.assemble(label, value_view))
}
fn assemble(&self, label: Label, value: AnyView) -> AnyView {
match self.layout {
RowLayout::Inline => AnyView::new(
hstack((label, spacer(), value))
.spacing(12.0)
.padding_with(EdgeInsets::symmetric(10.0, 16.0)),
),
RowLayout::Detail => AnyView::new(
vstack((label, value))
.alignment(HorizontalAlignment::Leading)
.spacing(6.0)
.padding_with(EdgeInsets::symmetric(10.0, 16.0)),
),
}
}
}
impl ListContent for Row {
fn collect_items(self, sink: &mut ListItemSink) {
let row = self;
let deletable_override = row.deletable;
let builder = AnyViewBuilder::new(move || {
let mut item = row.build_item();
if let Some(d) = deletable_override {
item = item.deletable(d);
}
item
});
sink.push(builder);
}
}
#[must_use]
pub fn row(label: impl IntoLabel, value: impl IntoText) -> Row {
Row {
label: label.into_label(),
value: value.into_text(),
layout: RowLayout::Inline,
value_color: None,
deletable: None,
}
}
#[must_use]
pub fn detail_row(label: impl IntoLabel, value: impl IntoText) -> Row {
row(label, value).detail()
}