css-style 0.14.1

Typed CSS Style
Documentation
use crate::{color::Color, unit::*, Style, StyleUpdater};
use derive_rich::Rich;
use std::{borrow::Cow, fmt};

/// ```
/// use css_style::{prelude::*, color, unit::{em, px}, text::{TextAlign, TextTransform}};
/// use palette::rgb::Rgb;
///
/// style()
///     .and_text(|conf| {
///         conf.line_height(1.7)
///             // rgb value f32 based, we can pass u32 hex value too e.g. 0xFFFFFFFF
///             .color((0.5, 0.1, 0.1))
///             // or we can use HTML colors
///             .color(color::named::BLUEVIOLET)
///             .align(TextAlign::Center)
///             .transform(TextTransform::Capitalize)
///             .indent(em(2.))
///             // for single text shadow
///             .and_shadow(|conf| {
///                 conf.x(px(3))
///                     .y(px(4))
///                     .color(color::named::BLUE)
///                     .blur(px(2))
///             })
///             // for multiple text shadows
///             .and_shadow(|conf| {
///                 conf.push(|conf| conf.x(px(2))).y(px(-4))
///                     .push(|conf| conf.x(px(9)))
///             })
///     });
/// ```
#[derive(Rich, Clone, Debug, PartialEq, Default)]
pub struct Text {
    #[rich(write, write(option))]
    pub color: Option<Color>,
    #[rich(write, write(option))]
    pub direction: Option<Direction>,
    #[rich(write, write(option))]
    pub letter_spacing: Option<LetterSpacing>,
    #[rich(write, write(option))]
    pub word_spacing: Option<WordSpacing>,
    #[rich(write, write(option))]
    pub line_height: Option<LineHeight>,
    #[rich(write, write(option))]
    pub align: Option<TextAlign>,
    #[rich(write, write(option))]
    pub align_last: Option<TextAlignLast>,
    #[rich(write, write(option))]
    pub justify: Option<TextJustify>,
    #[rich(write, write(option), write(style = compose))]
    pub shadow: Option<TextShadow>,
    #[rich(write, write(option))]
    pub indent: Option<TextIndent>,
    #[rich(write, write(option), write(style = compose))]
    pub decoration: Option<TextDecoration>,
    #[rich(write, write(option))]
    pub white_space: Option<WhiteSpace>,
    #[rich(write, write(option))]
    pub unicode_bidi: Option<UnicodeBidi>,
    #[rich(write, write(option))]
    pub transform: Option<TextTransform>,
    #[rich(write, write(option))]
    pub overflow: Option<TextOverflow>,
    #[rich(write, write(option))]
    // FIXME: this doesn't belong to text properties
    pub vertical_align: Option<VerticalAlign>,
    #[rich(write, write(option))]
    pub writing_mode: Option<WritingMode>,
    #[rich(write, write(option))]
    pub word_wrap: Option<WordWrap>,
    #[rich(write, write(option))]
    pub word_break: Option<WordBreak>,
}

impl StyleUpdater for Text {
    fn update_style(self, style: Style) -> Style {
        style
            .try_insert("color", self.color)
            .try_insert("direction", self.direction)
            .try_insert("letter-spacing", self.letter_spacing)
            .try_insert("word-spacing", self.word_spacing)
            .try_insert("line-height", self.line_height)
            .try_insert("text-align", self.align)
            .try_insert("text-align-last", self.align_last)
            .try_insert("text-justify", self.justify)
            .try_merge(self.shadow)
            .try_insert("text-indent", self.indent)
            .try_insert("text-decoration", self.decoration)
            .try_insert("white-space", self.white_space)
            .try_insert("unicode-bidi", self.unicode_bidi)
            .try_insert("text-transform", self.transform)
            .try_insert("text-overflow", self.overflow.clone())
            .try_insert("vertical-align", self.vertical_align)
            .try_insert("writing-mode", self.writing_mode)
            .try_insert("word-wrap", self.word_wrap)
            .try_insert("word-break", self.word_break)
    }
}

impl<T: Into<Color>> From<T> for Text {
    fn from(source: T) -> Self {
        Self::default().color(source.into())
    }
}

#[derive(Clone, Debug, PartialEq, From, Display)]
pub enum TextShadow {
    One(Shadow),
    #[display(
        fmt = "{}",
        "_0.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(\", \")"
    )]
    Multiple(Vec<Shadow>),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
    #[display(fmt = "none")]
    None,
    #[display(fmt = "unset")]
    Unset,
}

impl StyleUpdater for TextShadow {
    fn update_style(self, style: Style) -> Style {
        style.insert("text-shadow", self)
    }
}

