From e02def6bc16dfe61937816d669514c9d4300ae1a Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 21 Jan 2025 13:40:56 +0100 Subject: [PATCH] update --- src/ui/mod.rs | 23 +- src/ui/primitives/display.rs | 31 +++ src/ui/primitives/flex.rs | 84 +++++++ src/ui/primitives/grid.rs | 461 +++++++++++++++++++++++++++++++++++ src/ui/primitives/list.rs | 103 ++++++++ src/ui/primitives/mod.rs | 3 + src/ui/primitives/table.rs | 204 ++++++++++++++++ 7 files changed, 897 insertions(+), 12 deletions(-) create mode 100644 src/ui/primitives/grid.rs create mode 100644 src/ui/primitives/list.rs create mode 100644 src/ui/primitives/table.rs diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d158a54..ab1717d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,5 @@ use components::Shell; -use maud::{Markup, PreEscaped, Render}; -use prelude::Text; +use maud::{Markup, PreEscaped, Render, html}; // UI @@ -154,37 +153,37 @@ impl UIWidget for PreEscaped { impl UIWidget for String { fn can_inherit(&self) -> bool { - Text(&self).can_inherit() + false } fn base_class(&self) -> Vec { - Text(&self).base_class() + Vec::new() } fn extended_class(&self) -> Vec { - Text(&self).extended_class() + Vec::new() } - fn render_with_class(&self, class: &str) -> Markup { - Text(&self).render_with_class(class) + fn render_with_class(&self, _: &str) -> Markup { + html!((self)) } } impl UIWidget for &str { fn can_inherit(&self) -> bool { - Text(&self).can_inherit() + false } fn base_class(&self) -> Vec { - Text(&self).base_class() + Vec::new() } fn extended_class(&self) -> Vec { - Text(&self).extended_class() + Vec::new() } - fn render_with_class(&self, class: &str) -> Markup { - Text(&self).render_with_class(class) + fn render_with_class(&self, _: &str) -> Markup { + html!((self)) } } diff --git a/src/ui/primitives/display.rs b/src/ui/primitives/display.rs index 14fddee..287bd59 100644 --- a/src/ui/primitives/display.rs +++ b/src/ui/primitives/display.rs @@ -281,3 +281,34 @@ impl Overflow { constructor!(XScroll, "overflow-x-scroll"); constructor!(YScroll, "overflow-y-scroll"); } + +string_class_widget!(JustifySelf); + +impl JustifySelf { + constructor!(Auto, "justify-self-auto"); + constructor!(Start, "justify-self-start"); + constructor!(End, "justify-self-end"); + constructor!(Center, "justify-self-center"); + constructor!(Stretch, "justify-self-stretch"); +} + +string_class_widget!(PlaceSelf); + +impl PlaceSelf { + constructor!(Auto, "place-self-auto"); + constructor!(Start, "place-self-start"); + constructor!(End, "place-self-end"); + constructor!(Center, "place-self-center"); + constructor!(Stretch, "place-self-stretch"); +} + +string_class_widget!(AlignSelf); + +impl AlignSelf { + constructor!(Auto, "self-auto"); + constructor!(Start, "self-start"); + constructor!(End, "self-end"); + constructor!(Center, "self-center"); + constructor!(Stretch, "self-stretch"); + constructor!(Baseline, "self-baseline"); +} diff --git a/src/ui/primitives/flex.rs b/src/ui/primitives/flex.rs index 2c9013a..35b231e 100644 --- a/src/ui/primitives/flex.rs +++ b/src/ui/primitives/flex.rs @@ -109,6 +109,24 @@ impl FlexWidget { self } + #[must_use] + pub fn justify_items(mut self, justify: JustifyItems) -> Self { + self.1.push(justify.to_value().to_string()); + self + } + + #[must_use] + pub fn align_content(mut self, align: AlignContent) -> Self { + self.1.push(align.to_value().to_string()); + self + } + + #[must_use] + pub fn align_items(mut self, align: AlignItems) -> Self { + self.1.push(align.to_value().to_string()); + self + } + #[must_use] pub fn justify(mut self, value: Justify) -> Self { let class = match value { @@ -500,3 +518,69 @@ impl DivideStyle { } } } + +pub enum JustifyItems { + Start, + End, + Center, + Stretch, +} + +impl JustifyItems { + pub const fn to_value(&self) -> &str { + match self { + JustifyItems::Start => "justify-items-start", + JustifyItems::End => "justify-items-end", + JustifyItems::Center => "justify-items-center", + JustifyItems::Stretch => "justify-items-stretch", + } + } +} + +pub enum AlignContent { + Normal, + Center, + Start, + End, + Between, + Around, + Evenly, + Baseline, + Stretch, +} + +impl AlignContent { + pub const fn to_value(&self) -> &str { + match self { + AlignContent::Normal => "content-normal", + AlignContent::Center => "content-center", + AlignContent::Start => "content-start", + AlignContent::End => "content-end", + AlignContent::Between => "content-between", + AlignContent::Around => "content-around", + AlignContent::Evenly => "content-evenly", + AlignContent::Baseline => "content-baseline", + AlignContent::Stretch => "content-stretch", + } + } +} + +pub enum AlignItems { + Start, + End, + Center, + Baseline, + Stretch, +} + +impl AlignItems { + pub const fn to_value(&self) -> &str { + match self { + AlignItems::Start => "items-start", + AlignItems::End => "items-end", + AlignItems::Center => "items-center", + AlignItems::Baseline => "items-baseline", + AlignItems::Stretch => "items-stretch", + } + } +} diff --git a/src/ui/primitives/grid.rs b/src/ui/primitives/grid.rs new file mode 100644 index 0000000..cd1e345 --- /dev/null +++ b/src/ui/primitives/grid.rs @@ -0,0 +1,461 @@ +use crate::ui::{UIWidget, color::UIColor}; +use maud::{Markup, Render, html}; + +use super::{ + flex::{AlignContent, AlignItems, DivideStyle, DivideWidth, Justify, JustifyItems}, + space::ScreenValue, +}; + +#[allow(non_snake_case)] +pub fn Grid(inner: T) -> GridWidget { + GridWidget(Box::new(inner), vec![], false) +} + +pub struct GridWidget(Box, Vec, bool); + +impl Render for GridWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +pub enum GridAmount { + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + None, + Subgrid, +} + +impl GridAmount { + pub const fn to_value(&self) -> &str { + match self { + GridAmount::_1 => "1", + GridAmount::_2 => "2", + GridAmount::_3 => "3", + GridAmount::_4 => "4", + GridAmount::_5 => "5", + GridAmount::_6 => "6", + GridAmount::_7 => "7", + GridAmount::_8 => "8", + GridAmount::_9 => "9", + GridAmount::_10 => "10", + GridAmount::_11 => "11", + GridAmount::_12 => "12", + GridAmount::None => "none", + GridAmount::Subgrid => "subgrid", + } + } +} + +impl GridWidget { + #[must_use] + pub fn columns(mut self, amount: GridAmount) -> Self { + self.1.push(format!("grid-cols-{}", amount.to_value())); + self + } + + #[must_use] + pub fn rows(mut self, amount: GridAmount) -> Self { + self.1.push(format!("grid-rows-{}", amount.to_value())); + self + } + + #[must_use] + pub fn auto_flow(mut self, flow: GridAutoFlow) -> Self { + self.1.push(flow.to_value().to_string()); + self + } + + #[must_use] + pub fn auto_columns(mut self, size: GridAutoSize) -> Self { + self.1.push(format!("auto-cols-{}", size.to_value())); + self + } + + #[must_use] + pub fn auto_rows(mut self, size: GridAutoSize) -> Self { + self.1.push(format!("auto-rows-{}", size.to_value())); + self + } + + #[must_use] + pub fn full_center(mut self) -> Self { + self.1.push("items-center".to_owned()); + self.1.push("justify-center".to_owned()); + self + } + + #[must_use] + pub const fn group(mut self) -> Self { + self.2 = true; + self + } + + #[must_use] + pub fn divide_style(mut self, style: DivideStyle) -> Self { + self.1.push(style.to_value().to_string()); + self + } + + #[must_use] + pub fn divide_color(mut self, color: C) -> Self { + self.1.push(format!("divide-{}", color.color_class())); + self + } + + #[must_use] + pub fn divide_x(mut self, width: DivideWidth) -> Self { + self.1.push(format!("divide-x-{}", width.to_value())); + self + } + + #[must_use] + pub fn divide_y(mut self, width: DivideWidth) -> Self { + self.1.push(format!("divide-y-{}", width.to_value())); + self + } + + #[must_use] + pub fn justify_items(mut self, justify: JustifyItems) -> Self { + self.1.push(justify.to_value().to_string()); + self + } + + #[must_use] + pub fn align_content(mut self, align: AlignContent) -> Self { + self.1.push(align.to_value().to_string()); + self + } + + #[must_use] + pub fn align_items(mut self, align: AlignItems) -> Self { + self.1.push(align.to_value().to_string()); + self + } + + #[must_use] + pub fn justify(mut self, value: Justify) -> Self { + let class = match value { + Justify::Center => "justify-center".to_string(), + Justify::Between => "justify-between".to_string(), + Justify::Normal => "justify-normal".to_string(), + Justify::Start => "justify-start".to_string(), + Justify::End => "justify-end".to_string(), + Justify::Around => "justify-around".to_string(), + Justify::Evenly => "justify-evenly".to_string(), + Justify::Stretch => "justify-stretch".to_string(), + }; + + self.1.push(class); + self + } + + #[must_use] + pub fn items_center(mut self) -> Self { + self.1.push("items-center".to_owned()); + self + } + + #[must_use] + pub fn gap(mut self, amount: ScreenValue) -> Self { + self.1.push(format!("gap-{}", amount.to_value())); + self + } + + #[must_use] + pub fn gap_x(mut self, amount: ScreenValue) -> Self { + self.1.push(format!("gap-x-{}", amount.to_value())); + self + } + + #[must_use] + pub fn gap_y(mut self, amount: ScreenValue) -> Self { + self.1.push(format!("gap-y-{}", amount.to_value())); + self + } +} + +impl UIWidget for GridWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + let mut res = vec!["grid".to_string()]; + res.extend_from_slice(&self.1); + res + } + + fn extended_class(&self) -> Vec { + let mut c = self.base_class(); + c.extend_from_slice(&self.0.extended_class()); + c + } + + fn render_with_class(&self, class: &str) -> Markup { + if self.0.as_ref().can_inherit() && !self.2 { + self.0 + .as_ref() + .render_with_class(&format!("{} {class}", self.base_class().join(" "))) + } else { + html! { + div class=(format!("{} {class}", self.base_class().join(" "))) { + (self.0.as_ref()) + } + } + } + } +} + +pub enum GridAutoFlow { + Row, + Column, + Dense, + RowDense, + ColumnDense, +} + +impl GridAutoFlow { + pub const fn to_value(&self) -> &str { + match self { + GridAutoFlow::Row => "grid-flow-row", + GridAutoFlow::Column => "grid-flow-col", + GridAutoFlow::Dense => "grid-flow-dense", + GridAutoFlow::RowDense => "grid-flow-row-dense", + GridAutoFlow::ColumnDense => "grid-flow-col-dense", + } + } +} + +pub enum GridAutoSize { + Auto, + Min, + Max, + Fr, +} + +impl GridAutoSize { + pub const fn to_value(&self) -> &str { + match self { + GridAutoSize::Auto => "auto", + GridAutoSize::Min => "min", + GridAutoSize::Max => "max", + GridAutoSize::Fr => "fr", + } + } +} + +#[allow(non_snake_case)] +pub fn GridElementColumn(inner: T) -> GridElement { + GridElement(Box::new(inner), Vec::new(), "col".to_string()) +} + +#[allow(non_snake_case)] +pub fn GridElementRow(inner: T) -> GridElement { + GridElement(Box::new(inner), Vec::new(), "row".to_string()) +} + +pub struct GridElement(Box, Vec, String); + +impl GridElement { + pub fn auto(mut self) -> Self { + self.1.push(format!("{}-auto", self.2)); + self + } + + pub fn span(mut self, value: GridElementValue) -> Self { + self.1.push(format!("{}-span-{}", self.2, match value { + GridElementValue::_1 => "1", + GridElementValue::_2 => "2", + GridElementValue::_3 => "3", + GridElementValue::_4 => "4", + GridElementValue::_5 => "5", + GridElementValue::_6 => "6", + GridElementValue::_7 => "7", + GridElementValue::_8 => "8", + GridElementValue::_9 => "9", + GridElementValue::_10 => "10", + GridElementValue::_11 => "11", + GridElementValue::_12 => "12", + GridElementValue::Auto => "full", + })); + self + } + + pub fn start(mut self, value: GridElementValue) -> Self { + self.1 + .push(format!("{}-start-{}", self.2, value.to_value())); + self + } + + pub fn end(mut self, value: GridElementValue) -> Self { + self.1.push(format!("{}-end-{}", self.2, value.to_value())); + self + } +} + +impl Render for GridElement { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for GridElement { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + let mut res = vec!["grid".to_string()]; + res.extend_from_slice(&self.1); + res + } + + fn extended_class(&self) -> Vec { + let mut c = self.base_class(); + c.extend_from_slice(&self.0.extended_class()); + c + } + + fn render_with_class(&self, class: &str) -> Markup { + if self.0.as_ref().can_inherit() { + self.0 + .as_ref() + .render_with_class(&format!("{} {class}", self.base_class().join(" "))) + } else { + html! { + div class=(format!("{} {class}", self.base_class().join(" "))) { + (self.0.as_ref()) + } + } + } + } +} + +pub enum GridElementValue { + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + Auto, +} + +impl GridElementValue { + pub const fn to_value(&self) -> &str { + match self { + GridElementValue::_1 => "1", + GridElementValue::_2 => "2", + GridElementValue::_3 => "3", + GridElementValue::_4 => "4", + GridElementValue::_5 => "5", + GridElementValue::_6 => "6", + GridElementValue::_7 => "7", + GridElementValue::_8 => "8", + GridElementValue::_9 => "9", + GridElementValue::_10 => "10", + GridElementValue::_11 => "11", + GridElementValue::_12 => "12", + GridElementValue::Auto => "auto", + } + } +} + +macro_rules! string_class_widget { + ($name:ident) => { + pub struct $name(Box, String); + + impl Render for $name { + fn render(&self) -> Markup { + self.render_with_class("") + } + } + + impl UIWidget for $name { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + vec![self.1.clone()] + } + + fn extended_class(&self) -> Vec { + let mut c = self.base_class(); + c.extend_from_slice(&self.0.extended_class()); + c + } + + fn render_with_class(&self, class: &str) -> Markup { + if self.0.as_ref().can_inherit() { + self.0 + .as_ref() + .render_with_class(&format!("{} {class}", self.base_class().join(" "))) + } else { + html! { + div class=(format!("{} {class}", self.base_class().join(" "))) { + (self.0.as_ref()) + } + } + } + } + } + }; +} + +macro_rules! constructor { + ($name:ident, $class:literal) => { + #[allow(non_snake_case)] + pub fn $name(inner: T) -> Self { + Self(Box::new(inner), $class.to_string()) + } + }; +} + +string_class_widget!(Columns); + +impl Columns { + constructor!(_1, "columns-1"); + constructor!(_2, "columns-2"); + constructor!(_3, "columns-3"); + constructor!(_4, "columns-4"); + constructor!(_5, "columns-5"); + constructor!(_6, "columns-6"); + constructor!(_7, "columns-7"); + constructor!(_8, "columns-8"); + constructor!(_9, "columns-9"); + constructor!(_10, "columns-10"); + constructor!(_11, "columns-11"); + constructor!(_12, "columns-12"); + constructor!(Auto, "columns-auto"); + constructor!(_3XS, "columns-3xs"); + constructor!(_2XS, "columns-2xs"); + constructor!(XS, "columns-xs"); + constructor!(Small, "columns-sm"); + constructor!(Medium, "columns-md"); + constructor!(Large, "columns-lg"); + constructor!(XL, "columns-xl"); + constructor!(_2XL, "columns-2xl"); + constructor!(_3XL, "columns-3xl"); + constructor!(_4XL, "columns-4xl"); + constructor!(_5XL, "columns-5xl"); + constructor!(_6XL, "columns-6xl"); + constructor!(_7XL, "columns-7xl"); +} diff --git a/src/ui/primitives/list.rs b/src/ui/primitives/list.rs new file mode 100644 index 0000000..630a766 --- /dev/null +++ b/src/ui/primitives/list.rs @@ -0,0 +1,103 @@ +use crate::ui::UIWidget; +use maud::{Markup, Render, html}; + +#[allow(non_snake_case)] +#[must_use] +pub fn OrderedList() -> ListWidget { + ListWidget(Vec::new(), true) +} + +#[allow(non_snake_case)] +#[must_use] +pub fn UnorderedList() -> ListWidget { + ListWidget(Vec::new(), false) +} + +pub struct ListWidget(Vec>, bool); + +impl ListWidget { + #[must_use] + pub fn push(mut self, element: T) -> Self { + self.0.push(Box::new(element)); + self + } + + #[must_use] + pub fn push_some T>( + mut self, + option: Option, + then: U, + ) -> Self { + if let Some(val) = option { + self.0.push(Box::new(then(val))); + } + self + } + + #[must_use] + pub fn push_if T>( + mut self, + condition: bool, + then: U, + ) -> Self { + if condition { + self.0.push(Box::new(then())); + } + self + } + + #[must_use] + pub fn push_for_each(mut self, items: &[X], mut action: F) -> Self + where + T: UIWidget + 'static, + F: FnMut(&X) -> T, + { + for item in items { + self.0.push(Box::new(action(item))); + } + + self + } +} + +impl Render for ListWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for ListWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + vec![] + } + + fn extended_class(&self) -> Vec { + vec![] + } + + fn render_with_class(&self, class: &str) -> Markup { + let inner = html! { + @for e in &self.0 { + li { (e.as_ref()) }; + } + }; + + if self.1 { + html! { + ol class=(class) { + (inner); + } + } + } else { + html! { + ul class=(class) { + (inner); + } + } + } + } +} diff --git a/src/ui/primitives/mod.rs b/src/ui/primitives/mod.rs index 3234ef4..d2d21f5 100644 --- a/src/ui/primitives/mod.rs +++ b/src/ui/primitives/mod.rs @@ -11,11 +11,13 @@ pub mod display; pub mod div; pub mod filter; pub mod flex; +pub mod grid; pub mod header; pub mod height; pub mod image; pub mod input; pub mod link; +pub mod list; pub mod margin; pub mod padding; pub mod position; @@ -25,6 +27,7 @@ pub mod shadow; pub mod sized; pub mod space; pub mod svg; +pub mod table; pub mod text; pub mod transform; pub mod visibility; diff --git a/src/ui/primitives/table.rs b/src/ui/primitives/table.rs new file mode 100644 index 0000000..60bf8a6 --- /dev/null +++ b/src/ui/primitives/table.rs @@ -0,0 +1,204 @@ +use maud::{Markup, Render, html}; + +use crate::ui::UIWidget; + +use super::{div::Div, space::ScreenValue}; + +#[allow(non_snake_case)] +pub fn Table(inner: Vec>) -> TableWidget { + let inner = Div().vanish().push_for_each(&inner, |row| { + TableRow( + Div() + .vanish() + .push_for_each(&row, |col| TableData(col.clone())), + ) + }); + + TableWidget(Box::new(inner), Vec::new(), None, None) +} + +pub struct TableWidget( + Box, + Vec, + Option>, + Option, +); + +impl TableWidget { + pub fn header(mut self, header: T) -> Self { + self.2 = Some(Box::new(header)); + self + } + + pub fn caption(mut self, caption: Caption) -> Self { + self.3 = Some(caption); + self + } + + pub fn border_collapse(mut self) -> Self { + self.1.push("border-collapse".to_string()); + self + } + + pub fn border_seperate(mut self) -> Self { + self.1.push("border-separate".to_string()); + self + } + + pub fn border_spacing(mut self, spacing: ScreenValue) -> Self { + self.1 + .push(format!("border-spacing-{}", spacing.to_value())); + self + } + + pub fn border_spacing_x(mut self, spacing: ScreenValue) -> Self { + self.1 + .push(format!("border-spacing-x-{}", spacing.to_value())); + self + } + + pub fn border_spacing_y(mut self, spacing: ScreenValue) -> Self { + self.1 + .push(format!("border-spacing-y-{}", spacing.to_value())); + self + } + + pub fn layout_fixed(mut self) -> Self { + self.1.push("table-fixed".to_string()); + self + } + + pub fn layout_auto(mut self) -> Self { + self.1.push("table-auto".to_string()); + self + } +} + +impl Render for TableWidget { + fn render(&self) -> maud::Markup { + self.render_with_class("") + } +} + +impl UIWidget for TableWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + self.1.clone() + } + + fn extended_class(&self) -> Vec { + self.base_class() + } + + fn render_with_class(&self, class: &str) -> maud::Markup { + html! { + table class=(format!("{} {class}", self.base_class().join(" "))) { + @if let Some(caption) = &self.3 { + (caption) + } + + @if let Some(header) = &self.2 { + thead { + (header) + }; + }; + + (self.0.as_ref()) + }; + } + } +} + +pub struct Caption(Box, bool); + +impl Caption { + #[allow(non_snake_case)] + pub fn Top(inner: T) -> Self { + Self(Box::new(inner), true) + } + + #[allow(non_snake_case)] + pub fn Bottom(inner: T) -> Self { + Self(Box::new(inner), false) + } +} + +impl Render for Caption { + fn render(&self) -> maud::Markup { + self.render_with_class("") + } +} + +impl UIWidget for Caption { + fn can_inherit(&self) -> bool { + false + } + + fn base_class(&self) -> Vec { + if self.1 { + vec!["caption-top".to_string()] + } else { + vec!["caption-bottom".to_string()] + } + } + + fn extended_class(&self) -> Vec { + self.base_class() + } + + fn render_with_class(&self, _: &str) -> maud::Markup { + html! { + caption class=(self.base_class().join(" ")) { + (self.0.as_ref()) + }; + } + } +} + +macro_rules! element_widget { + ($name:ident, $widget:ident, $element:ident) => { + #[allow(non_snake_case)] + pub fn $name(inner: T) -> $widget { + $widget(Box::new(inner)) + } + + pub struct $widget(Box); + + impl Render for $widget { + fn render(&self) -> Markup { + self.render_with_class("") + } + } + + impl UIWidget for $widget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + Vec::new() + } + + fn extended_class(&self) -> Vec { + let mut c = self.base_class(); + c.extend_from_slice(&self.0.extended_class()); + c + } + + fn render_with_class(&self, class: &str) -> Markup { + html! { + $element class=(class) { + (self.0.as_ref()) + } + } + } + } + }; +} + +element_widget!(TableRow, TableRowWidget, tr); +element_widget!(TableHead, TableHeadWidget, th); +element_widget!(TableData, TableDataWidget, td);