Compare commits

...

11 commits

Author SHA1 Message Date
e02def6bc1
update 2025-01-21 13:40:56 +01:00
a9a8b8b951
update 2025-01-21 09:00:45 +01:00
95ceaa8231
update 2025-01-21 00:52:29 +01:00
b752d77815
add position 2025-01-20 23:09:36 +01:00
e98242addf
update modifiers 2025-01-20 22:18:21 +01:00
01e33afd93
background 2025-01-20 12:18:27 +01:00
ddd2e363c2
add more borders 2025-01-20 09:20:15 +01:00
bc27b457ea
add svg 2025-01-20 08:41:20 +01:00
35669c423c
add border 2025-01-20 06:44:06 +01:00
5d4aa21edd
update 2025-01-20 06:09:56 +01:00
f7db3333c5
add filters 2025-01-20 05:51:22 +01:00
23 changed files with 3467 additions and 57 deletions

View file

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

View file

@ -1,3 +1,5 @@
#![feature(const_vec_string_slice)]
use tokio::sync::OnceCell;
pub mod auth;

View file

@ -12,7 +12,7 @@ pub trait ColorCircle {
fn next(&self) -> Self;
}
// todo : specific colors rgb
// todo : specific colors rgb -[#50d71e]
macro_rules! color_map {
($name:ident, $id:literal) => {
@ -133,3 +133,81 @@ impl UIColor for Colors {
}
}
}
// TODO : Gradient
pub struct Gradient {
start: Box<dyn UIColor>,
middle: Option<Box<dyn UIColor>>,
end: Option<Box<dyn UIColor>>,
pos_start: Option<u8>,
pos_middle: Option<u8>,
pos_end: Option<u8>,
}
impl Gradient {
pub fn from<C: UIColor + 'static>(start: C) -> Self {
Self {
start: Box::new(start),
middle: None,
end: None,
pos_end: None,
pos_middle: None,
pos_start: None,
}
}
pub fn via<C: UIColor + 'static>(mut self, middle: C) -> Self {
self.middle = Some(Box::new(middle));
self
}
pub fn to<C: UIColor + 'static>(mut self, end: C) -> Self {
self.end = Some(Box::new(end));
self
}
pub fn step_start(mut self, percentage: u8) -> Self {
assert!(percentage <= 100, "Percentage should be under 100%");
self.pos_start = Some(percentage);
self
}
pub fn step_middle(mut self, percentage: u8) -> Self {
assert!(percentage <= 100, "Percentage should be under 100%");
self.pos_middle = Some(percentage);
self
}
pub fn step_end(mut self, percentage: u8) -> Self {
assert!(percentage <= 100, "Percentage should be under 100%");
self.pos_end = Some(percentage);
self
}
pub fn color_class(&self) -> Vec<String> {
let mut classes = vec![format!("from-{}", self.start.color_class())];
if let Some(via) = &self.middle {
classes.push(format!("via-{}", via.color_class()));
}
if let Some(end) = &self.end {
classes.push(format!("to-{}", end.color_class()));
}
if let Some(step) = &self.pos_start {
classes.push(format!("from-{step}%"));
}
if let Some(step) = &self.pos_middle {
classes.push(format!("via-{step}%"));
}
if let Some(step) = &self.pos_end {
classes.push(format!("to-{step}%"));
}
classes
}
}

View file

