From bf72429ac55040df596e33d63db30149875129fa Mon Sep 17 00:00:00 2001 From: JMARyA Date: Fri, 17 Jan 2025 14:39:54 +0100 Subject: [PATCH] update + text --- src/lib.rs | 4 + src/ui/mod.rs | 7 +- src/ui/primitives/div.rs | 1 + src/ui/primitives/height.rs | 40 ++- src/ui/primitives/text.rs | 633 +++++++++++++++++++++++++++++++++++- src/ui/primitives/width.rs | 37 ++- 6 files changed, 703 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bfe93a5..1985369 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,10 @@ pub mod ui; // Postgres +// TODO : IDEA +// more efficient table join using WHERE ANY instead of multiple SELECTs +// map_tables(Vec, Fn(&T) -> U) -> Vec + pub static PG: OnceCell = OnceCell::const_new(); /// A macro to retrieve or initialize the `PostgreSQL` connection pool. diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 8ef0eae..280dfa5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -39,7 +39,12 @@ pub mod prelude { pub use super::primitives::shadow::Shadow; pub use super::primitives::sized::Sized; pub use super::primitives::space::{ScreenValue, SpaceBetween}; - pub use super::primitives::text::{Paragraph, Span, Text}; + pub use super::primitives::text::{ + DecorationKind, DecorationStyle, DecorationThickness, LetterSpacing, LineClamp, LineHeight, + ListStyle, NumberStyle, Paragraph, Span, Text, TextAlignment, TextContent, TextDecoration, + TextHyphens, TextOverflow, TextTransform, TextWhitespace, TextWordBreak, TextWrap, + UnderlineOffset, VerticalTextAlignment, + }; pub use super::primitives::visibility::Visibility; pub use super::primitives::width::{MaxWidth, MinWidth, Width}; pub use super::primitives::zindex::ZIndex; diff --git a/src/ui/primitives/div.rs b/src/ui/primitives/div.rs index 02e0cbc..c4a597d 100644 --- a/src/ui/primitives/div.rs +++ b/src/ui/primitives/div.rs @@ -67,6 +67,7 @@ impl DivWidget { self } + // todo : Fix weird types #[must_use] pub fn push_for_each< T: UIWidget + 'static, diff --git a/src/ui/primitives/height.rs b/src/ui/primitives/height.rs index b465d22..72bff47 100644 --- a/src/ui/primitives/height.rs +++ b/src/ui/primitives/height.rs @@ -1,24 +1,36 @@ use crate::ui::UIWidget; use maud::{Markup, Render, html}; -use super::space::ScreenValue; +use super::{ + flex::Either, + space::{Fraction, ScreenValue}, +}; #[allow(non_snake_case)] -pub fn Height(size: ScreenValue, inner: T) -> HeightWidget { +pub fn Height( + size: Either, + inner: T, +) -> HeightWidget { HeightWidget(Box::new(inner), size, 0) } #[allow(non_snake_case)] -pub fn MinHeight(size: ScreenValue, inner: T) -> HeightWidget { +pub fn MinHeight( + size: Either, + inner: T, +) -> HeightWidget { HeightWidget(Box::new(inner), size, 1) } #[allow(non_snake_case)] -pub fn MaxHeight(size: ScreenValue, inner: T) -> HeightWidget { +pub fn MaxHeight( + size: Either, + inner: T, +) -> HeightWidget { HeightWidget(Box::new(inner), size, 2) } -pub struct HeightWidget(Box, ScreenValue, u8); +pub struct HeightWidget(Box, Either, u8); impl Render for HeightWidget { fn render(&self) -> Markup { @@ -34,15 +46,27 @@ impl UIWidget for HeightWidget { fn base_class(&self) -> Vec { match self.2 { 1 => { - return vec![format!("min-h-{}", self.1.to_value())]; + return vec![format!( + "min-h-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )]; } 2 => { - return vec![format!("max-h-{}", self.1.to_value())]; + return vec![format!( + "max-h-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )]; } _ => {} } - vec![format!("h-{}", self.1.to_value())] + vec![format!( + "h-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )] } fn extended_class(&self) -> Vec { diff --git a/src/ui/primitives/text.rs b/src/ui/primitives/text.rs index 39ca05d..d57f132 100644 --- a/src/ui/primitives/text.rs +++ b/src/ui/primitives/text.rs @@ -1,6 +1,8 @@ use crate::ui::{UIWidget, color::UIColor}; use maud::{Markup, Render, html}; +use super::{Nothing, space::ScreenValue}; + #[allow(non_snake_case)] /// Text UI Widget #[must_use] @@ -13,6 +15,21 @@ pub fn Text(txt: &str) -> TextWidget { color: String::new(), style: Vec::new(), size: String::new(), + line_height: None, + overflow: None, + wrap: None, + indent: None, + transform: None, + decoration: None, + whitespace: None, + wordbreak: None, + hyphens: None, + spacing: None, + clamp: None, + pseudo: None, + align: None, + vert_align: None, + list_style: None, span: false, } } @@ -28,6 +45,21 @@ pub fn Paragraph(inner: T) -> TextWidget { txt: String::new(), style: Vec::new(), size: String::new(), + indent: None, + overflow: None, + decoration: None, + whitespace: None, + wordbreak: None, + hyphens: None, + wrap: None, + spacing: None, + transform: None, + pseudo: None, + vert_align: None, + line_height: None, + list_style: None, + clamp: None, + align: None, span: false, } } @@ -43,7 +75,22 @@ pub fn Span(txt: &str) -> TextWidget { font: String::new(), style: Vec::new(), color: String::new(), + list_style: None, size: String::new(), + indent: None, + overflow: None, + whitespace: None, + wordbreak: None, + hyphens: None, + decoration: None, + transform: None, + wrap: None, + vert_align: None, + spacing: None, + line_height: None, + clamp: None, + align: None, + pseudo: None, span: true, } } @@ -55,11 +102,44 @@ pub struct TextWidget { style: Vec, font: String, color: String, + list_style: Option, + line_height: Option, + decoration: Option, + transform: Option, + vert_align: Option, + overflow: Option, + indent: Option, + wrap: Option, + whitespace: Option, + wordbreak: Option, + hyphens: Option, size: String, span: bool, + spacing: Option, + pseudo: Option, + align: Option, + clamp: Option, } impl TextWidget { + #[must_use] + pub fn whitespace(mut self, whitespace: TextWhitespace) -> Self { + self.whitespace = Some(whitespace); + self + } + + #[must_use] + pub fn wordbreak(mut self, wordbreak: TextWordBreak) -> Self { + self.wordbreak = Some(wordbreak); + self + } + + #[must_use] + pub fn hyphen(mut self, hyphen: TextHyphens) -> Self { + self.hyphens = Some(hyphen); + self + } + // Weight #[must_use] @@ -137,12 +217,78 @@ impl TextWidget { self } + #[must_use] + pub fn wrap(mut self, wrap: TextWrap) -> Self { + self.wrap = Some(wrap); + self + } + + #[must_use] + pub fn indentation(mut self, indent: ScreenValue) -> Self { + self.indent = Some(indent); + self + } + + #[must_use] + pub fn align(mut self, alignment: TextAlignment) -> Self { + self.align = Some(alignment); + self + } + + #[must_use] + pub fn align_vertical(mut self, alignment: VerticalTextAlignment) -> Self { + self.vert_align = Some(alignment); + self + } + + #[must_use] + pub fn transform(mut self, transform: TextTransform) -> Self { + self.transform = Some(transform); + self + } + #[must_use] pub fn number_style(mut self, s: NumberStyle) -> Self { self.style.push(s.to_value().to_string()); self } + #[must_use] + pub fn overflow(mut self, overflow: TextOverflow) -> Self { + self.overflow = Some(overflow); + self + } + + #[must_use] + pub fn max_lines(mut self, l: LineClamp) -> Self { + self.clamp = Some(l); + self + } + + #[must_use] + pub fn decoration(mut self, decoration: DecorationWidget) -> Self { + self.decoration = Some(decoration); + self + } + + #[must_use] + pub fn list_style(mut self, style: ListStyle) -> Self { + self.list_style = Some(style); + self + } + + #[must_use] + pub fn content(mut self, content: TextContent) -> Self { + self.pseudo = Some(content); + self + } + + #[must_use] + pub fn line_height(mut self, height: LineHeight) -> Self { + self.line_height = Some(height); + self + } + // Sizes #[must_use] @@ -291,12 +437,48 @@ impl UIWidget for TextWidget { } fn base_class(&self) -> Vec { - vec![ + let mut ret = vec![ self.color.clone(), self.font.clone(), self.size.clone(), self.family.clone(), - ] + ]; + + macro_rules! add_option { + ($opt:ident, $ret:ident) => { + if let Some($opt) = &self.$opt { + $ret.push($opt.to_value().to_string()); + } + }; + } + + add_option!(spacing, ret); + + if let Some(indent) = &self.indent { + ret.push(format!("indent-{}", indent.to_value())); + } + + add_option!(clamp, ret); + add_option!(align, ret); + add_option!(vert_align, ret); + add_option!(list_style, ret); + add_option!(pseudo, ret); + add_option!(line_height, ret); + + if let Some(decoration) = &self.decoration { + ret.extend_from_slice(&decoration.base_class()); + } + + ret.extend_from_slice(&self.style); + + add_option!(transform, ret); + add_option!(overflow, ret); + add_option!(wrap, ret); + add_option!(whitespace, ret); + add_option!(wordbreak, ret); + add_option!(hyphens, ret); + + ret } fn extended_class(&self) -> Vec { @@ -357,3 +539,450 @@ impl NumberStyle { } } } + +pub enum LetterSpacing { + Tighter, + Tight, + Normal, + Wide, + Wider, + Widest, +} + +impl LetterSpacing { + pub const fn to_value(&self) -> &str { + match self { + LetterSpacing::Tighter => "tracking-tighter", + LetterSpacing::Tight => "tracking-tight", + LetterSpacing::Normal => "tracking-normal", + LetterSpacing::Wide => "tracking-wide", + LetterSpacing::Wider => "tracking-wider", + LetterSpacing::Widest => "tracking-widest", + } + } +} + +pub enum LineClamp { + None, + _1, + _2, + _3, + _4, + _5, + _6, +} + +impl LineClamp { + pub fn to_value(&self) -> &str { + match self { + LineClamp::None => "line-clamp-none", + LineClamp::_1 => "line-clamp-1", + LineClamp::_2 => "line-clamp-2", + LineClamp::_3 => "line-clamp-3", + LineClamp::_4 => "line-clamp-4", + LineClamp::_5 => "line-clamp-5", + LineClamp::_6 => "line-clamp-6", + } + } +} + +pub enum TextAlignment { + Left, + Center, + Right, + Justify, + Start, + End, +} + +impl TextAlignment { + pub const fn to_value(&self) -> &str { + match self { + TextAlignment::Left => "text-left", + TextAlignment::Center => "text-center", + TextAlignment::Right => "text-right", + TextAlignment::Justify => "text-justify", + TextAlignment::Start => "text-start", + TextAlignment::End => "text-end", + } + } +} + +pub enum ListStyle { + Url(String), + None, + Disc, + Decimal, +} + +impl ListStyle { + pub fn to_value(&self) -> String { + match self { + ListStyle::Url(url) => format!("list-image-[url({url})]"), + ListStyle::None => "list-none".to_string(), + ListStyle::Disc => "list-disc".to_string(), + ListStyle::Decimal => "list-decimal".to_string(), + } + } +} + +pub enum TextContent { + None, + Before(String), + After(String), +} + +impl TextContent { + pub fn to_value(&self) -> String { + match self { + TextContent::None => "content-none".to_string(), + TextContent::Before(c) => format!("before:content-['{c}']"), + TextContent::After(c) => format!("after:content-['{c}']"), + } + } +} + +pub enum LineHeight { + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + None, + Tight, + Snug, + Normal, + Relaxed, + Loose, +} + +impl LineHeight { + pub const fn to_value(&self) -> &str { + match self { + LineHeight::_3 => "leading-3", + LineHeight::_4 => "leading-4", + LineHeight::_5 => "leading-5", + LineHeight::_6 => "leading-6", + LineHeight::_7 => "leading-7", + LineHeight::_8 => "leading-8", + LineHeight::_9 => "leading-9", + LineHeight::_10 => "leading-10", + LineHeight::None => "leading-none", + LineHeight::Tight => "leading-tight", + LineHeight::Snug => "leading-snug", + LineHeight::Normal => "leading-normal", + LineHeight::Relaxed => "leading-relaxed", + LineHeight::Loose => "leading-loose", + } + } +} + +// Decoration + +#[allow(non_snake_case)] +pub fn TextDecoration(kind: DecorationKind) -> DecorationWidget { + DecorationWidget { + kind: kind.to_value().to_string(), + color: None, + style: None, + underline_offset: None, + thickness: None, + } +} + +pub struct DecorationWidget { + kind: String, + color: Option, + style: Option, + thickness: Option, + underline_offset: Option, +} + +impl DecorationWidget { + #[must_use] + pub fn thickness(mut self, thickness: DecorationThickness) -> Self { + self.thickness = Some(thickness); + self + } + + #[must_use] + pub fn style(mut self, style: DecorationStyle) -> Self { + self.style = Some(style); + self + } + + #[must_use] + pub fn color(mut self, color: C) -> Self { + self.color = Some(format!("decoration-{}", color.color_class())); + self + } + + #[must_use] + pub fn underline_offset(mut self, offset: UnderlineOffset) -> Self { + self.underline_offset = Some(offset); + self + } +} + +pub enum DecorationKind { + Underline, + Overline, + LineThrough, + NoUnderline, +} + +impl DecorationKind { + pub const fn to_value(&self) -> &str { + match self { + DecorationKind::Underline => "underline", + DecorationKind::Overline => "overline", + DecorationKind::LineThrough => "line-through", + DecorationKind::NoUnderline => "no-underline", + } + } +} + +pub enum DecorationThickness { + Auto, + FromFont, + _0, + _1, + _2, + _4, + _8, +} + +impl DecorationThickness { + pub const fn to_value(&self) -> &str { + match self { + DecorationThickness::Auto => "decoration-auto", + DecorationThickness::FromFont => "decoration-from-font", + DecorationThickness::_0 => "decoration-0", + DecorationThickness::_1 => "decoration-1", + DecorationThickness::_2 => "decoration-2", + DecorationThickness::_4 => "decoration-4", + DecorationThickness::_8 => "decoration-8", + } + } +} + +pub enum DecorationStyle { + Solid, + Double, + Dotted, + Dashed, + Wavy, +} + +impl DecorationStyle { + pub const fn to_value(&self) -> &str { + match self { + DecorationStyle::Solid => "decoration-solid", + DecorationStyle::Double => "decoration-double", + DecorationStyle::Dotted => "decoration-dotted", + DecorationStyle::Dashed => "decoration-dashed", + DecorationStyle::Wavy => "decoration-wavy", + } + } +} + +impl Render for DecorationWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for DecorationWidget { + fn can_inherit(&self) -> bool { + false + } + + fn base_class(&self) -> Vec { + let mut ret = vec![self.kind.clone()]; + + if let Some(color) = &self.color { + ret.push(color.clone()); + } + + if let Some(style) = &self.style { + ret.push(style.to_value().to_string()); + } + + if let Some(thickness) = &self.thickness { + ret.push(thickness.to_value().to_string()); + } + + if let Some(offset) = &self.underline_offset { + ret.push(offset.to_value().to_string()); + } + + ret + } + + fn extended_class(&self) -> Vec { + self.base_class() + } + + fn render_with_class(&self, _: &str) -> Markup { + Nothing() + } +} + +pub enum UnderlineOffset { + Auto, + _0, + _1, + _2, + _4, + _8, +} + +impl UnderlineOffset { + pub const fn to_value(&self) -> &str { + match self { + Self::Auto => "underline-offset-auto", + Self::_0 => "underline-offset-0", + Self::_1 => "underline-offset-1", + Self::_2 => "underline-offset-2", + Self::_4 => "underline-offset-4", + Self::_8 => "underline-offset-8", + } + } +} + +pub enum VerticalTextAlignment { + Baseline, + Top, + Middle, + Bottom, + TextTop, + TextBottom, + Sub, + Super, +} + +impl VerticalTextAlignment { + pub const fn to_value(&self) -> &str { + match self { + VerticalTextAlignment::Baseline => "align-baseline", + VerticalTextAlignment::Top => "align-top", + VerticalTextAlignment::Middle => "align-middle", + VerticalTextAlignment::Bottom => "align-bottom", + VerticalTextAlignment::TextTop => "align-text-top", + VerticalTextAlignment::TextBottom => "align-text-bottom", + VerticalTextAlignment::Sub => "align-sub", + VerticalTextAlignment::Super => "align-super", + } + } +} + +pub enum TextTransform { + Uppecase, + Lowercase, + Capitalize, + None, +} + +impl TextTransform { + pub const fn to_value(&self) -> &str { + match self { + TextTransform::Uppecase => "uppercase", + TextTransform::Lowercase => "lowercase", + TextTransform::Capitalize => "capitalize", + TextTransform::None => "normal-case", + } + } +} + +pub enum TextOverflow { + Truncate, + Ellipsis, + Clip, +} + +impl TextOverflow { + pub const fn to_value(&self) -> &str { + match self { + TextOverflow::Truncate => "truncate", + TextOverflow::Ellipsis => "text-ellipsis", + TextOverflow::Clip => "text-clip", + } + } +} + +pub enum TextWrap { + Wrap, + NoWrap, + Balance, + Pretty, +} + +impl TextWrap { + pub const fn to_value(&self) -> &str { + match self { + TextWrap::Wrap => "text-wrap", + TextWrap::NoWrap => "text-nowrap", + TextWrap::Balance => "text-balance", + TextWrap::Pretty => "text-pretty", + } + } +} + +pub enum TextWhitespace { + Normal, + NoWrap, + Pre, + PreLine, + PreWrap, + BreakSpaces, +} + +impl TextWhitespace { + pub const fn to_value(&self) -> &str { + match self { + TextWhitespace::Normal => "whitespace-normal", + TextWhitespace::NoWrap => "whitespace-nowrap", + TextWhitespace::Pre => "whitespace-pre", + TextWhitespace::PreLine => "whitespace-pre-line", + TextWhitespace::PreWrap => "whitespace-pre-wrap", + TextWhitespace::BreakSpaces => "whitespace-break-spaces", + } + } +} + +pub enum TextWordBreak { + Normal, + Words, + All, + Keep, +} + +impl TextWordBreak { + pub const fn to_value(&self) -> &str { + match self { + TextWordBreak::Normal => "break-normal", + TextWordBreak::Words => "break-words", + TextWordBreak::All => "break-all", + TextWordBreak::Keep => "break-keep", + } + } +} + +pub enum TextHyphens { + None, + Manual, + Auto, +} + +impl TextHyphens { + pub const fn to_value(&self) -> &str { + match self { + TextHyphens::None => "hyphens-none", + TextHyphens::Manual => "hyphens-manual", + TextHyphens::Auto => "hyphens-auto", + } + } +} diff --git a/src/ui/primitives/width.rs b/src/ui/primitives/width.rs index 807e0a0..b5e662e 100644 --- a/src/ui/primitives/width.rs +++ b/src/ui/primitives/width.rs @@ -1,24 +1,33 @@ use crate::ui::UIWidget; use maud::{Markup, Render, html}; -use super::space::ScreenValue; +use super::{ + flex::Either, + space::{Fraction, ScreenValue}, +}; #[allow(non_snake_case)] -pub fn Width(size: ScreenValue, inner: T) -> WidthWidget { +pub fn Width(size: Either, inner: T) -> WidthWidget { WidthWidget(Box::new(inner), size, 0) } #[allow(non_snake_case)] -pub fn MinWidth(size: ScreenValue, inner: T) -> WidthWidget { +pub fn MinWidth( + size: Either, + inner: T, +) -> WidthWidget { WidthWidget(Box::new(inner), size, 1) } #[allow(non_snake_case)] -pub fn MaxWidth(size: ScreenValue, inner: T) -> WidthWidget { +pub fn MaxWidth( + size: Either, + inner: T, +) -> WidthWidget { WidthWidget(Box::new(inner), size, 2) } -pub struct WidthWidget(Box, ScreenValue, u8); +pub struct WidthWidget(Box, Either, u8); impl Render for WidthWidget { fn render(&self) -> Markup { @@ -34,15 +43,27 @@ impl UIWidget for WidthWidget { fn base_class(&self) -> Vec { match self.2 { 1 => { - return vec![format!("min-w-{}", self.1.to_value())]; + return vec![format!( + "min-w-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )]; } 2 => { - return vec![format!("max-w-{}", self.1.to_value())]; + return vec![format!( + "max-w-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )]; } _ => {} } - vec![format!("w-{}", self.1.to_value())] + vec![format!( + "w-{}", + self.1 + .map(|x| x.to_value().to_string(), |x| x.to_value().to_string()) + )] } fn extended_class(&self) -> Vec {