This commit is contained in:
JMARyA 2025-01-16 18:22:52 +01:00
parent f3a85de02e
commit 86f61ff3f6
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
15 changed files with 918 additions and 41 deletions

View file

@ -15,12 +15,16 @@ pub async fn index_page(ctx: RequestContext) -> StringResponse {
h1 { "Hello World!" };
(Hover(
Padding(Text("").color(&Gray::_400)).x(ScreenValue::_10),
Link("/test", Text("Hello")).hx_get("/test").hx_get("/test").hx_trigger(
Event::on_load().delay("2s")
.and(Event::on_revealed())
Cursor::NorthEastResize.on(
Padding(Text("").color(&Gray::_400)).x(ScreenValue::_10)
)
))
).on(
Link("/test", Text("Hello")).hx_get("/test").hx_get("/test").hx_trigger(
Event::on_load().delay("2s")
.and(Event::on_revealed())
)
)
)
(content)

View file

@ -1,8 +1,11 @@
use crate::request::assets::DataResponse;
use rocket::get;
use crate::request::{StringResponse, respond_script};
#[get("/assets/htmx.min.js")]
pub fn htmx_script_route() -> StringResponse {
respond_script(include_str!("htmx.min.js").to_string())
pub fn htmx_script_route() -> DataResponse {
DataResponse::new(
include_str!("htmx.min.js").as_bytes().to_vec(),
"application/javascript".to_string(),
Some(60 * 60 * 24 * 3),
)
}

View file

@ -15,17 +15,18 @@ pub mod components;
// Preludes
pub mod prelude {
pub use super::color::*;
pub use super::primitives::Nothing;
pub use super::primitives::Context;
pub use super::primitives::Nothing;
pub use super::primitives::Side;
pub use super::primitives::Size;
pub use super::primitives::aspect::Aspect;
pub use super::primitives::background::Background;
pub use super::primitives::container::Container;
pub use super::primitives::cursor::Cursor;
pub use super::primitives::div::Div;
pub use super::primitives::flex::{Flex, Justify};
pub use super::primitives::header::Header;
pub use super::primitives::height::Height;
pub use super::primitives::height::HeightWidget;
pub use super::primitives::image::Image;
pub use super::primitives::link::Link;
pub use super::primitives::margin::Margin;
@ -36,7 +37,9 @@ pub mod prelude {
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::visibility::Visibility;
pub use super::primitives::width::Width;
pub use super::primitives::zindex::ZIndex;
pub use super::wrapper::Hover;
}

124
src/ui/primitives/cursor.rs Normal file
View file

@ -0,0 +1,124 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
pub enum Cursor {
Auto,
Default,
Pointer,
Wait,
Text,
Move,
Help,
NotAllowed,
None,
ContextMenu,
Progress,
Cell,
Crosshair,
VerticalText,
Alias,
Copy,
NoDrop,
Grab,
Grabbing,
AllScroll,
ColResize,
RowResize,
NorthResize,
EastResize,
SouthResize,
WestResize,
NorthEastResize,
NorthWestResize,
SouthEastResize,
SouthWestResize,
EastWestResize,
NorthSouthResize,
NorthEastSouthWestResize,
NorthWestSouthEastResize,
ZoomIn,
ZoomOut,
}
impl Cursor {
pub fn on<T: UIWidget + 'static>(self, inner: T) -> CursorWidget {
CursorWidget(self, Box::new(inner))
}
}
pub struct CursorWidget(Cursor, Box<dyn UIWidget>);
impl Render for CursorWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for CursorWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.0 {
Cursor::Auto => "cursor-auto",
Cursor::Default => "cursor-default",
Cursor::Pointer => "cursor-pointer",
Cursor::Wait => "cursor-wait",
Cursor::Text => "cursor-text",
Cursor::Move => "cursor-move",
Cursor::Help => "cursor-help",
Cursor::NotAllowed => "cursor-not-allowed",
Cursor::None => "cursor-none",
Cursor::ContextMenu => "cursor-context-menu",
Cursor::Progress => "cursor-progress",
Cursor::Cell => "cursor-cell",
Cursor::Crosshair => "cursor-crosshair",
Cursor::VerticalText => "cursor-vertical-text",
Cursor::Alias => "cursor-alias",
Cursor::Copy => "cursor-copy",
Cursor::NoDrop => "cursor-no-drop",
Cursor::Grab => "cursor-grab",
Cursor::Grabbing => "cursor-grabbing",
Cursor::AllScroll => "cursor-all-scroll",
Cursor::ColResize => "cursor-col-resize",
Cursor::RowResize => "cursor-row-resize",
Cursor::NorthResize => "cursor-n-resize",
Cursor::EastResize => "cursor-e-resize",
Cursor::SouthResize => "cursor-s-resize",
Cursor::WestResize => "cursor-w-resize",
Cursor::NorthEastResize => "cursor-ne-resize",
Cursor::NorthWestResize => "cursor-nw-resize",
Cursor::SouthEastResize => "cursor-se-resize",
Cursor::SouthWestResize => "cursor-sw-resize",
Cursor::EastWestResize => "cursor-ew-resize",
Cursor::NorthSouthResize => "cursor-ns-resize",
Cursor::NorthEastSouthWestResize => "cursor-nesw-resize",
Cursor::NorthWestSouthEastResize => "cursor-nwse-resize",
Cursor::ZoomIn => "cursor-zoom-in",
Cursor::ZoomOut => "cursor-zoom-out",
};
vec![class.to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.1.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
let inner = &self.1;
if inner.can_inherit() {
inner.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(inner)
}
}
}
}
}

