From a9a8b8b951d9556b9bcd22bc5002b0d6c9dbb6a0 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 21 Jan 2025 09:00:45 +0100 Subject: [PATCH] update --- examples/ui.rs | 4 +- src/ui/mod.rs | 5 + src/ui/primitives/display.rs | 283 +++++++++++++++++++++++++++++++++++ src/ui/primitives/mod.rs | 2 + src/ui/primitives/scroll.rs | 234 +++++++++++++++++++++++++++++ 5 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 src/ui/primitives/display.rs create mode 100644 src/ui/primitives/scroll.rs diff --git a/examples/ui.rs b/examples/ui.rs index b2256f7..e2f92d3 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -15,8 +15,8 @@ pub async fn index_page(ctx: RequestContext) -> StringResponse { h1 { "Hello World!" }; ( - Screen::medium(Hover(Background(Red::_700, Nothing()))).on( - Background(Blue::_700, Text("HELLO!")) + Screen::medium(Hover(Background(Nothing()).color(Red::_700))).on( + Background(Text("HELLO!")).color(Blue::_700) ) ) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index af4fedf..d158a54 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -29,6 +29,10 @@ pub mod prelude { }; pub use super::primitives::container::Container; pub use super::primitives::cursor::{Action, Cursor, TouchAction}; + pub use super::primitives::display::{ + BoxDecorationBreak, BoxSizing, BreakAfter, BreakBefore, BreakInside, BreakInsideValue, + BreakValue, Clear, Display, Float, ObjectFit, Overflow, + }; pub use super::primitives::div::Div; pub use super::primitives::filter::{ Blur, Brightness, Contrast, Grayscale, HueRotate, Invert, Saturate, Sepia, @@ -45,6 +49,7 @@ pub mod prelude { pub use super::primitives::position::{Position, PositionKind, Resize, Resizeable}; pub use super::primitives::rounded::Rounded; pub use super::primitives::script; + pub use super::primitives::scroll::{Overscroll, Scroll, SnapAlign, SnapType}; pub use super::primitives::shadow::Shadow; pub use super::primitives::sized::Sized; pub use super::primitives::space::{ScreenValue, SpaceBetween}; diff --git a/src/ui/primitives/display.rs b/src/ui/primitives/display.rs new file mode 100644 index 0000000..14fddee --- /dev/null +++ b/src/ui/primitives/display.rs @@ -0,0 +1,283 @@ +use crate::ui::UIWidget; +use maud::{Markup, Render, html}; + +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()) + } + }; +} + +pub enum BreakValue { + Auto, + Avoid, + All, + AvoidPage, + Page, + Left, + Right, + Column, +} + +impl BreakValue { + pub const fn to_value(&self) -> &str { + match self { + BreakValue::Auto => "auto", + BreakValue::Avoid => "avoid", + BreakValue::All => "all", + BreakValue::AvoidPage => "break-page", + BreakValue::Page => "page", + BreakValue::Left => "left", + BreakValue::Right => "right", + BreakValue::Column => "column", + } + } +} + +#[allow(non_snake_case)] +pub fn BreakAfter(value: BreakValue, inner: T) -> BreakWidget { + BreakWidget(Box::new(inner), false, value) +} + +#[allow(non_snake_case)] +pub fn BreakBefore(value: BreakValue, inner: T) -> BreakWidget { + BreakWidget(Box::new(inner), true, value) +} + +pub struct BreakWidget(Box, bool, BreakValue); + +impl Render for BreakWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for BreakWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + if self.1 { + vec![format!("break-before-{}", self.2.to_value())] + } else { + vec![format!("break-after-{}", self.2.to_value())] + } + } + + 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 BreakInsideValue { + Auto, + Avoid, + AvoidPage, + AvoidColumn, +} + +impl BreakInsideValue { + pub const fn to_value(&self) -> &str { + match self { + BreakInsideValue::Auto => "break-inside-auto", + BreakInsideValue::Avoid => "break-inside-avoid", + BreakInsideValue::AvoidPage => "break-inside-avoid-page", + BreakInsideValue::AvoidColumn => "break-inside-avoid-column", + } + } +} + +#[allow(non_snake_case)] +pub fn BreakInside(value: BreakValue, inner: T) -> BreakWidget { + BreakWidget(Box::new(inner), true, value) +} + +pub struct BreakInsideWidget(Box, BreakValue); + +impl Render for BreakInsideWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for BreakInsideWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + vec![self.1.to_value().to_string()] + } + + 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()) + } + } + } + } +} + +string_class_widget!(BoxDecorationBreak); + +impl BoxDecorationBreak { + constructor!(Clone, "box-decoration-clone"); + constructor!(Slice, "box-decoration-slice"); +} + +string_class_widget!(BoxSizing); + +impl BoxSizing { + constructor!(Border, "box-border"); + constructor!(Content, "box-content"); +} + +string_class_widget!(Display); + +impl Display { + constructor!(Block, "block"); + constructor!(InlineBlock, "inline-block"); + constructor!(Inline, "inline"); + constructor!(Flex, "flex"); + constructor!(InlineFlex, "inline-flex"); + constructor!(Table, "table"); + constructor!(InlineTable, "inline-table"); + constructor!(TableCaption, "table-caption"); + constructor!(TableCell, "table-cell"); + constructor!(TableColumn, "table-column"); + constructor!(TableColumnGroup, "table-column-group"); + constructor!(TableFooterGroup, "table-footer-group"); + constructor!(TableHeaderGroup, "table-header-group"); + constructor!(TableRowGroup, "table-row-group"); + constructor!(TableRow, "table-row"); + constructor!(FlowRoot, "flow-root"); + constructor!(Grid, "grid"); + constructor!(InlineGrid, "inline-grid"); + constructor!(Contents, "contents"); + constructor!(ListItem, "list-item"); + constructor!(Hidden, "hidden"); +} + +string_class_widget!(Float); + +impl Float { + constructor!(Start, "float-start"); + constructor!(End, "float-end"); + constructor!(Left, "float-left"); + constructor!(Right, "float-right"); + constructor!(None, "float-none"); +} + +string_class_widget!(Clear); + +impl Clear { + constructor!(Start, "clear-start"); + constructor!(End, "clear-end"); + constructor!(Left, "clear-left"); + constructor!(Right, "clear-right"); + constructor!(Both, "clear-both"); + constructor!(None, "clear-none"); +} + +string_class_widget!(ObjectFit); + +impl ObjectFit { + constructor!(Contain, "object-contain"); + constructor!(Cover, "object-cover"); + constructor!(Fill, "object-fill"); + constructor!(None, "object-none"); + constructor!(ScaleDown, "object-scale-down"); +} + +string_class_widget!(Overflow); + +impl Overflow { + constructor!(Auto, "overflow-auto"); + constructor!(Hidden, "overflow-hidden"); + constructor!(Clip, "overflow-clip"); + constructor!(Visible, "overflow-visible"); + constructor!(Scroll, "overflow-scroll"); + constructor!(XAuto, "overflow-x-auto"); + constructor!(YAuto, "overflow-y-auto"); + constructor!(XHidden, "overflow-x-hidden"); + constructor!(YHidden, "overflow-y-hidden"); + constructor!(XClip, "overflow-x-clip"); + constructor!(YClip, "overflow-y-clip"); + constructor!(XVisible, "overflow-x-visible"); + constructor!(YVisible, "overflow-y-visible"); + constructor!(XScroll, "overflow-x-scroll"); + constructor!(YScroll, "overflow-y-scroll"); +} diff --git a/src/ui/primitives/mod.rs b/src/ui/primitives/mod.rs index 89bdd20..3234ef4 100644 --- a/src/ui/primitives/mod.rs +++ b/src/ui/primitives/mod.rs @@ -7,6 +7,7 @@ pub mod background; pub mod border; pub mod container; pub mod cursor; +pub mod display; pub mod div; pub mod filter; pub mod flex; @@ -19,6 +20,7 @@ pub mod margin; pub mod padding; pub mod position; pub mod rounded; +pub mod scroll; pub mod shadow; pub mod sized; pub mod space; diff --git a/src/ui/primitives/scroll.rs b/src/ui/primitives/scroll.rs new file mode 100644 index 0000000..720ffe2 --- /dev/null +++ b/src/ui/primitives/scroll.rs @@ -0,0 +1,234 @@ +use super::{margin::Margin, padding::PaddingWidget}; +use crate::ui::UIWidget; +use maud::{Markup, Render, html}; + +#[allow(non_snake_case)] +pub fn Scroll(inner: T) -> ScrollWidget { + ScrollWidget(Box::new(inner), true, None, None, None, None, false) +} + +pub struct ScrollWidget( + Box, + bool, + Option, + Option, + Option, + Option, + bool, +); + +impl ScrollWidget { + pub fn smooth(mut self, value: bool) -> Self { + self.1 = value; + self + } + + pub fn scroll_margin(mut self, margin: Margin) -> Self { + self.2 = Some(margin); + self + } + + pub fn scroll_padding(mut self, padding: PaddingWidget) -> Self { + self.3 = Some(padding); + self + } + + pub fn overscroll(mut self, behaviour: Overscroll) -> Self { + self.4 = Some(behaviour); + self + } + + pub fn snap(mut self, kind: SnapType) -> Self { + self.5 = Some(kind); + self + } + + pub fn skip_snap(mut self) -> Self { + self.6 = true; + self + } +} + +impl Render for ScrollWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for ScrollWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + let mut ret = Vec::new(); + + if self.1 { + ret.push("scroll-smooth".to_string()); + } + + if let Some(margin) = &self.2 { + let classes = margin + .base_class() + .into_iter() + .map(|x| format!("scroll-{x}")) + .collect::>(); + ret.extend_from_slice(&classes); + } + + if let Some(padding) = &self.3 { + let classes = padding + .base_class() + .into_iter() + .map(|x| format!("scroll-{x}")) + .collect::>(); + ret.extend_from_slice(&classes); + } + + if let Some(overscroll) = &self.4 { + ret.push(overscroll.to_value().to_string()); + } + + if let Some(snap) = &self.5 { + ret.push(snap.to_value().to_string()); + } + + if self.6 { + ret.push("snap-normal".to_string()); + } else { + ret.push("snap-always".to_string()); + } + + ret + } + + 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 Overscroll { + Auto, + Contain, + None, + YAuto, + YContain, + YNone, + XAuto, + XContain, + XNone, +} + +impl Overscroll { + pub const fn to_value(&self) -> &str { + match self { + Overscroll::Auto => "overscroll-auto", + Overscroll::Contain => "overscroll-contain", + Overscroll::None => "overscroll-none", + Overscroll::YAuto => "overscroll-y-auto", + Overscroll::YContain => "overscroll-y-contain", + Overscroll::YNone => "overscroll-y-none", + Overscroll::XAuto => "overscroll-x-auto", + Overscroll::XContain => "overscroll-x-contain", + Overscroll::XNone => "overscroll-x-none", + } + } +} + +pub enum SnapType { + None, + X, + Y, + Both, + Mandatory, + Proximity, +} + +impl SnapType { + pub const fn to_value(&self) -> &str { + match self { + SnapType::None => "snap-none", + SnapType::X => "snap-x", + SnapType::Y => "snap-y", + SnapType::Both => "snap-both", + SnapType::Mandatory => "snap-mandatory", + SnapType::Proximity => "snap-proximity", + } + } +} + +pub struct SnapAlign(Box, String); + +impl SnapAlign { + #[allow(non_snake_case)] + pub fn Start(inner: T) -> Self { + Self(Box::new(inner), "snap-start".to_string()) + } + + #[allow(non_snake_case)] + pub fn End(inner: T) -> Self { + Self(Box::new(inner), "snap-end".to_string()) + } + + #[allow(non_snake_case)] + pub fn Center(inner: T) -> Self { + Self(Box::new(inner), "snap-center".to_string()) + } + + #[allow(non_snake_case)] + pub fn None(inner: T) -> Self { + Self(Box::new(inner), "snap-align-none".to_string()) + } +} + +impl Render for SnapAlign { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for SnapAlign { + 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()) + } + } + } + } +}