impl Default for TextShadow {
    fn default() -> Self {
        TextShadow::Multiple(vec![])
    }
}

impl TextShadow {
    fn shadow(mut self, conf: impl FnOnce(Shadow) -> Shadow) -> Self {
        self = match self {
            Self::One(shadow) => Self::One(conf(shadow)),
            Self::Multiple(shadows) => {
                Self::One(conf(shadows.into_iter().next().unwrap_or_default()))
            }
            _ => Self::One(conf(Shadow::default())),
        };
        self
    }

    pub fn new() -> Self {
        TextShadow::default()
    }

    pub fn x(self, val: impl Into<Length>) -> Self {
        self.shadow(|sh| sh.x(val))
    }

    pub fn y(self, val: impl Into<Length>) -> Self {
        self.shadow(|sh| sh.y(val))
    }

    pub fn blur(self, val: impl Into<Length>) -> Self {
        self.shadow(|sh| sh.blur(val))
    }

    pub fn try_blur(self, val: Option<impl Into<Length>>) -> Self {
        self.shadow(|sh| sh.try_blur(val))
    }

    pub fn color(self, val: impl Into<Color>) -> Self {
        self.shadow(|sh| sh.color(val))
    }

    pub fn try_color(self, val: Option<impl Into<Color>>) -> Self {
        self.shadow(|sh| sh.try_color(val))
    }

    pub fn push(mut self, get_val: impl FnOnce(Shadow) -> Shadow) -> Self {
        let val = get_val(Shadow::default());
        self = match self {
            Self::Multiple(mut vec) => {
                vec.push(val);
                Self::Multiple(vec)
            }
            _ => Self::Multiple(vec![val]),
        };
        self
    }
}

#[derive(Rich, Clone, Debug, PartialEq)]
pub struct Shadow {
    #[rich(write)]
    x: Length,
    #[rich(write)]
    y: Length,
    #[rich(write, write(option))]
    blur: Option<Length>,
    #[rich(write, write(option))]
    color: Option<Color>,
}

impl fmt::Display for Shadow {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", self.x, self.y)?;
        if let Some(blur) = self.blur.as_ref() {
            write!(f, " {}", blur)?;
        }
        if let Some(color) = self.color.as_ref() {
            write!(f, " {}", color)?;
        }
        Ok(())
    }
}