View file

@ -1,14 +1,22 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
use super::space::{Fraction, ScreenValue};
#[allow(non_snake_case)]
pub fn Flex<T: UIWidget + 'static>(inner: T) -> FlexWidget {
FlexWidget(Box::new(inner), vec![], false)
}
pub enum Justify {
Normal,
Start,
End,
Center,
Between,
Around,
Evenly,
Stretch,
}
pub struct FlexWidget(Box<dyn UIWidget>, Vec<String>, bool);
@ -33,11 +41,29 @@ impl FlexWidget {
self
}
#[must_use]
pub fn direction(mut self, direction: Direction) -> Self {
self.1.push(format!("flex-{}", direction.to_value()));
self
}
#[must_use]
pub fn wrap(mut self, wrap: Wrap) -> Self {
self.1.push(format!("flex-{}", wrap.to_value()));
self
}
#[must_use]
pub fn justify(mut self, value: Justify) -> Self {
let class = match value {
Justify::Center => "justify-center".to_owned(),
Justify::Between => "justify-between".to_owned(),
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);
@ -51,10 +77,56 @@ impl FlexWidget {
}
#[must_use]
pub fn gap(mut self, amount: u32) -> Self {
self.1.push(format!("gap-{amount}"));
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
}
}
pub enum Direction {
Row,
RowReverse,
Column,
ColumnReverse,
}
impl Direction {
pub const fn to_value(&self) -> &str {
match self {
Direction::Row => "row",
Direction::RowReverse => "row-reverse",
Direction::Column => "col",
Direction::ColumnReverse => "col-reverse",
}
}
}
pub enum Wrap {
Wrap,
Reverse,
NoWrap,
}
impl Wrap {
pub const fn to_value(&self) -> &str {
match self {
Wrap::Wrap => "wrap",
Wrap::Reverse => "wrap-reverse",
Wrap::NoWrap => "nowrap",
}
}
}
impl UIWidget for FlexWidget {
@ -88,3 +160,242 @@ impl UIWidget for FlexWidget {
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Either<R, L> {
Right(R),
Left(L),
}
impl<R, L> Either<R, L> {
pub fn map<X, Y, U>(self, lf: X, rf: Y) -> U
where
X: FnOnce(L) -> U,
Y: FnOnce(R) -> U,
{
match self {
Either::Right(r) => rf(r),
Either::Left(l) => lf(l),
}
}
}
impl From<ScreenValue> for Either<ScreenValue, Fraction> {
fn from(value: ScreenValue) -> Self {
Self::Right(value)
}
}
impl From<Fraction> for Either<ScreenValue, Fraction> {
fn from(value: Fraction) -> Self {
Self::Left(value)
}
}
#[allow(non_snake_case)]
pub fn FlexBasis<T: UIWidget + 'static>(
inner: T,
value: Either<ScreenValue, Fraction>,
) -> FlexBasisWidget {
FlexBasisWidget(Box::new(inner), value)
}
pub struct FlexBasisWidget(Box<dyn UIWidget>, Either<ScreenValue, Fraction>);
impl Render for FlexBasisWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for FlexBasisWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
vec![format!(
"basis-{}",
self.1
.clone()
.map(|x| x.to_value().to_string(), |x| x.to_value().to_string())
)]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.0.extended_class());
c
}
fn render_with_class(&self, _: &str) -> Markup {
if self.0.as_ref().can_inherit() {
self.0
.as_ref()
.render_with_class(&format!("{}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{}", self.base_class().join(" "))) {
(self.0.as_ref())
}
}
}
}
}
#[allow(non_snake_case)]
pub fn FlexGrow<T: UIWidget + 'static>(strategy: Strategy, inner: T) -> FlexGrowWidget {
FlexGrowWidget(strategy, Box::new(inner))
}
pub struct FlexGrowWidget(Strategy, Box<dyn UIWidget>);
impl Render for FlexGrowWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for FlexGrowWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
vec![self.0.to_value().to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.1.extended_class());
c
}
fn render_with_class(&self, _: &str) -> Markup {
if self.1.as_ref().can_inherit() {
self.1
.as_ref()
.render_with_class(&format!("{}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{}", self.base_class().join(" "))) {
(self.1.as_ref())
}
}
}
}
}
pub enum Strategy {
/// Allow a flex item to shrink but not grow, taking into account its initial size.
Initial,
/// Allow a flex item to grow and shrink as needed, ignoring its initial size.
Expand,
/// Allow a flex item to grow and shrink, taking into account its initial size.
Auto,
/// Prevent a flex item from growing or shrinking.
None,
/// Allow a flex item to grow to fill any available space.
Grow,
/// Prevent a flex item from growing.
NoGrow,
/// Allow a flex item to shrink if needed.
Shrink,
/// Prevent a flex item from shrinking.
NoShrink,
}
impl Strategy {
pub fn to_value(&self) -> &str {
match self {
Self::Initial => "flex-initial",
Self::Expand => "flex-1",
Self::Auto => "flex-auto",
Self::None => "flex-none",
Self::Grow => "grow",
Self::NoGrow => "grow-0",
Self::Shrink => "shrink",
Self::NoShrink => "shrink-0",
}
}
}
pub enum Order {
_1,
_2,
_3,
_4,
_5,
_6,
_7,
_8,
_9,
_10,
_11,
_12,
First,
Last,
None,
}
impl Order {
pub fn on<T: UIWidget + 'static>(self, inner: T) -> OrderWidget {
OrderWidget(self, Box::new(inner))
}
}
pub struct OrderWidget(Order, Box<dyn UIWidget>);
impl Render for OrderWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for OrderWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.0 {
Order::_1 => "order-1",
Order::_2 => "order-2",
Order::_3 => "order-3",
Order::_4 => "order-4",
Order::_5 => "order-5",
Order::_6 => "order-6",
Order::_7 => "order-7",
Order::_8 => "order-8",
Order::_9 => "order-9",
Order::_10 => "order-10",
Order::_11 => "order-11",
Order::_12 => "order-12",
Order::First => "order-first",
Order::Last => "order-last",
Order::None => "order-none",
};
vec![class.to_string()]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.1.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
let inner = &self.1;
if inner.can_inherit() {
inner.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(inner)
}
}
}
}
}