@ -38,9 +38,8 @@ impl UIWidget for AppBarWidget {
}
fn render_with_class(&self, _: &str) -> Markup {
Padding(Shadow::medium(Background(
Gray::_800,
Header(
Padding(Shadow::medium(
Background(Header(
Padding(
Flex(
Div()
@ -70,8 +69,9 @@ impl UIWidget for AppBarWidget {
.items_center(),
)
.x(ScreenValue::_6),
),
)))
))
.color(Gray::_800),
))
.y(ScreenValue::_2)
.render()
}

View file

@ -1,5 +1,7 @@
use maud::{PreEscaped, html};
// TODO : refactor shell
/// Represents the HTML structure of a page shell, including the head, body class, and body content.
///
/// This structure is used to construct the overall HTML structure of a page, making it easier to generate consistent HTML pages dynamically.

View file

@ -1,6 +1,5 @@
use components::Shell;
use maud::{Markup, PreEscaped, Render};
use prelude::Text;
use maud::{Markup, PreEscaped, Render, html};
// UI
@ -17,15 +16,26 @@ pub mod components;
pub mod prelude {
pub use super::color::*;
pub use super::primitives::Context;
pub use super::primitives::NoBrowserAppearance;
pub use super::primitives::Nothing;
pub use super::primitives::Side;
pub use super::primitives::Size;
pub use super::primitives::animation::{Animated, Animation, Delay, Duration, Scope, Timing};
pub use super::primitives::aspect::Aspect;
pub use super::primitives::background::Background;
pub use super::primitives::border::{
Border, BorderSide, BorderSize, BorderStyle, Outline, OutlineStyle, Ring,
};
pub use super::primitives::container::Container;
pub use super::primitives::cursor::Cursor;
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,
};
pub use super::primitives::flex::{
Direction, Flex, FlexBasis, FlexGrow, Justify, Order, Strategy, Wrap,
};
@ -35,16 +45,20 @@ pub mod prelude {
pub use super::primitives::link::Link;
pub use super::primitives::margin::Margin;
pub use super::primitives::padding::Padding;
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};
pub use super::primitives::svg::SVG;
pub use super::primitives::text::{
Code, DecorationKind, DecorationStyle, DecorationThickness, LetterSpacing, LineClamp,
LineHeight, ListStyle, NumberStyle, Paragraph, Span, Text, TextAlignment, TextContent,
TextDecoration, TextHyphens, TextOverflow, TextTransform, TextWhitespace, TextWordBreak,
TextWrap, UnderlineOffset, VerticalTextAlignment,
AccentColor, Code, DecorationKind, DecorationStyle, DecorationThickness, LetterSpacing,
LineClamp, LineHeight, ListStyle, NumberStyle, Paragraph, Span, Text, TextAlignment,
TextContent, TextCursorColor, TextDecoration, TextHyphens, TextOverflow, TextSelection,
TextTransform, TextWhitespace, TextWordBreak, TextWrap, UnderlineOffset,
VerticalTextAlignment,
};
pub use super::primitives::transform::{
RenderTransformCPU, RenderTransformGPU, Rotate, Scale, Skew, Transform, TransformOrigin,
@ -139,37 +153,37 @@ impl UIWidget for PreEscaped<String> {
impl UIWidget for String {
fn can_inherit(&self) -> bool {
Text(&self).can_inherit()
false
}
fn base_class(&self) -> Vec<String> {
Text(&self).base_class()
Vec::new()
}
fn extended_class(&self) -> Vec<String> {
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<String> {
Text(&self).base_class()
Vec::new()
}
fn extended_class(&self) -> Vec<String> {
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))
}
}

View file

@ -1,16 +1,98 @@
use maud::{Markup, Render, html};
use crate::ui::{UIWidget, color::UIColor};
use crate::ui::{
UIWidget,
color::{Gradient, UIColor},
};
#[allow(non_snake_case)]
pub fn Background<T: UIWidget + 'static, C: UIColor + 'static>(
color: C,
inner: T,
) -> BackgroundWidget {
BackgroundWidget(Box::new(inner), Box::new(color))
pub fn Background<T: UIWidget + 'static>(inner: T) -> BackgroundWidget {
BackgroundWidget(
Box::new(inner),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
)
}
pub struct BackgroundWidget(Box<dyn UIWidget>, Box<dyn UIColor>);
pub struct BackgroundWidget(
// Inner
Box<dyn UIWidget>,
// Background Color
Option<Box<dyn UIColor>>,
// Background Attachment
Option<BackgroundScrollAttachment>,
Option<BackgroundClip>,
Option<BackgroundOrigin>,
Option<BackgroundRepeat>,
Option<BackgroundSize>,
// Background Image URL
Option<String>,
// Gradient
Option<BackgroundGradient>,
Option<Gradient>,
Option<BackgroundPosition>,
);
impl BackgroundWidget {
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.1 = Some(Box::new(color));
self
}
pub fn image(mut self, url: &str) -> Self {
self.7 = Some(url.to_string());
self
}
pub fn none(mut self) -> Self {
self.8 = Some(BackgroundGradient::None);
self
}
pub fn gradient(mut self, direction: BackgroundGradient, gradient: Gradient) -> Self {
self.8 = Some(direction);
self.9 = Some(gradient);
self
}
pub fn position(mut self, position: BackgroundPosition) -> Self {
self.10 = Some(position);
self
}
pub fn scroll(mut self, attachment: BackgroundScrollAttachment) -> Self {
self.2 = Some(attachment);
self
}
pub fn clip(mut self, clip: BackgroundClip) -> Self {
self.3 = Some(clip);
self
}
pub fn origin(mut self, origin: BackgroundOrigin) -> Self {
self.4 = Some(origin);
self
}
pub fn repeat(mut self, repeat: BackgroundRepeat) -> Self {
self.5 = Some(repeat);
self
}
pub fn size(mut self, size: BackgroundSize) -> Self {
self.6 = Some(size);
self
}
}
impl Render for BackgroundWidget {
fn render(&self) -> Markup {
@ -24,7 +106,49 @@ impl UIWidget for BackgroundWidget {
}
fn base_class(&self) -> Vec<String> {
vec![format!("bg-{}", self.1.color_class())]
let mut ret = Vec::new();
if let Some(color) = &self.1 {
ret.push(format!("bg-{}", color.color_class()));
}
if let Some(attachment) = &self.2 {
ret.push(attachment.to_value().to_string());
}
if let Some(clip) = &self.3 {
ret.push(clip.to_value().to_string());
}
if let Some(origin) = &self.4 {
ret.push(origin.to_value().to_string());
}
if let Some(repeat) = &self.5 {
ret.push(repeat.to_value().to_string());
}
if let Some(size) = &self.6 {
ret.push(size.to_value().to_string());
}
if let Some(image) = &self.7 {
ret.push(format!("bg-[url('{image}')]"));
}
if let Some(gradient) = &self.8 {
ret.push(gradient.to_value().to_string());
}
if let Some(gradient) = &self.9 {
ret.extend_from_slice(&gradient.color_class());
}
if let Some(position) = &self.10 {
ret.push(position.to_value().to_string());
}
ret
}
fn extended_class(&self) -> Vec<String> {
@ -47,3 +171,151 @@ impl UIWidget for BackgroundWidget {
}
}
}
/// Controlling how a background image behaves when scrolling.
pub enum BackgroundScrollAttachment {
/// Fix the background image relative to the viewport.
Fixed,
/// Scroll the background image with the container and the viewport.
Local,
/// Scroll the background image with the viewport, but not with the container.
Scroll,
}
impl BackgroundScrollAttachment {
pub const fn to_value(&self) -> &str {
match self {
BackgroundScrollAttachment::Fixed => "bg-fixed",
BackgroundScrollAttachment::Local => "bg-local",
BackgroundScrollAttachment::Scroll => "bg-scroll",
}
}
}
pub enum BackgroundClip {
Border,
Padding,
Content,
Text,
}
impl BackgroundClip {
pub const fn to_value(&self) -> &str {
match self {
BackgroundClip::Border => "bg-clip-border",
BackgroundClip::Padding => "bg-clip-padding",
BackgroundClip::Content => "bg-clip-content",
BackgroundClip::Text => "bg-clip-text",
}
}
}
pub enum BackgroundOrigin {
Border,
Padding,
Content,
}
impl BackgroundOrigin {
pub const fn to_value(&self) -> &str {
match self {
BackgroundOrigin::Border => "bg-origin-border",
BackgroundOrigin::Padding => "bg-origin-padding",
BackgroundOrigin::Content => "bg-origin-content",
}
}
}
pub enum BackgroundRepeat {
Repeat,
NoRepeat,
RepeatX,
RepeatY,
Round,
Space,
}
impl BackgroundRepeat {
pub const fn to_value(&self) -> &str {
match self {
BackgroundRepeat::Repeat => "bg-repeat",
BackgroundRepeat::NoRepeat => "bg-no-repeat",
BackgroundRepeat::RepeatX => "bg-repeat-x",
BackgroundRepeat::RepeatY => "bg-repeat-y",
BackgroundRepeat::Round => "bg-repeat-round",
BackgroundRepeat::Space => "bg-repeat-space",
}
}
}
pub enum BackgroundSize {
Auto,
Cover,
Contain,
}
impl BackgroundSize {
pub const fn to_value(&self) -> &str {
match self {
BackgroundSize::Auto => "bg-auto",
BackgroundSize::Cover => "bg-cover",
BackgroundSize::Contain => "bg-contain",
}
}
}
pub enum BackgroundGradient {
None,
ToTop,
ToTopRight,
ToRight,
ToBottomRight,
ToBottom,
ToBottomLeft,
ToLeft,
ToTopLeft,
}
impl BackgroundGradient {
pub const fn to_value(&self) -> &str {
match self {
BackgroundGradient::None => "bg-none",
BackgroundGradient::ToTop => "bg-gradient-to-t",
BackgroundGradient::ToTopRight => "bg-gradient-to-tr",
BackgroundGradient::ToRight => "bg-gradient-to-r",
BackgroundGradient::ToBottomRight => "bg-gradient-to-br",
BackgroundGradient::ToBottom => "bg-gradient-to-b",
BackgroundGradient::ToBottomLeft => "bg-gradient-to-bl",
BackgroundGradient::ToLeft => "bg-gradient-to-l",
BackgroundGradient::ToTopLeft => "bg-gradient-to-tl",
}
}
}
pub enum BackgroundPosition {
Bottom,
Center,
Left,
LeftBottom,
LeftTop,
Right,
RightBottom,
RightTop,
Top,
}
impl BackgroundPosition {
pub const fn to_value(&self) -> &str {
match self {
BackgroundPosition::Bottom => "bg-bottom",
BackgroundPosition::Center => "bg-center",
BackgroundPosition::Left => "bg-left",
BackgroundPosition::LeftBottom => "bg-left-bottom",
BackgroundPosition::LeftTop => "bg-left-top",
BackgroundPosition::Right => "bg-right",
BackgroundPosition::RightBottom => "bg-right-bottom",
BackgroundPosition::RightTop => "bg-right-top",
BackgroundPosition::Top => "bg-top",
}
}
}

397
src/ui/primitives/border.rs Normal file
View file

@ -0,0 +1,397 @@
use maud::{Markup, Render, html};
use crate::ui::{UIWidget, color::UIColor};
pub enum BorderSize {
_0,
_2,
_4,
_8,
}
impl BorderSize {
pub const fn to_value(&self) -> &str {
match self {
BorderSize::_0 => "0",
BorderSize::_2 => "2",
BorderSize::_4 => "4",
BorderSize::_8 => "8",
}
}
}
pub enum BorderSide {
X,
Y,
Start,
End,
Top,
Right,
Bottom,
Left,
}
impl BorderSide {
pub const fn to_value(&self) -> &str {
match self {
BorderSide::X => "x",
BorderSide::Y => "y",
BorderSide::Start => "s",
BorderSide::End => "e",
BorderSide::Top => "t",
BorderSide::Right => "r",
BorderSide::Bottom => "b",
BorderSide::Left => "l",
}
}
}
pub enum BorderStyle {
Solid,
Dashed,
Dotted,
Double,
Hidden,
None,
}
impl BorderStyle {
pub const fn to_value(&self) -> &str {
match self {
BorderStyle::Solid => "border-solid",
BorderStyle::Dashed => "border-dashed",
BorderStyle::Dotted => "border-dotted",
BorderStyle::Double => "border-double",
BorderStyle::Hidden => "border-hidden",
BorderStyle::None => "border-none",
}
}
}
#[allow(non_snake_case)]
pub fn Border<T: UIWidget + 'static>(inner: T) -> BorderWidget {
BorderWidget(Box::new(inner), None, None, None, None)
}
pub struct BorderWidget(
Box<dyn UIWidget>,
Option<BorderSize>,
Option<BorderSide>,
Option<Box<dyn UIColor>>,
Option<BorderStyle>,
);
impl BorderWidget {
#[must_use]
pub fn size(mut self, size: BorderSize) -> Self {
self.1 = Some(size);
self
}
#[must_use]
pub const fn side(mut self, side: BorderSide) -> Self {
self.2 = Some(side);
self
}
#[must_use]
pub const fn style(mut self, style: BorderStyle) -> Self {
self.4 = Some(style);
self
}
#[must_use]
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.3 = Some(Box::new(color));
self
}
fn border_class(&self) -> String {
if let Some(side) = &self.2 {
if let Some(size) = &self.1 {
return format!("border-{}-{}", side.to_value(), size.to_value());
}
} else if let Some(size) = &self.1 {
return format!("border-{}", size.to_value());
}
"border".to_owned()
}
}
impl Render for BorderWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for BorderWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let mut ret = vec![self.border_class()];
if let Some(color) = &self.3 {
ret.push(format!("border-{}", color.color_class()));
}
if let Some(style) = &self.4 {
ret.push(style.to_value().to_string());
}
ret
}
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())
}
}
}
}
}
pub enum OutlineStyle {
Solid,
Dashed,
Dotted,
Double,
None,
}
impl OutlineStyle {
pub const fn to_value(&self) -> &str {
match self {
OutlineStyle::Solid => "outline",
OutlineStyle::Dashed => "outline-dashed",
OutlineStyle::Dotted => "outline-dotted",
OutlineStyle::Double => "outline-double",
OutlineStyle::None => "outline-none",
}
}
}
#[allow(non_snake_case)]
pub fn Outline<T: UIWidget + 'static>(width: u32, inner: T) -> OutlineWidget {
OutlineWidget(Box::new(inner), width, None, None, 0)
}
pub struct OutlineWidget(
Box<dyn UIWidget>,
u32,
Option<Box<dyn UIColor>>,
Option<OutlineStyle>,
u32,
);
impl OutlineWidget {
#[must_use]
pub const fn offset(mut self, offset: u32) -> Self {
self.4 = offset;
self
}
#[must_use]
pub const fn style(mut self, style: OutlineStyle) -> Self {
self.3 = Some(style);
self
}
#[must_use]
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.2 = Some(Box::new(color));
self
}
}
impl Render for OutlineWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for OutlineWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.1 {
0 => "outline-0",
1 => "outline-1",
2 => "outline-2",
4 => "outline-4",
8 => "outline-8",
_ => &format!("outline-[{}px]", self.1),
};
let mut ret = vec![class.to_string()];
if let Some(color) = &self.2 {
ret.push(format!("outline-{}", color.color_class()));
}
if let Some(style) = &self.3 {
ret.push(style.to_value().to_string());
}
ret.push(match self.4 {
0 => "outline-offset-0".to_string(),
1 => "outline-offset-1".to_string(),
2 => "outline-offset-2".to_string(),
4 => "outline-offset-4".to_string(),
8 => "outline-offset-8".to_string(),
_ => format!("outline-offset-[{}px]", self.4),
});
ret
}
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())
}
}
}
}
}
#[allow(non_snake_case)]
pub fn Ring<T: UIWidget + 'static>(width: u32, inner: T) -> RingWidget {
RingWidget(Box::new(inner), width, None, false, 0, None)
}
pub struct RingWidget(
// Inner
Box<dyn UIWidget>,
// Size
u32,
// Color
Option<Box<dyn UIColor>>,
// Inset
bool,
// Offset Width
u32,
// Offset Color
Option<Box<dyn UIColor>>,
);
impl RingWidget {
#[must_use]
pub const fn inset(mut self) -> Self {
self.3 = true;
self
}
#[must_use]
pub const fn offset_width(mut self, offset: u32) -> Self {
self.4 = offset;
self
}
#[must_use]
pub fn offset_color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.5 = Some(Box::new(color));
self
}
#[must_use]
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.2 = Some(Box::new(color));
self
}
}
impl Render for RingWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for RingWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.1 {
0 => "ring-0",
1 => "ring-1",
2 => "ring-2",
4 => "ring-4",
8 => "ring-8",
_ => &format!("ring-[{}px]", self.1),
};
let mut ret = vec![class.to_string()];
if let Some(color) = &self.2 {
ret.push(format!("ring-{}", color.color_class()));
}
if self.3 {
ret.push("ring-inset".to_string());
}
ret.push(match self.4 {
0 => "ring-offset-0".to_string(),
1 => "ring-offset-1".to_string(),
2 => "ring-offset-2".to_string(),
4 => "ring-offset-4".to_string(),
8 => "ring-offset-8".to_string(),
_ => format!("ring-offset-[{}px]", self.4),
});
if let Some(color) = &self.5 {
ret.push(format!("ring-offset-{}", color.color_class()));
}
ret
}
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

