iced_web/
css.rs

1//! Style your widgets.
2use crate::{bumpalo, Align, Background, Color, Length};
3
4use std::collections::BTreeMap;
5
6/// A CSS rule of a VDOM node.
7#[derive(Debug)]
8pub enum Rule {
9    /// Container with vertical distribution
10    Column,
11
12    /// Container with horizonal distribution
13    Row,
14
15    /// Padding of the container
16    Padding(u16),
17
18    /// Spacing between elements
19    Spacing(u16),
20}
21
22impl Rule {
23    /// Returns the class name of the [`Rule`].
24    pub fn class<'a>(&self) -> String {
25        match self {
26            Rule::Column => String::from("c"),
27            Rule::Row => String::from("r"),
28            Rule::Padding(padding) => format!("p-{}", padding),
29            Rule::Spacing(spacing) => format!("s-{}", spacing),
30        }
31    }
32
33    /// Returns the declaration of the [`Rule`].
34    pub fn declaration<'a>(&self, bump: &'a bumpalo::Bump) -> &'a str {
35        let class = self.class();
36
37        match self {
38            Rule::Column => {
39                let body = "{ display: flex; flex-direction: column; }";
40
41                bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
42            }
43            Rule::Row => {
44                let body = "{ display: flex; flex-direction: row; }";
45
46                bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
47            }
48            Rule::Padding(padding) => bumpalo::format!(
49                in bump,
50                ".{} {{ box-sizing: border-box; padding: {}px }}",
51                class,
52                padding
53            )
54            .into_bump_str(),
55            Rule::Spacing(spacing) => bumpalo::format!(
56                in bump,
57                ".c.{} > * {{ margin-bottom: {}px }} \
58                 .r.{} > * {{ margin-right: {}px }} \
59                 .c.{} > *:last-child {{ margin-bottom: 0 }} \
60                 .r.{} > *:last-child {{ margin-right: 0 }}",
61                class,
62                spacing,
63                class,
64                spacing,
65                class,
66                class
67            )
68            .into_bump_str(),
69        }
70    }
71}
72
73/// A cascading style sheet.
74#[derive(Debug)]
75pub struct Css<'a> {
76    rules: BTreeMap<String, &'a str>,
77}
78
79impl<'a> Css<'a> {
80    /// Creates an empty [`Css`].
81    pub fn new() -> Self {
82        Css {
83            rules: BTreeMap::new(),
84        }
85    }
86
87    /// Inserts the [`Rule`] in the [`Css`], if it was not previously
88    /// inserted.
89    ///
90    /// It returns the class name of the provided [`Rule`].
91    pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String {
92        let class = rule.class();
93
94        if !self.rules.contains_key(&class) {
95            let _ = self.rules.insert(class.clone(), rule.declaration(bump));
96        }
97
98        class
99    }
100
101    /// Produces the VDOM node of the [`Css`].
102    pub fn node(self, bump: &'a bumpalo::Bump) -> dodrio::Node<'a> {
103        use dodrio::builder::*;
104
105        let mut declarations = bumpalo::collections::Vec::new_in(bump);
106
107        declarations.push(text("html { height: 100% }"));
108        declarations.push(text(
109            "body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }",
110        ));
111        declarations.push(text("* { margin: 0; padding: 0 }"));
112        declarations.push(text(
113            "button { border: none; cursor: pointer; outline: none }",
114        ));
115
116        for declaration in self.rules.values() {
117            declarations.push(text(*declaration));
118        }
119
120        style(bump).children(declarations).finish()
121    }
122}
123
124/// Returns the style value for the given [`Length`].
125pub fn length(length: Length) -> String {
126    match length {
127        Length::Shrink => String::from("auto"),
128        Length::Units(px) => format!("{}px", px),
129        Length::Fill | Length::FillPortion(_) => String::from("100%"),
130    }
131}
132
133/// Returns the style value for the given maximum length in units.
134pub fn max_length(units: u32) -> String {
135    use std::u32;
136
137    if units == u32::MAX {
138        String::from("initial")
139    } else {
140        format!("{}px", units)
141    }
142}
143
144/// Returns the style value for the given minimum length in units.
145pub fn min_length(units: u32) -> String {
146    if units == 0 {
147        String::from("initial")
148    } else {
149        format!("{}px", units)
150    }
151}
152
153/// Returns the style value for the given [`Color`].
154pub fn color(Color { r, g, b, a }: Color) -> String {
155    format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a)
156}
157
158/// Returns the style value for the given [`Background`].
159pub fn background(background: Background) -> String {
160    match background {
161        Background::Color(c) => color(c),
162    }
163}
164
165/// Returns the style value for the given [`Align`].
166pub fn align(align: Align) -> &'static str {
167    match align {
168        Align::Start => "flex-start",
169        Align::Center => "center",
170        Align::End => "flex-end",
171    }
172}