View file

@ -4,24 +4,44 @@ use maud::{Markup, Render, html};
use super::space::ScreenValue;
#[allow(non_snake_case)]
pub fn Width<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> Height {
Height(Box::new(inner), size)
pub fn Height<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
HeightWidget(Box::new(inner), size, 0)
}
pub struct Height(Box<dyn UIWidget>, ScreenValue);
#[allow(non_snake_case)]
pub fn MinHeight<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
HeightWidget(Box::new(inner), size, 1)
}
impl Render for Height {
#[allow(non_snake_case)]
pub fn MaxHeight<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> HeightWidget {
HeightWidget(Box::new(inner), size, 2)
}
pub struct HeightWidget(Box<dyn UIWidget>, ScreenValue, u8);
impl Render for HeightWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for Height {
impl UIWidget for HeightWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
match self.2 {
1 => {
return vec![format!("min-h-{}", self.1.to_value())];
}
2 => {
return vec![format!("max-h-{}", self.1.to_value())];
}
_ => {}
}
vec![format!("h-{}", self.1.to_value())]
}

View file

@ -69,6 +69,7 @@ impl LinkWidget {
/// Enable HTMX link capabilities
#[must_use]
pub fn use_htmx(self) -> Self {
// todo : investigate htmx attrs
let url = self.1.clone();
self.hx_get(&url)
.hx_target(Selector::Query("#main_content".to_string()))

View file

@ -5,6 +5,7 @@ use super::UIWidget;
pub mod aspect;
pub mod background;
pub mod container;
pub mod cursor;
pub mod div;
pub mod flex;
pub mod header;
@ -19,7 +20,9 @@ pub mod shadow;
pub mod sized;
pub mod space;
pub mod text;
pub mod visibility;
pub mod width;
pub mod zindex;
#[allow(non_snake_case)]
#[must_use]

View file

@ -31,10 +31,6 @@ impl Shadow {
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "2xl".to_owned())
}
pub fn inner<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "inner".to_owned())
}
}
impl Render for Shadow {
@ -76,3 +72,75 @@ impl UIWidget for Shadow {
}
}
}
pub struct DropShadow(Box<dyn UIWidget>, String);
impl DropShadow {
pub fn small<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "sm".to_owned())
}
pub fn regular<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), String::new())
}
pub fn medium<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "md".to_owned())
}
pub fn large<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "lg".to_owned())
}
pub fn none<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "none".to_owned())
}
pub fn xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "xl".to_owned())
}
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "2xl".to_owned())
}
}
impl Render for DropShadow {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for DropShadow {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
if self.1.is_empty() {
vec!["drop-shadow".to_string()]
} else {
vec![format!("drop-shadow-{}", self.1)]
}
}
fn extended_class(&self) -> Vec<String> {
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())
}
}
}
}
}

