Compare commits
11 commits
ec10e5a89d
...
e02def6bc1
Author | SHA1 | Date | |
---|---|---|---|
e02def6bc1 | |||
a9a8b8b951 | |||
95ceaa8231 | |||
b752d77815 | |||
e98242addf | |||
01e33afd93 | |||
ddd2e363c2 | |||
bc27b457ea | |||
35669c423c | |||
5d4aa21edd | |||
f7db3333c5 |
23 changed files with 3467 additions and 57 deletions
|
@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(const_vec_string_slice)]
|
||||
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub mod auth;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
397
src/ui/primitives/border.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
314
src/ui/primitives/display.rs
Normal file
314
src/ui/primitives/display.rs
Normal 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
479
src/ui/primitives/filter.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
461
src/ui/primitives/grid.rs
Normal 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
103
src/ui/primitives/list.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
341
src/ui/primitives/position.rs
Normal file
341
src/ui/primitives/position.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
234
src/ui/primitives/scroll.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
76
src/ui/primitives/svg.rs
Normal 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
204
src/ui/primitives/table.rs
Normal 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);
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue