summaryrefslogblamecommitdiffstats
path: root/web/src/css.rs
blob: 21f51f85522eb291fee61e3e4746a2d6b3b73339 (plain) (tree)
1
2
3
4
5
6
7
8
                       
                                                                


                               
                              
                
               





                                             

                                


                                         

 
           
                                               

                                       

                                              
                                                               
                                                               


         
                                                



                                                                       
                             



                                                                                
                          



                                                                                
                                                       












                                                              







































                                                                                                                                                                



         
                            
                

                                     

 
                  
                                 
                          

                                   


         
                                                                     

                 
                                                           

                                                                             
 

                                                                             




             
                                              








                                                                                    
                                                               



                                                                      
                                                







                                                     



                                                 
                                                                      


     



















                                                                  
                                                    



                                                                       
                                                         





                                                     
                                                    






                                            









                                                                
//! Style your widgets.
use crate::{bumpalo, Align, Background, Color, Length, Padding};

use std::collections::BTreeMap;

/// A CSS rule of a VDOM node.
#[derive(Debug)]
pub enum Rule {
    /// Container with vertical distribution
    Column,

    /// Container with horizonal distribution
    Row,

    /// Spacing between elements
    Spacing(u16),

    /// Toggler input for a specific size
    Toggler(u16),
}

impl Rule {
    /// Returns the class name of the [`Rule`].
    pub fn class<'a>(&self) -> String {
        match self {
            Rule::Column => String::from("c"),
            Rule::Row => String::from("r"),
            Rule::Spacing(spacing) => format!("s-{}", spacing),
            Rule::Toggler(size) => format!("toggler-{}", size),
        }
    }

    /// Returns the declaration of the [`Rule`].
    pub fn declaration<'a>(&self, bump: &'a bumpalo::Bump) -> &'a str {
        let class = self.class();

        match self {
            Rule::Column => {
                let body = "{ display: flex; flex-direction: column; }";

                bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
            }
            Rule::Row => {
                let body = "{ display: flex; flex-direction: row; }";

                bumpalo::format!(in bump, ".{} {}", class, body).into_bump_str()
            }
            Rule::Spacing(spacing) => bumpalo::format!(
                in bump,
                ".c.{} > * {{ margin-bottom: {}px }} \
                 .r.{} > * {{ margin-right: {}px }} \
                 .c.{} > *:last-child {{ margin-bottom: 0 }} \
                 .r.{} > *:last-child {{ margin-right: 0 }}",
                class,
                spacing,
                class,
                spacing,
                class,
                class
            )
            .into_bump_str(),
            Rule::Toggler(size) => bumpalo::format!(
                in bump,
                ".toggler-{} {{ display: flex; cursor: pointer; justify-content: space-between; }} \
                 .toggler-{} input {{ display:none; }} \
                 .toggler-{} span {{ background-color: #b1b1b1; position: relative; display: inline-flex; width:{}px; height: {}px; border-radius: {}px;}} \
                 .toggler-{} span > span {{ background-color: #FFFFFF; width: {}px; height: {}px; border-radius: 50%; top: 1px; left: 1px;}} \
                 .toggler-{}:hover span > span {{ background-color: #f1f1f1 !important; }} \
                 .toggler-{} input:checked + span {{ background-color: #00FF00; }} \
                 .toggler-{} input:checked + span > span {{ -webkit-transform: translateX({}px); -ms-transform:translateX({}px); transform: translateX({}px); }}
                ",
                // toggler
                size,

                // toggler input
                size,

                // toggler span
                size,
                size*2,
                size,
                size,

                // toggler span > span
                size,
                size-2,
                size-2,

                // toggler: hover + span > span
                size,

                // toggler input:checked + span
                size,

                // toggler input:checked + span > span
                size,
                size,
                size,
                size
            )
            .into_bump_str(),
        }
    }
}

/// A cascading style sheet.
#[derive(Debug)]
pub struct Css<'a> {
    rules: BTreeMap<String, &'a str>,
}

impl<'a> Css<'a> {
    /// Creates an empty [`Css`].
    pub fn new() -> Self {
        Css {
            rules: BTreeMap::new(),
        }
    }

    /// Inserts the [`Rule`] in the [`Css`], if it was not previously
    /// inserted.
    ///
    /// It returns the class name of the provided [`Rule`].
    pub fn insert(&mut self, bump: &'a bumpalo::Bump, rule: Rule) -> String {
        let class = rule.class();

        if !self.rules.contains_key(&class) {
            let _ = self.rules.insert(class.clone(), rule.declaration(bump));
        }

        class
    }

    /// Produces the VDOM node of the [`Css`].
    pub fn node(self, bump: &'a bumpalo::Bump) -> dodrio::Node<'a> {
        use dodrio::builder::*;

        let mut declarations = bumpalo::collections::Vec::new_in(bump);

        declarations.push(text("html { height: 100% }"));
        declarations.push(text(
            "body { height: 100%; margin: 0; padding: 0; font-family: sans-serif }",
        ));
        declarations.push(text("* { margin: 0; padding: 0 }"));
        declarations.push(text(
            "button { border: none; cursor: pointer; outline: none }",
        ));

        for declaration in self.rules.values() {
            declarations.push(text(*declaration));
        }

        style(bump).children(declarations).finish()
    }
}

/// Returns the style value for the given [`Length`].
pub fn length(length: Length) -> String {
    match length {
        Length::Shrink => String::from("auto"),
        Length::Units(px) => format!("{}px", px),
        Length::Fill | Length::FillPortion(_) => String::from("100%"),
    }
}

/// Returns the style value for the given maximum length in units.
pub fn max_length(units: u32) -> String {
    use std::u32;

    if units == u32::MAX {
        String::from("initial")
    } else {
        format!("{}px", units)
    }
}

/// Returns the style value for the given minimum length in units.
pub fn min_length(units: u32) -> String {
    if units == 0 {
        String::from("initial")
    } else {
        format!("{}px", units)
    }
}

/// Returns the style value for the given [`Color`].
pub fn color(Color { r, g, b, a }: Color) -> String {
    format!("rgba({}, {}, {}, {})", 255.0 * r, 255.0 * g, 255.0 * b, a)
}

/// Returns the style value for the given [`Background`].
pub fn background(background: Background) -> String {
    match background {
        Background::Color(c) => color(c),
    }
}

/// Returns the style value for the given [`Align`].
pub fn align(align: Align) -> &'static str {
    match align {
        Align::Start => "flex-start",
        Align::Center => "center",
        Align::End => "flex-end",
    }
}

/// Returns the style value for the given [`Padding`].
///
/// [`Padding`]: struct.Padding.html
pub fn padding(padding: Padding) -> String {
    format!(
        "{}px {}px {}px {}px",
        padding.top, padding.right, padding.bottom, padding.left
    )
}