View file

@ -70,6 +70,7 @@ impl UIWidget for SpaceBetweenWidget {
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub enum ScreenValue {
_0,
_0p5,
@ -165,6 +166,7 @@ impl ScreenValue {
}
}
#[derive(Debug, Clone, Copy)]
pub enum Fraction {
_1on2,
_1on3,

View file

@ -8,8 +8,10 @@ pub fn Text(txt: &str) -> TextWidget {
TextWidget {
inner: None,
txt: txt.to_string(),
family: String::new(),
font: String::new(),
color: String::new(),
style: Vec::new(),
size: String::new(),
span: false,
}
@ -21,8 +23,10 @@ pub fn Paragraph<T: UIWidget + 'static>(inner: T) -> TextWidget {
TextWidget {
inner: Some(Box::new(inner)),
font: String::new(),
family: String::new(),
color: String::new(),
txt: String::new(),
style: Vec::new(),
size: String::new(),
span: false,
}
@ -35,7 +39,9 @@ pub fn Span(txt: &str) -> TextWidget {
TextWidget {
inner: None,
txt: txt.to_string(),
family: String::new(),
font: String::new(),
style: Vec::new(),
color: String::new(),
size: String::new(),
span: true,
@ -45,6 +51,8 @@ pub fn Span(txt: &str) -> TextWidget {
pub struct TextWidget {
inner: Option<Box<dyn UIWidget>>,
txt: String,
family: String,
style: Vec<String>,
font: String,
color: String,
size: String,
@ -52,6 +60,41 @@ pub struct TextWidget {
}
impl TextWidget {
// Weight
#[must_use]
pub fn thin(mut self) -> Self {
self.font = "font-thin".to_string();
self
}
#[must_use]
pub fn extralight(mut self) -> Self {
self.font = "font-extralight".to_string();
self
}
#[must_use]
pub fn light(mut self) -> Self {
self.font = "font-light".to_string();
self
}
#[must_use]
pub fn normal(mut self) -> Self {
self.font = "font-normal".to_string();
self
}
/// Turn `Text` medium.
///
/// Adds the class `font-medium`
#[must_use]
pub fn medium(mut self) -> Self {
self.font = "font-medium".to_string();
self
}
/// Turn `Text` semibold.
///
/// Adds the class `font-semibold`
@ -70,12 +113,83 @@ impl TextWidget {
self
}
/// Turn `Text` medium.
///
/// Adds the class `font-medium`
#[must_use]
pub fn medium(mut self) -> Self {
self.font = "font-medium".to_string();
pub fn extrabold(mut self) -> Self {
self.font = "font-extrabold".to_string();
self
}
#[must_use]
pub fn weight_black(mut self) -> Self {
self.font = "font-black".to_string();
self
}
// Styles
#[must_use]
pub fn italic(mut self, apply: bool) -> Self {
if apply {
self.style.push("italic".to_string());
} else {
self.style.push("not-italic".to_string())
}
self
}
#[must_use]
pub fn number_style(mut self, s: NumberStyle) -> Self {
self.style.push(s.to_value().to_string());
self
}
// Sizes
#[must_use]
pub fn _9xl(mut self) -> Self {
self.size = "text-9xl".to_string();
self
}
#[must_use]
pub fn _8xl(mut self) -> Self {
self.size = "text-8xl".to_string();
self
}
#[must_use]
pub fn _7xl(mut self) -> Self {
self.size = "text-7xl".to_string();
self
}
#[must_use]
pub fn _6xl(mut self) -> Self {
self.size = "text-6xl".to_string();
self
}
#[must_use]
pub fn _5xl(mut self) -> Self {
self.size = "text-5xl".to_string();
self
}
#[must_use]
pub fn _4xl(mut self) -> Self {
self.size = "text-4xl".to_string();
self
}
#[must_use]
pub fn large(mut self) -> Self {
self.size = "text-lg".to_string();
self
}
#[must_use]
pub fn base_size(mut self) -> Self {
self.size = "text-base".to_string();
self
}
@ -115,6 +229,17 @@ impl TextWidget {
self
}
/// Turn `Text` size to x small.
///
/// Adds the class `text-xs`
#[must_use]
pub fn xs(mut self) -> Self {
self.size = "text-xs".to_string();
self
}
// Text Color
#[must_use]
pub fn color<T: UIColor>(mut self, color: &T) -> Self {
self.color = format!("text-{}", color.color_class());
@ -132,6 +257,26 @@ impl TextWidget {
self.color = "text-white".to_string();
self
}
// Font Family
#[must_use]
pub fn sans(mut self) -> Self {
self.family = "font-sans".to_string();
self
}
#[must_use]
pub fn serif(mut self) -> Self {
self.family = "font-serif".to_string();
self
}
#[must_use]
pub fn mono(mut self) -> Self {
self.family = "font-mono".to_string();
self
}
}
impl Render for TextWidget {
@ -146,7 +291,12 @@ impl UIWidget for TextWidget {
}
fn base_class(&self) -> Vec<String> {
vec![self.color.clone(), self.font.clone(), self.size.clone()]
vec![
self.color.clone(),
self.font.clone(),
self.size.clone(),
self.family.clone(),
]
}
fn extended_class(&self) -> Vec<String> {
@ -179,3 +329,31 @@ impl UIWidget for TextWidget {
}
}
}
pub enum NumberStyle {
Normal,
Ordinal,
SlashedZero,
OldStyle,
Lining,
Proportional,
Tabular,
DiagonalFractions,
StackedFractions,
}
impl NumberStyle {
pub const fn to_value(&self) -> &str {
match self {
NumberStyle::Normal => "normal-nums",
NumberStyle::Ordinal => "ordinal",
NumberStyle::SlashedZero => "slashed-zero",
NumberStyle::OldStyle => "oldstyle-nums",
NumberStyle::Lining => "lining-nums",
NumberStyle::Proportional => "proportional-nums",
NumberStyle::Tabular => "tabular-nums",
NumberStyle::DiagonalFractions => "diagonal-fractions",
NumberStyle::StackedFractions => "stacked-fractions",
}
}
}

View file

@ -0,0 +1,54 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
pub struct Visibility(Box<dyn UIWidget>, String);
impl Visibility {
pub fn visible<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "visible".to_string())
}
pub fn hidden<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "invisible".to_string())
}
pub fn collapsed<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "collapse".to_string())
}
}
impl Render for Visibility {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for Visibility {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![self.1.clone()]
}
fn extended_class(&self) -> Vec<String> {
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())
}
}
}
}
}