impl Default for Shadow {
    fn default() -> Self {
        Self {
            x: px(0),
            y: px(0),
            blur: None,
            color: None,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum Direction {
    #[display(fmt = "ltr")]
    Ltr,
    #[display(fmt = "rtl")]
    Rtl,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum Spacing {
    #[display(fmt = "normal")]
    Normal,
    Length(Length),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

pub type LetterSpacing = Spacing;
pub type WordSpacing = Spacing;

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum LineHeight {
    #[display(fmt = "normal")]
    Normal,
    Number(f32),
    Length(Length),
    Percent(Percent),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextAlign {
    #[display(fmt = "start")]
    Start,
    #[display(fmt = "end")]
    End,
    #[display(fmt = "left")]
    Left,
    #[display(fmt = "right")]
    Right,
    #[display(fmt = "center")]
    Center,
    #[display(fmt = "justify")]
    Justify,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

fn display_helper(value: &Option<impl ToString>) -> String {
    value
        .as_ref()
        .map(|v| v.to_string() + " ")
        .unwrap_or_else(|| "".into())
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextDecoration {
    #[display(
        fmt = "{}{}{}",
        "display_helper(line)",
        "display_helper(color)",
        "display_helper(style).trim()"
    )]
    Decoration {
        // TODO: add support for multiple unique values
        line: Option<TextDecorationLine>,
        color: Option<TextDecorationColor>,
        style: Option<TextDecorationStyle>,
    },
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

impl Default for TextDecoration {
    fn default() -> Self {
        TextDecoration::Initial
    }
}

impl TextDecoration {
    pub fn line(mut self, value: impl Into<TextDecorationLine>) -> Self {
        match self {
            Self::Decoration { ref mut line, .. } => *line = Some(value.into()),
            _ => {
                self = Self::Decoration {
                    line: Some(value.into()),
                    color: None,
                    style: None,
                }
            }
        };
        self
    }

    pub fn line_none(self) -> Self {
        self.line(TextDecorationLine::None)
    }

    pub fn line_underline(self) -> Self {
        self.line(TextDecorationLine::Underline)
    }

    pub fn line_overline(self) -> Self {
        self.line(TextDecorationLine::Overline)
    }

    pub fn line_line_through(self) -> Self {
        self.line(TextDecorationLine::LineThrough)
    }

    pub fn color(mut self, value: impl Into<TextDecorationColor>) -> Self {
        match self {
            Self::Decoration { ref mut color, .. } => *color = Some(value.into()),
            _ => {
                self = Self::Decoration {
                    line: Some(TextDecorationLine::None),
                    color: Some(value.into()),
                    style: None,
                }
            }
        };
        self
    }

    pub fn style(mut self, value: impl Into<TextDecorationStyle>) -> Self {
        match self {
            Self::Decoration { ref mut style, .. } => *style = Some(value.into()),
            _ => {
                self = Self::Decoration {
                    line: Some(TextDecorationLine::None),
                    color: None,
                    style: Some(value.into()),
                }
            }
        };
        self
    }

    pub fn style_solid(self) -> Self {
        self.style(TextDecorationStyle::Solid)
    }

    pub fn style_dashed(self) -> Self {
        self.style(TextDecorationStyle::Dashed)
    }

    // TODO: add shortcute functions none(), solid() ..etc
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextDecorationLine {
    #[display(fmt = "none")]
    None,
    #[display(fmt = "underline")]
    Underline,
    #[display(fmt = "overline")]
    Overline,
    #[display(fmt = "line-through")]
    LineThrough,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextDecorationColor {
    Color(Color),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextDecorationStyle {
    #[display(fmt = "solid")]
    Solid,
    #[display(fmt = "double")]
    Double,
    #[display(fmt = "dotted")]
    Dotted,
    #[display(fmt = "dashed")]
    Dashed,
    #[display(fmt = "wavy")]
    Wavy,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum TextIndent {
    Length(Length),
    Percent(Percent),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextTransform {
    #[display(fmt = "none")]
    None,
    #[display(fmt = "capitalize")]
    Capitalize,
    #[display(fmt = "uppercase")]
    Uppercase,
    #[display(fmt = "lowercase")]
    Lowercase,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum TextOverflow {
    #[display(fmt = "clip")]
    Clip,
    #[display(fmt = "ellipsis")]
    Ellipsis,
    String(Cow<'static, str>),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum UnicodeBidi {
    #[display(fmt = "normal")]
    Normal,
    #[display(fmt = "embed")]
    Embed,
    #[display(fmt = "bidi-override")]
    BidiOverride,
    #[display(fmt = "isolate")]
    Isolate,
    #[display(fmt = "isolate-override")]
    IsolateOverride,
    #[display(fmt = "plaintext")]
    Plaintext,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Debug, PartialEq, Display, From)]
pub enum VerticalAlign {
    #[display(fmt = "baseline")]
    Baseline,
    #[display(fmt = "sub")]
    Sub,
    #[display(fmt = "super")]
    Super,
    #[display(fmt = "top")]
    Top,
    #[display(fmt = "text-top")]
    TextTop,
    #[display(fmt = "middle")]
    Middle,
    #[display(fmt = "bottom")]
    Bottom,
    #[display(fmt = "text-bottom")]
    TextBottom,
    Length(Length),
    Percent(Percent),
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum WhiteSpace {
    #[display(fmt = "normal")]
    Normal,
    #[display(fmt = "nowrap")]
    Nowrap,
    #[display(fmt = "pre")]
    Pre,
    #[display(fmt = "pre-line")]
    PreLine,
    #[display(fmt = "pre-wrap")]
    PreWrap,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextAlignLast {
    #[display(fmt = "auto")]
    Auto,
    #[display(fmt = "left")]
    Left,
    #[display(fmt = "right")]
    Right,
    #[display(fmt = "center")]
    Center,
    #[display(fmt = "justify")]
    Justify,
    #[display(fmt = "start")]
    Start,
    #[display(fmt = "end")]
    End,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum TextJustify {
    #[display(fmt = "auto")]
    Auto,
    #[display(fmt = "inter-word")]
    InterWord,
    #[display(fmt = "inter-character")]
    InterCharacter,
    #[display(fmt = "none")]
    None,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum WordBreak {
    #[display(fmt = "normal")]
    Normal,
    #[display(fmt = "break-all")]
    BreakAll,
    #[display(fmt = "keep-all")]
    KeepAll,
    #[display(fmt = "break-word")]
    BreakWord,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum WordWrap {
    #[display(fmt = "normal")]
    Normal,
    #[display(fmt = "break-word")]
    BreakWord,
    #[display(fmt = "initial")]
    Initial,
    #[display(fmt = "inherit")]
    Inherit,
}

#[derive(Clone, Copy, Debug, PartialEq, Display, From)]
pub enum WritingMode {
    #[display(fmt = "horizontal-tb")]
    HorizontalTb,
    #[display(fmt = "vertical-rl")]
    VerticalRl,
    #[display(fmt = "vertical-lr")]
    VerticalLr,
}