@ -122,3 +122,76 @@ impl UIWidget for CursorWidget {
}
}
}
#[allow(non_snake_case)]
pub fn TouchAction<T: UIWidget + 'static>(action: Action, inner: T) -> TouchActionWidget {
TouchActionWidget(Box::new(inner), action)
}
pub struct TouchActionWidget(Box<dyn UIWidget>, Action);
impl Render for TouchActionWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for TouchActionWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
vec![self.1.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, 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 Action {
Auto,
None,
PanX,
PanLeft,
PanRight,
PanY,
PanUp,
PanDown,
PinchZoom,
Manipulation,
}
impl Action {
pub const fn to_value(&self) -> &str {
match self {
Action::Auto => "touch-auto",
Action::None => "touch-none",
Action::PanX => "touch-pan-x",
Action::PanLeft => "touch-pan-left",
Action::PanRight => "touch-pan-right",
Action::PanY => "touch-pan-y",
Action::PanUp => "touch-pan-up",
Action::PanDown => "touch-pan-down",
Action::PinchZoom => "touch-pinch-zoom",
Action::Manipulation => "touch-manipulation",
}
}
}

View file

@ -0,0 +1,314 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
macro_rules! string_class_widget {
($name:ident) => {
pub struct $name(Box<dyn UIWidget>, 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<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())
}
}
}
}
}
};
}
macro_rules! constructor {
($name:ident, $class:literal) => {
#[allow(non_snake_case)]
pub fn $name<T: UIWidget + 'static>(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<T: UIWidget + 'static>(value: BreakValue, inner: T) -> BreakWidget {
BreakWidget(Box::new(inner), false, value)
}
#[allow(non_snake_case)]
pub fn BreakBefore<T: UIWidget + 'static>(value: BreakValue, inner: T) -> BreakWidget {
BreakWidget(Box::new(inner), true, value)
}
pub struct BreakWidget(Box<dyn UIWidget>, 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<String> {
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<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())
}
}
}
}
}
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<T: UIWidget + 'static>(value: BreakValue, inner: T) -> BreakWidget {
BreakWidget(Box::new(inner), true, value)
}
pub struct BreakInsideWidget(Box<dyn UIWidget>, 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<String> {
vec![self.1.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, 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");
}
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");
}

479
src/ui/primitives/filter.rs Normal file
View file

@ -0,0 +1,479 @@
use crate::ui::UIWidget;
use maud::{Markup, Render, html};
use super::Size;
#[allow(non_snake_case)]
pub fn Blur<T: UIWidget + 'static>(amount: Size, inner: T) -> BlurWidget {
BlurWidget(Box::new(inner), amount, false)
}
pub struct BlurWidget(Box<dyn UIWidget>, Size, bool);
impl BlurWidget {
pub fn backdrop(mut self) -> Self {
self.2 = true;
self
}
}
impl Render for BlurWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for BlurWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match &self.1 {
Size::Custom(s) => &format!(" blur-[{s}]"),
Size::None => "blur-none",
Size::Small => "blur-sm",
Size::Regular => "blur",
Size::Medium => "blur-md",
Size::Large => "blur-lg",
Size::XL => "blur-xl",
Size::_2XL => "blur-2xl",
Size::_3XL => "blur-3xl",
Size::Full => "blur-3xl",
};
if self.2 {
return vec![format!("backdrop-{class}")];
}
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())
}
}
}
}
}
macro_rules! build_value_widget {
($constr:ident, $widget:ident, $class:literal) => {
#[allow(non_snake_case)]
pub fn $constr<T: UIWidget + 'static>(value: f64, inner: T) -> $widget {
$widget(Box::new(inner), value, false)
}
pub struct $widget(Box<dyn UIWidget>, f64, bool);
impl $widget {
pub fn backdrop(mut self) -> Self {
self.2 = true;
self
}
}
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<String> {
let mut ret = $class.to_string();
ret.push_str(&format!("-[{:.2}]", self.1));
if self.2 {
return vec![format!("backdrop-{ret}")];
}
vec![ret]
}
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())
}
}
}
}
}
};
}
build_value_widget!(Brightness, BrightnessWidget, "brightness");
build_value_widget!(Contrast, ConstrastWidget, "contrast");
build_value_widget!(Saturate, SaturationWidget, "saturate");
macro_rules! build_on_off_widget {
($constr:ident, $widget:ident, $class:literal) => {
#[allow(non_snake_case)]
pub fn $constr<T: UIWidget + 'static>(inner: T) -> $widget {
$widget(Box::new(inner), true, false)
}
pub struct $widget(Box<dyn UIWidget>, bool, bool);
impl $widget {
pub fn none(mut self) -> Self {
self.1 = false;
self
}
pub fn backdrop(mut self) -> Self {
self.2 = true;
self
}
}
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<String> {
let class = if self.1 {
$class.to_string()
} else {
concat!($class, "-0").to_string()
};
if self.2 {
return vec![format!("backdrop-{class}")];
}
vec![class]
}
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())
}
}
}
}
}
};
}
build_on_off_widget!(Grayscale, GrayscaleWidget, "grayscale");
build_on_off_widget!(Invert, InvertWidget, "invert");
build_on_off_widget!(Sepia, SepiaWidget, "sepia");
#[allow(non_snake_case)]
pub fn HueRotate<T: UIWidget + 'static>(deg: u32, inner: T) -> HueRotateWidget {
HueRotateWidget(Box::new(inner), deg, false)
}
pub struct HueRotateWidget(Box<dyn UIWidget>, u32, bool);
impl HueRotateWidget {
pub fn backdrop(mut self) -> Self {
self.2 = true;
self
}
}
impl Render for HueRotateWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for HueRotateWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = format!("hue-rotate-[{:.2}deg]", self.1);
if self.2 {
return vec![format!("backdrop-{class}")];
}
vec![class]
}
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())
}
}
}
}
}
#[allow(non_snake_case)]
pub fn Opacity<T: UIWidget + 'static>(value: f64, inner: T) -> OpacityWidget {
OpacityWidget(Box::new(inner), value, false)
}
pub struct OpacityWidget(Box<dyn UIWidget>, f64, bool);
impl OpacityWidget {
pub fn backdrop(mut self) -> Self {
self.2 = true;
self
}
}
impl Render for OpacityWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for OpacityWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let class = match self.1 {
0.0 => "opacity-0",
0.05 => "opacity-5",
0.1 => "opacity-10",
0.15 => "opacity-15",
0.2 => "opacity-20",
0.25 => "opacity-25",
0.3 => "opacity-30",
0.35 => "opacity-35",
0.4 => "opacity-40",
0.45 => "opacity-45",
0.5 => "opacity-50",
0.55 => "opacity-55",
0.6 => "opacity-60",
0.65 => "opacity-65",
0.7 => "opacity-70",
0.75 => "opacity-75",
0.8 => "opacity-80",
0.85 => "opacity-85",
0.9 => "opacity-90",
0.95 => "opacity-95",
1.0 => "opacity-100",
_ => &format!("opacity-[{:.2}]", self.1),
};
if self.2 {
return vec![format!("backdrop-{class}")];
}
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())
}
}
}
}
}
pub enum BlendMode {
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
PlusDarker,
PlusLighter,
}
impl BlendMode {
pub const fn to_value(&self) -> &str {
match self {
BlendMode::Normal => "normal",
BlendMode::Multiply => "multiply",
BlendMode::Screen => "screen",
BlendMode::Overlay => "overlay",
BlendMode::Darken => "darken",
BlendMode::Lighten => "lighten",
BlendMode::ColorDodge => "color-dodge",
BlendMode::ColorBurn => "color-burn",
BlendMode::HardLight => "hard-light",
BlendMode::SoftLight => "soft-light",
BlendMode::Difference => "difference",
BlendMode::Exclusion => "exclusion",
BlendMode::Hue => "hue",
BlendMode::Saturation => "saturation",
BlendMode::Color => "color",
BlendMode::Luminosity => "luminosity",
BlendMode::PlusDarker => "plus-darker",
BlendMode::PlusLighter => "plus-lighter",
}
}
}
#[allow(non_snake_case)]
pub fn MixBlendMode<T: UIWidget + 'static>(mode: BlendMode, inner: T) -> MixBlendModeWidget {
MixBlendModeWidget(Box::new(inner), mode)
}
pub struct MixBlendModeWidget(Box<dyn UIWidget>, BlendMode);
impl Render for MixBlendModeWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for MixBlendModeWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![format!("mix-blend-{}", self.1.to_value())]
}
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())
}
}
}
}
}
#[allow(non_snake_case)]
pub fn BackgroundBlendMode<T: UIWidget + 'static>(
mode: BlendMode,
inner: T,
) -> BackgroundBlendModeWidget {
BackgroundBlendModeWidget(Box::new(inner), mode)
}
pub struct BackgroundBlendModeWidget(Box<dyn UIWidget>, BlendMode);
impl Render for BackgroundBlendModeWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for BackgroundBlendModeWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![format!("bg-blend-{}", self.1.to_value())]
}
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