View file

@ -5,10 +5,20 @@ use super::space::ScreenValue;
#[allow(non_snake_case)]
pub fn Width<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
WidthWidget(Box::new(inner), size)
WidthWidget(Box::new(inner), size, 0)
}
pub struct WidthWidget(Box<dyn UIWidget>, ScreenValue);
#[allow(non_snake_case)]
pub fn MinWidth<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
WidthWidget(Box::new(inner), size, 1)
}
#[allow(non_snake_case)]
pub fn MaxWidth<T: UIWidget + 'static>(size: ScreenValue, inner: T) -> WidthWidget {
WidthWidget(Box::new(inner), size, 2)
}
pub struct WidthWidget(Box<dyn UIWidget>, ScreenValue, u8);
impl Render for WidthWidget {
fn render(&self) -> Markup {
@ -22,6 +32,16 @@ impl UIWidget for WidthWidget {
}
fn base_class(&self) -> Vec<String> {
match self.2 {
1 => {
return vec![format!("min-w-{}", self.1.to_value())];
}
2 => {
return vec![format!("max-w-{}", self.1.to_value())];
}
_ => {}
}
vec![format!("w-{}", self.1.to_value())]
}

View file

@ -0,0 +1,82 @@
use maud::{Markup, Render, html};
use crate::ui::UIWidget;
pub struct ZIndex(Box<dyn UIWidget>, u8);
impl Render for ZIndex {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl ZIndex {
pub fn auto<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 0)
}
pub fn zero<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 1)
}
pub fn one<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 2)
}
pub fn two<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 3)
}
pub fn three<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 4)
}
pub fn four<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 5)
}
pub fn five<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), 6)
}
}
impl UIWidget for ZIndex {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.1 {
0 => "z-auto",
1 => "z-0",
2 => "z-10",
3 => "z-20",
4 => "z-30",
5 => "z-40",
6 => "z-50",
_ => "z-auto",
};
vec![class.to_string()]
}
fn extended_class(&self) -> Vec<String> {
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())
}
}
}
}
}

