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