@ -1,11 +1,11 @@
use crate::ui::UIWidget;
use crate::ui::{UIWidget, color::UIColor};
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)
FlexWidget(Box::new(inner), vec![], false, None)
}
pub enum Justify {
@ -19,7 +19,7 @@ pub enum Justify {
Stretch,
}
pub struct FlexWidget(Box<dyn UIWidget>, Vec<String>, bool);
pub struct FlexWidget(Box<dyn UIWidget>, Vec<String>, bool, Option<Direction>);
impl Render for FlexWidget {
fn render(&self) -> Markup {
@ -41,9 +41,65 @@ impl FlexWidget {
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<C: UIColor + 'static>(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 {
let reversed = self
.3
.as_ref()
.map(|x| match x {
Direction::Row => false,
Direction::RowReverse => true,
Direction::Column => false,
Direction::ColumnReverse => true,
})
.unwrap_or_default();
self.1.push(format!("divide-x-{}", width.to_value()));
if reversed {
self.1.push("divide-x-reverse".to_string());
}
self
}
#[must_use]
pub fn divide_y(mut self, width: DivideWidth) -> Self {
let reversed = self
.3
.as_ref()
.map(|x| match x {
Direction::Row => false,
Direction::RowReverse => true,
Direction::Column => false,
Direction::ColumnReverse => true,
})
.unwrap_or_default();
self.1.push(format!("divide-y-{}", width.to_value()));
if reversed {
self.1.push("divide-y-reverse".to_string());
}
self
}
#[must_use]
pub fn direction(mut self, direction: Direction) -> Self {
self.1.push(format!("flex-{}", direction.to_value()));
self.3 = Some(direction);
self
}
@ -53,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 {
@ -95,6 +169,26 @@ impl FlexWidget {
}
}
pub enum DivideWidth {
Custom(u64),
_0,
_2,
_4,
_8,
}
impl DivideWidth {
pub fn to_value(&self) -> String {
match self {
DivideWidth::Custom(s) => format!("[{s}px]"),
DivideWidth::_0 => "0".to_string(),
DivideWidth::_2 => "2".to_string(),
DivideWidth::_4 => "4".to_string(),
DivideWidth::_8 => "8".to_string(),
}
}
}
pub enum Direction {
Row,
RowReverse,
@ -136,6 +230,11 @@ impl UIWidget for FlexWidget {
fn base_class(&self) -> Vec<String> {
let mut res = vec!["flex".to_string()];
if let Some(direction) = &self.3 {
res.push(format!("flex-{}", direction.to_value()));
}
res.extend_from_slice(&self.1);
res
}
@ -399,3 +498,89 @@ impl UIWidget for OrderWidget {
}
}
}
pub enum DivideStyle {
Solid,
Dashed,
Dotted,
Double,
None,
}
impl DivideStyle {
pub const fn to_value(&self) -> &str {
match self {
DivideStyle::Solid => "divide-solid",
DivideStyle::Dashed => "divide-dashed",
DivideStyle::Dotted => "divide-dotted",
DivideStyle::Double => "divide-double",
DivideStyle::None => "divide-none",
}
}
}
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",
}
}
}

