update + text

This commit is contained in:
JMARyA 2025-01-17 14:39:54 +01:00
parent 340f014365
commit bf72429ac5
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
6 changed files with 703 additions and 19 deletions

View file

@ -12,6 +12,10 @@ pub mod ui;
// Postgres
// TODO : IDEA
// more efficient table join using WHERE ANY instead of multiple SELECTs
// map_tables(Vec<T>, Fn(&T) -> U) -> Vec<U>
pub static PG: OnceCell<sqlx::PgPool> = OnceCell::const_new();
/// A macro to retrieve or initialize the `PostgreSQL` connection pool.

View file

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

View file

@ -67,6 +67,7 @@ impl DivWidget {
self
}
// todo : Fix weird types
#[must_use]
pub fn push_for_each<
T: UIWidget + 'static,

View file

@ -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<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
pub fn Height<T: UIWidget + 'static>(
size: Either<ScreenValue, Fraction>,
inner: T,
) -> HeightWidget {
HeightWidget(Box::new(inner), size, 0)
}
#[allow(non_snake_case)]
pub fn MinHeight<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
pub fn MinHeight<T: UIWidget + 'static>(
size: Either<ScreenValue, Fraction>,
inner: T,
) -> HeightWidget {
HeightWidget(Box::new(inner), size, 1)
}
#[allow(non_snake_case)]
pub fn MaxHeight<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
pub fn MaxHeight<T: UIWidget + 'static>(
size: Either<ScreenValue, Fraction>,
inner: T,
) -> HeightWidget {
HeightWidget(Box::new(inner), size, 2)
}
pub struct HeightWidget(Box<dyn UIWidget>, ScreenValue, u8);
pub struct HeightWidget(Box<dyn UIWidget>, Either<ScreenValue, Fraction>, u8);
impl Render for HeightWidget {
fn render(&self) -> Markup {
@ -34,15 +46,27 @@ impl UIWidget for HeightWidget {
fn base_class(&self) -> Vec<String> {
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<String> {

View file

@ -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<T: UIWidget + 'static>(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<String>,
font: String,
color: String,
list_style: Option<ListStyle>,
line_height: Option<LineHeight>,
decoration: Option<DecorationWidget>,
transform: Option<TextTransform>,
vert_align: Option<VerticalTextAlignment>,
overflow: Option<TextOverflow>,
indent: Option<ScreenValue>,
wrap: Option<TextWrap>,
whitespace: Option<TextWhitespace>,
wordbreak: Option<TextWordBreak>,
hyphens: Option<TextHyphens>,
size: String,
span: bool,
spacing: Option<LetterSpacing>,
pseudo: Option<TextContent>,
align: Option<TextAlignment>,
clamp: Option<LineClamp>,
}
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<String> {
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<String> {
@ -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<T: UIWidget + 'static>(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<String>,
style: Option<DecorationStyle>,
thickness: Option<DecorationThickness>,
underline_offset: Option<UnderlineOffset>,
}
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<C: UIColor>(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<String> {
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<String> {
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",
}
}
}

View file

@ -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<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
pub fn Width<T: UIWidget + 'static>(size: Either<ScreenValue, Fraction>, inner: T) -> WidthWidget {
WidthWidget(Box::new(inner), size, 0)
}
#[allow(non_snake_case)]
pub fn MinWidth<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
pub fn MinWidth<T: UIWidget + 'static>(
size: Either<ScreenValue, Fraction>,
inner: T,
) -> WidthWidget {
WidthWidget(Box::new(inner), size, 1)
}
#[allow(non_snake_case)]
pub fn MaxWidth<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
pub fn MaxWidth<T: UIWidget + 'static>(
size: Either<ScreenValue, Fraction>,
inner: T,
) -> WidthWidget {
WidthWidget(Box::new(inner), size, 2)
}
pub struct WidthWidget(Box<dyn UIWidget>, ScreenValue, u8);
pub struct WidthWidget(Box<dyn UIWidget>, Either<ScreenValue, Fraction>, u8);
impl Render for WidthWidget {
fn render(&self) -> Markup {
@ -34,15 +43,27 @@ impl UIWidget for WidthWidget {
fn base_class(&self) -> Vec<String> {
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<String> {