View file

@ -3,11 +3,11 @@ use maud::{Markup, Render, html};
use crate::ui::UIWidget;
#[allow(non_snake_case)]
pub fn Hover<T: UIWidget + 'static, I: UIWidget + 'static>(inherit: I, inner: T) -> HoverWrapper {
HoverWrapper(Box::new(inner), Box::new(inherit))
pub fn Hover<I: UIWidget + 'static>(inherit: I) -> HoverWrapper {
HoverWrapper(None, Box::new(inherit))
}
pub struct HoverWrapper(Box<dyn UIWidget>, Box<dyn UIWidget>);
pub struct HoverWrapper(Option<Box<dyn UIWidget>>, Box<dyn UIWidget>);
impl HoverWrapper {
fn hovered_class(&self) -> Vec<String> {
@ -18,6 +18,11 @@ impl HoverWrapper {
.map(|x| format!("hover:{x}"))
.collect::<Vec<_>>()
}
pub fn on<T: UIWidget + 'static>(mut self, inner: T) -> Self {
self.0 = Some(Box::new(inner));
self
}
}
impl Render for HoverWrapper {
@ -36,20 +41,19 @@ impl UIWidget for HoverWrapper {
}
fn extended_class(&self) -> Vec<String> {
let mut ret = self.base_class();
ret.extend_from_slice(&self.0.extended_class());
ret
self.base_class()
}
fn render_with_class(&self, class: &str) -> Markup {
if self.0.as_ref().can_inherit() {
if self.0.as_ref().unwrap().can_inherit() {
self.0
.as_ref()
.unwrap()
.render_with_class(&format!("{} {class}", self.hovered_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.hovered_class().join(" "))) {
(self.0.as_ref())
(self.0.as_ref().unwrap())
}
}
}