461
src/ui/primitives/grid.rs Normal file
View file

@ -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<T: UIWidget + 'static>(inner: T) -> GridWidget {
GridWidget(Box::new(inner), vec![], false)
}
pub struct GridWidget(Box<dyn UIWidget>, Vec<String>, 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<C: UIColor + 'static>(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<String> {
let mut res = vec!["grid".to_string()];
res.extend_from_slice(&self.1);
res
}
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.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<T: UIWidget + 'static>(inner: T) -> GridElement {
GridElement(Box::new(inner), Vec::new(), "col".to_string())
}
#[allow(non_snake_case)]
pub fn GridElementRow<T: UIWidget + 'static>(inner: T) -> GridElement {
GridElement(Box::new(inner), Vec::new(), "row".to_string())
}
pub struct GridElement(Box<dyn UIWidget>, Vec<String>, 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<String> {
let mut res = vec!["grid".to_string()];
res.extend_from_slice(&self.1);
res
}
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())
}
}
}
}
}
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<dyn UIWidget>, 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<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())
}
}
}
}
}
};
}
macro_rules! constructor {
($name:ident, $class:literal) => {
#[allow(non_snake_case)]
pub fn $name<T: UIWidget + 'static>(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");
}

103
src/ui/primitives/list.rs Normal file
View file

@ -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<Box<dyn UIWidget>>, bool);
impl ListWidget {
#[must_use]
pub fn push<T: UIWidget + 'static>(mut self, element: T) -> Self {
self.0.push(Box::new(element));
self
}
#[must_use]
pub fn push_some<T: UIWidget + 'static, X, U: Fn(X) -> T>(
mut self,
option: Option<X>,
then: U,
) -> Self {
if let Some(val) = option {
self.0.push(Box::new(then(val)));
}
self
}
#[must_use]
pub fn push_if<T: UIWidget + 'static, U: Fn() -> T>(
mut self,
condition: bool,
then: U,
) -> Self {
if condition {
self.0.push(Box::new(then()));
}
self
}
#[must_use]
pub fn push_for_each<T, X, F>(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<String> {
vec![]
}
fn extended_class(&self) -> Vec<String> {
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);
}
}
}
}
}

View file

@ -1,25 +1,33 @@
use maud::{PreEscaped, html};
use super::UIWidget;
use maud::{Markup, PreEscaped, Render, html};
pub mod animation;
pub mod aspect;
pub mod background;
pub mod border;
pub mod container;
pub mod cursor;
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;
pub mod rounded;
pub mod scroll;
pub mod shadow;
pub mod sized;
pub mod space;
pub mod svg;
pub mod table;
pub mod text;
pub mod transform;
pub mod visibility;
@ -62,6 +70,7 @@ pub fn script(script: &str) -> PreEscaped<String> {
}
pub enum Size {
Custom(String),
None,
Small,
Regular,
@ -77,6 +86,7 @@ impl Size {
#[must_use]
pub const fn to_value(&self) -> &str {
match self {
Self::Custom(str) => str.as_str(),
Self::None => "none",
Self::Small => "sm",
Self::Regular => "",
@ -130,3 +140,46 @@ impl Side {
}
}
}
#[allow(non_snake_case)]
pub fn NoBrowserAppearance<T: UIWidget + 'static>(inner: T) -> NoBrowserAppearanceWidget {
NoBrowserAppearanceWidget(Box::new(inner))
}
pub struct NoBrowserAppearanceWidget(Box<dyn UIWidget>);
impl Render for NoBrowserAppearanceWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for NoBrowserAppearanceWidget {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec!["appearance-none".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!("appearance-none {class}"))
} else {
html! {
div class=(format!("appearance-none {class}")) {
(self.0.as_ref())
}
}
}
}
}

View file

@ -0,0 +1,341 @@
use maud::{Markup, Render, html};
use crate::ui::UIWidget;
use super::Side;
#[allow(non_snake_case)]
pub fn Position<T: UIWidget + 'static>(kind: PositionKind, inner: T) -> Positioned {
Positioned {
inner: Box::new(inner),
kind,
inset: None,
inset_x: None,
inset_y: None,
start: None,
end: None,
top: None,
right: None,
bottom: None,
left: None,
}
}
pub struct Positioned {
inner: Box<dyn UIWidget>,
kind: PositionKind,
inset: Option<i64>,
inset_x: Option<i64>,
inset_y: Option<i64>,
start: Option<i64>,
end: Option<i64>,
top: Option<i64>,
right: Option<i64>,
bottom: Option<i64>,
left: Option<i64>,
}
impl Positioned {
pub fn inset(mut self, value: i64) -> Self {
self.inset = Some(value);
self
}
pub fn inset_x(mut self, value: i64) -> Self {
self.inset_x = Some(value);
self
}
pub fn inset_y(mut self, value: i64) -> Self {
self.inset_y = Some(value);
self
}
pub fn start(mut self, value: i64) -> Self {
self.start = Some(value);
self
}
pub fn end(mut self, value: i64) -> Self {
self.end = Some(value);
self
}
pub fn top(mut self, value: i64) -> Self {
self.top = Some(value);
self
}
pub fn right(mut self, value: i64) -> Self {
self.right = Some(value);
self
}
pub fn bottom(mut self, value: i64) -> Self {
self.bottom = Some(value);
self
}
pub fn left(mut self, value: i64) -> Self {
self.left = Some(value);
self
}
}
impl Render for Positioned {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for Positioned {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
let mut ret = vec![self.kind.to_value().to_string()];
if let Some(inset) = &self.inset {
if inset.is_negative() {
ret.push(format!("-inset-[{inset}px]"));
} else {
ret.push(format!("inset-[{inset}px]"));
}
}
if let Some(inset) = &self.inset_x {
if inset.is_negative() {
ret.push(format!("-inset-x-[{inset}px]"));
} else {
ret.push(format!("inset-x-[{inset}px]"));
}
}
if let Some(inset) = &self.inset_y {
if inset.is_negative() {
ret.push(format!("-inset-y-[{inset}px]"));
} else {
ret.push(format!("inset-y-[{inset}px]"));
}
}
if let Some(start) = &self.start {
if start.is_negative() {
ret.push(format!("-start-[{start}px]"));
} else {
ret.push(format!("start-[{start}px]"));
}
}
if let Some(end) = &self.end {
if end.is_negative() {
ret.push(format!("-end-[{end}px]"));
} else {
ret.push(format!("end-[{end}px]"));
}
}
if let Some(value) = &self.top {
if value.is_negative() {
ret.push(format!("-top-[{value}px]"));
} else {
ret.push(format!("top-[{value}px]"));
}
}
if let Some(value) = &self.right {
if value.is_negative() {
ret.push(format!("-right-[{value}px]"));
} else {
ret.push(format!("right-[{value}px]"));
}
}
if let Some(value) = &self.bottom {
if value.is_negative() {
ret.push(format!("-bottom-[{value}px]"));
} else {
ret.push(format!("bottom-[{value}px]"));
}
}
if let Some(value) = &self.left {
if value.is_negative() {
ret.push(format!("-left-[{value}px]"));
} else {
ret.push(format!("left-[{value}px]"));
}
}
ret
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.inner.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.inner.as_ref().can_inherit() {
self.inner
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.inner.as_ref())
}
}
}
}
}
pub enum PositionKind {
Static,
Fixed,
Absolute,
Relative,
Sticky,
}
impl PositionKind {
pub const fn to_value(&self) -> &str {
match self {
PositionKind::Static => "static",
PositionKind::Fixed => "fixed",
PositionKind::Absolute => "absolute",
PositionKind::Relative => "relative",
PositionKind::Sticky => "sticky",
}
}
}
#[allow(non_snake_case)]
pub fn ObjectPosition<T: UIWidget + 'static>(side: Side, inner: T) -> ObjectPositioned {
ObjectPositioned {
inner: Box::new(inner),
side,
}
}
pub struct ObjectPositioned {
inner: Box<dyn UIWidget>,
side: Side,
}
impl Render for ObjectPositioned {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for ObjectPositioned {
fn can_inherit(&self) -> bool {
true
}
fn base_class(&self) -> Vec<String> {
vec![
match self.side {
Side::Start => "object-top",
Side::End => "object-bottom",
Side::Top => "object-top",
Side::Right => "object-right",
Side::Bottom => "object-bottom",
Side::Left => "object-left",
Side::StartStart => "object-left-top",
Side::StartEnd => "object-right-top",
Side::EndEnd => "object-right-bottom",
Side::EndStart => "object-left-bottom",
Side::TopLeft => "object-left-top",
Side::TopRight => "object-right-top",
Side::BottomRight => "object-right-bottom",
Side::BottomLeft => "object-left-bottom",
Side::Center => "object-center",
}
.to_string(),
]
}
fn extended_class(&self) -> Vec<String> {
let mut c = self.base_class();
c.extend_from_slice(&self.inner.extended_class());
c
}
fn render_with_class(&self, class: &str) -> Markup {
if self.inner.as_ref().can_inherit() {
self.inner
.as_ref()
.render_with_class(&format!("{} {class}", self.base_class().join(" ")))
} else {
html! {
div class=(format!("{} {class}", self.base_class().join(" "))) {
(self.inner.as_ref())
}
}
}
}
}
pub enum Resize {
None,
Y,
X,
Both,
}
impl Resize {
pub const fn to_value(&self) -> &str {
match self {
Resize::None => "resize-none",
Resize::Y => "resize-y",
Resize::X => "resize-x",
Resize::Both => "resize",
}
}
}
#[allow(non_snake_case)]
pub fn Resizeable<T: UIWidget + 'static>(mode: Resize, inner: T) -> ResizeableWidget {
ResizeableWidget(Box::new(inner), mode)
}
pub struct ResizeableWidget(Box<dyn UIWidget>, Resize);
impl Render for ResizeableWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for ResizeableWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
vec![self.1.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, 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

@ -12,7 +12,7 @@ pub struct RoundedWidget(Box<dyn UIWidget>, Option<Size>, Option<Side>);
impl RoundedWidget {
#[must_use]
pub const fn size(mut self, size: Size) -> Self {
pub fn size(mut self, size: Size) -> Self {
self.1 = Some(size);
self
}

234
src/ui/primitives/scroll.rs Normal file
View file

@ -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<T: UIWidget + 'static>(inner: T) -> ScrollWidget {
ScrollWidget(Box::new(inner), true, None, None, None, None, false)
}
pub struct ScrollWidget(
Box<dyn UIWidget>,
bool,
Option<Margin>,
Option<PaddingWidget>,
Option<Overscroll>,
Option<SnapType>,
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<String> {
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::<Vec<_>>();
ret.extend_from_slice(&classes);
}
if let Some(padding) = &self.3 {
let classes = padding
.base_class()
.into_iter()
.map(|x| format!("scroll-{x}"))
.collect::<Vec<_>>();
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<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())
}
}
}
}
}
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<dyn UIWidget>, String);
impl SnapAlign {
#[allow(non_snake_case)]
pub fn Start<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "snap-start".to_string())
}
#[allow(non_snake_case)]
pub fn End<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "snap-end".to_string())
}
#[allow(non_snake_case)]
pub fn Center<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "snap-center".to_string())
}
#[allow(non_snake_case)]
pub fn None<T: UIWidget + 'static>(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<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

@ -1,35 +1,46 @@
use crate::ui::UIWidget;
use crate::ui::{UIWidget, color::UIColor};
use maud::{Markup, Render, html};
pub struct Shadow(Box<dyn UIWidget>, String);
pub struct Shadow(Box<dyn UIWidget>, String, Option<Box<dyn UIColor>>);
impl Shadow {
pub fn medium<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "md".to_owned())
}
pub fn small<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "sm".to_owned())
Self(Box::new(inner), "sm".to_owned(), None)
}
pub fn regular<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), String::new())
Self(Box::new(inner), String::new(), None)
}
pub fn medium<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "md".to_owned(), None)
}
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())
Self(Box::new(inner), "lg".to_owned(), None)
}
pub fn xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "xl".to_owned())
Self(Box::new(inner), "xl".to_owned(), None)
}
pub fn _2xl<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "2xl".to_owned())
Self(Box::new(inner), "2xl".to_owned(), None)
}
pub fn inner<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "inner".to_owned(), None)
}
pub fn none<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), "none".to_owned(), None)
}
}
impl Shadow {
pub fn color<C: UIColor + 'static>(mut self, color: C) -> Self {
self.2 = Some(Box::new(color));
self
}
}
@ -45,11 +56,17 @@ impl UIWidget for Shadow {
}
fn base_class(&self) -> Vec<String> {
if self.1.is_empty() {
let mut ret = if self.1.is_empty() {
vec!["shadow".to_string()]
} else {
vec![format!("shadow-{}", self.1)]
};
if let Some(color) = &self.2 {
ret.push(format!("shadow-{}", color.color_class()));
}
ret
}
fn extended_class(&self) -> Vec<String> {

76
src/ui/primitives/svg.rs Normal file
View file

@ -0,0 +1,76 @@
use maud::{Markup, Render, html};
use crate::ui::{UIWidget, color::UIColor};
#[allow(non_snake_case)]
pub fn SVG<T: UIWidget + 'static>(inner: T) -> SVGWidget {
SVGWidget(Box::new(inner), None, None, None)
}
pub struct SVGWidget(
Box<dyn UIWidget>,
Option<Box<dyn UIColor>>,
Option<Box<dyn UIColor>>,
Option<u32>,
);
impl SVGWidget {
pub fn fill<C: UIColor + 'static>(mut self, color: C) -> Self {
self.1 = Some(Box::new(color));
self
}
pub fn stroke<C: UIColor + 'static>(mut self, color: C) -> Self {
self.2 = Some(Box::new(color));
self
}
pub fn stroke_width(mut self, width: u32) -> Self {
self.3 = Some(width);
self
}
}
impl Render for SVGWidget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for SVGWidget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
let mut ret = vec![];
if let Some(fill) = &self.1 {
ret.push(format!("fill-{}", fill.color_class()));
}
if let Some(stroke) = &self.2 {
ret.push(format!("stroke-{}", stroke.color_class()));
}
if let Some(stroke_width) = &self.3 {
ret.push(format!("stroke-[{stroke_width}px]"));
}
ret
}
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 {
html! {
svg class=(self.base_class().join(" ")) {
(self.0.as_ref())
}
}
}
}

204
src/ui/primitives/table.rs Normal file
View file

@ -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<T: UIWidget + 'static + Clone>(inner: Vec<Vec<T>>) -> 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<dyn UIWidget>,
Vec<String>,
Option<Box<dyn UIWidget>>,
Option<Caption>,
);
impl TableWidget {
pub fn header<T: UIWidget + 'static>(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<String> {
self.1.clone()
}
fn extended_class(&self) -> Vec<String> {
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<dyn UIWidget>, bool);
impl Caption {
#[allow(non_snake_case)]
pub fn Top<T: UIWidget + 'static>(inner: T) -> Self {
Self(Box::new(inner), true)
}
#[allow(non_snake_case)]
pub fn Bottom<T: UIWidget + 'static>(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<String> {
if self.1 {
vec!["caption-top".to_string()]
} else {
vec!["caption-bottom".to_string()]
}
}
fn extended_class(&self) -> Vec<String> {
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<T: UIWidget + 'static>(inner: T) -> $widget {
$widget(Box::new(inner))
}
pub struct $widget(Box<dyn UIWidget>);
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<String> {
Vec::new()
}
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 {
html! {
$element class=(class) {
(self.0.as_ref())
}
}
}
}
};
}
element_widget!(TableRow, TableRowWidget, tr);
element_widget!(TableHead, TableHeadWidget, th);
element_widget!(TableData, TableDataWidget, td);

View file

@ -1,5 +1,5 @@
use crate::ui::{UIWidget, color::UIColor};
use maud::{Markup, PreEscaped, Render};
use maud::{Markup, PreEscaped, Render, html};
use super::{Nothing, space::ScreenValue};
@ -31,6 +31,7 @@ pub fn Text(txt: &str) -> TextWidget {
align: None,
vert_align: None,
list_style: None,
select: None,
kind: TextKind::Paragraph,
}
}
@ -68,6 +69,7 @@ pub fn Paragraph<T: UIWidget + 'static>(inner: T) -> TextWidget {
list_style: None,
clamp: None,
align: None,
select: None,
kind: TextKind::Paragraph,
}
}
@ -100,6 +102,7 @@ pub fn Span(txt: &str) -> TextWidget {
clamp: None,
align: None,
pseudo: None,
select: None,
kind: TextKind::Span,
}
}
@ -132,6 +135,7 @@ pub fn Code(txt: &str) -> TextWidget {
clamp: None,
align: None,
pseudo: None,
select: None,
kind: TextKind::Pre,
}
}
@ -160,10 +164,17 @@ pub struct TextWidget {
pseudo: Option<TextContent>,
align: Option<TextAlignment>,
clamp: Option<LineClamp>,
select: Option<TextSelection>,
title: Option<String>,
}
impl TextWidget {
#[must_use]
pub fn select(mut self, select: TextSelection) -> Self {
self.select = Some(select);
self
}
#[must_use]
pub fn whitespace(mut self, whitespace: TextWhitespace) -> Self {
self.whitespace = Some(whitespace);
@ -527,6 +538,7 @@ impl UIWidget for TextWidget {
add_option!(list_style, ret);
add_option!(pseudo, ret);
add_option!(line_height, ret);
add_option!(select, ret);
if let Some(decoration) = &self.decoration {
ret.extend_from_slice(&decoration.base_class());
@ -1061,3 +1073,73 @@ impl TextHyphens {
}
}
}
pub enum TextSelection {
None,
Text,
All,
Auto,
}
impl TextSelection {
pub const fn to_value(&self) -> &str {
match self {
TextSelection::None => "select-none",
TextSelection::Text => "select-text",
TextSelection::All => "select-all",
TextSelection::Auto => "select-auto ",
}
}
}
macro_rules! color_widget {
($constr:ident, $widget:ident, $class:literal) => {
#[allow(non_snake_case)]
pub fn $constr<T: UIWidget + 'static, C: UIColor + 'static>(color: C, inner: T) -> $widget {
$widget(Box::new(inner), Box::new(color))
}
pub struct $widget(Box<dyn UIWidget>, Box<dyn UIColor>);
impl Render for $widget {
fn render(&self) -> Markup {
self.render_with_class("")
}
}
impl UIWidget for $widget {
fn can_inherit(&self) -> bool {
false
}
fn base_class(&self) -> Vec<String> {
let mut class = $class.to_string();
class.push_str(&format!("-{}", self.1.color_class()));
vec![class]
}
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())
}
}
}
}
}
};
}
color_widget!(TextCursorColor, CaretColorWidget, "caret");
color_widget!(AccentColor, AccentColorWidget, "accent");

View file

@ -74,6 +74,29 @@ macro_rules! wrapper {
}
wrapper!(Hover, HoverWrapper, "hover");
wrapper!(DarkMode, DarkModeWrapper, "dark");
wrapper!(Active, ActiveWrapper, "active");
wrapper!(Focus, FocusWrapper, "focus");
wrapper!(First, FirstWrapper, "first");
wrapper!(Odd, OddWrapper, "odd");
wrapper!(Even, EvenWrapper, "even");
wrapper!(Required, RequiredWrapper, "required");
wrapper!(Invalid, InvalidWrapper, "invalid");
wrapper!(Disabled, DisabledWrapper, "disabled");
wrapper!(Placeholder, PlaceholderWrapper, "placeholder");
wrapper!(FileButton, FileButtonWrapper, "file");
wrapper!(Marker, MarkerWrapper, "marker");
wrapper!(Selection, SelectionWrapper, "selection");
wrapper!(FirstLine, FirstLineWrapper, "first-line");
wrapper!(FirstLetter, FirstLetterWrapper, "first-letter");
wrapper!(Portrait, PortraitWrapper, "portrait");
wrapper!(Landscape, LandscapeWrapper, "landscape");
wrapper!(Print, PrintWrapper, "print");
wrapper!(LeftToRight, LeftToRightWrapper, "ltr");
wrapper!(RightToLeft, RightToLeftWrapper, "rtl");
wrapper!(Opened, OpenWrapper, "open");
wrapper!(SmallScreen, SmallScreenWrapper, "sm");
wrapper!(MediumScreen, MediumScreenWrapper, "md");