azul_css/
css.rs

1//! Types and methods used to describe the style of an application
2use crate::css_properties::{CssProperty, CssPropertyType};
3use std::fmt;
4
5/// Css stylesheet - contains a parsed CSS stylesheet in "rule blocks",
6/// i.e. blocks of key-value pairs associated with a selector path.
7#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
8pub struct Css {
9    /// One CSS stylesheet can hold more than one sub-stylesheet:
10    /// For example, when overriding native styles, the `.sort_by_specificy()` function
11    /// should not mix the two stylesheets during sorting.
12    pub stylesheets: Vec<Stylesheet>,
13}
14
15impl Css {
16    pub const fn new(stylesheets: Vec<Stylesheet>) -> Self {
17        Self { stylesheets }
18    }
19}
20
21#[derive(Debug, Default, PartialEq, PartialOrd, Clone)]
22pub struct Stylesheet {
23    /// The style rules making up the document - for example, de-duplicated CSS rules
24    pub rules: Vec<CssRuleBlock>,
25}
26
27impl Stylesheet {
28    pub const fn new(rules: Vec<CssRuleBlock>) -> Self {
29        Self { rules }
30    }
31}
32
33impl From<Vec<CssRuleBlock>> for Stylesheet {
34    fn from(rules: Vec<CssRuleBlock>) -> Self {
35        Self { rules }
36    }
37}
38
39/// Contains one parsed `key: value` pair, static or dynamic
40#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
41pub enum CssDeclaration {
42    /// Static key-value pair, such as `width: 500px`
43    Static(CssProperty),
44    /// Dynamic key-value pair with default value, such as `width: [[ my_id | 500px ]]`
45    Dynamic(DynamicCssProperty),
46}
47
48impl CssDeclaration {
49
50    pub const fn new_static(prop: CssProperty) -> Self {
51        CssDeclaration::Static(prop)
52    }
53
54    pub const fn new_dynamic(prop: DynamicCssProperty) -> Self {
55        CssDeclaration::Dynamic(prop)
56    }
57
58    /// Returns the type of the property (i.e. the CSS key as a typed enum)
59    pub fn get_type(&self) -> CssPropertyType {
60        use self::CssDeclaration::*;
61        match self {
62            Static(s) => s.get_type(),
63            Dynamic(d) => d.default_value.get_type(),
64        }
65    }
66
67    /// Determines if the property will be inherited (applied to the children)
68    /// during the recursive application of the style on the DOM tree
69    pub fn is_inheritable(&self) -> bool {
70        use self::CssDeclaration::*;
71        match self {
72            Static(s) => s.get_type().is_inheritable(),
73            Dynamic(d) => d.is_inheritable(),
74        }
75    }
76
77    /// Returns whether this rule affects only styling properties or layout
78    /// properties (that could trigger a re-layout)
79    pub fn can_trigger_relayout(&self) -> bool {
80        use self::CssDeclaration::*;
81        match self {
82            Static(s) => s.get_type().can_trigger_relayout(),
83            Dynamic(d) => d.can_trigger_relayout(),
84        }
85    }
86
87    pub fn to_str(&self) -> String {
88        use self::CssDeclaration::*;
89        match self {
90            Static(s) => format!("{:?}", s),
91            Dynamic(d) => format!("var(--{}, {:?})", d.dynamic_id, d.default_value),
92        }
93    }
94}
95
96/// A `DynamicCssProperty` is a type of css property that can be changed on possibly
97/// every frame by the Rust code - for example to implement an `On::Hover` behaviour.
98///
99/// The syntax for such a property looks like this:
100///
101/// ```no_run,ignore
102/// #my_div {
103///    padding: var(--my_dynamic_property_id, 400px);
104/// }
105/// ```
106///
107/// Azul will register a dynamic property with the key "my_dynamic_property_id"
108/// and the default value of 400px. If the property gets overridden during one frame,
109/// the overridden property takes precedence.
110///
111/// At runtime the style is immutable (which is a performance optimization - if we
112/// can assume that the property never changes at runtime), we can do some optimizations on it.
113/// Dynamic style properties can also be used for animations and conditional styles
114/// (i.e. `hover`, `focus`, etc.), thereby leading to cleaner code, since all of these
115/// special cases now use one single API.
116#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
117pub struct DynamicCssProperty {
118    /// The stringified ID of this property, i.e. the `"my_id"` in `width: var(--my_id, 500px)`.
119    pub dynamic_id: String,
120    /// Default values for this properties - one single value can control multiple properties!
121    pub default_value: CssProperty,
122}
123
124#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
125pub enum CssPropertyValue<T> {
126    Auto,
127    None,
128    Initial,
129    Inherit,
130    Exact(T),
131}
132
133impl<T: fmt::Debug> fmt::Debug for CssPropertyValue<T> {
134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135        use self::CssPropertyValue::*;
136        match self {
137            Auto => write!(f, "auto"),
138            None => write!(f, "none"),
139            Initial => write!(f, "initial"),
140            Inherit => write!(f, "inherit"),
141            Exact(e) => write!(f, "{:?}", e),
142        }
143    }
144}
145
146impl<T: fmt::Display> fmt::Display for CssPropertyValue<T> {
147    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148        use self::CssPropertyValue::*;
149        match self {
150            Auto => write!(f, "auto"),
151            None => write!(f, "none"),
152            Initial => write!(f, "initial"),
153            Inherit => write!(f, "inherit"),
154            Exact(e) => write!(f, "{}", e),
155        }
156    }
157}
158
159impl<T> From<T> for CssPropertyValue<T> {
160    fn from(c: T) -> Self { CssPropertyValue::Exact(c) }
161}
162
163impl<T> CssPropertyValue<T> {
164
165    /// Transforms a `CssPropertyValue<T>` into a `CssPropertyValue<U>` by applying a mapping function
166    #[inline]
167    pub fn map_property<F: Fn(T) -> U, U>(self, map_fn: F) -> CssPropertyValue<U> {
168        match self {
169            CssPropertyValue::Exact(c) => CssPropertyValue::Exact(map_fn(c)),
170            CssPropertyValue::Auto => CssPropertyValue::Auto,
171            CssPropertyValue::None => CssPropertyValue::None,
172            CssPropertyValue::Initial => CssPropertyValue::Initial,
173            CssPropertyValue::Inherit => CssPropertyValue::Inherit,
174        }
175    }
176
177    #[inline]
178    pub fn get_property(&self) -> Option<&T> {
179        match self {
180            CssPropertyValue::Exact(c) => Some(c),
181            _ => None,
182        }
183    }
184
185    #[inline]
186    pub fn get_property_owned(self) -> Option<T> {
187        match self {
188            CssPropertyValue::Exact(c) => Some(c),
189            _ => None,
190        }
191    }
192
193    #[inline]
194    pub fn is_auto(&self) -> bool {
195        match self {
196            CssPropertyValue::Auto => true,
197            _ => false,
198        }
199    }
200
201    #[inline]
202    pub fn is_none(&self) -> bool {
203        match self {
204            CssPropertyValue::None => true,
205            _ => false,
206        }
207    }
208
209    #[inline]
210    pub fn is_initial(&self) -> bool {
211        match self {
212            CssPropertyValue::Initial => true,
213            _ => false,
214        }
215    }
216
217    #[inline]
218    pub fn is_inherit(&self) -> bool {
219        match self {
220            CssPropertyValue::Inherit => true,
221            _ => false,
222        }
223    }
224}
225
226impl<T: Default> CssPropertyValue<T> {
227    #[inline]
228    pub fn get_property_or_default(self) -> Option<T> {
229        match self {
230            CssPropertyValue::Auto | CssPropertyValue::Initial => Some(T::default()),
231            CssPropertyValue::Exact(c) => Some(c),
232            CssPropertyValue::None | CssPropertyValue::Inherit => None,
233        }
234    }
235}
236
237impl<T: Default> Default for CssPropertyValue<T> {
238    #[inline]
239    fn default() -> Self {
240        CssPropertyValue::Exact(T::default())
241    }
242}
243
244impl DynamicCssProperty {
245    pub fn is_inheritable(&self) -> bool {
246        // Dynamic style properties should not be inheritable,
247        // since that could lead to bugs - you set a property in Rust, suddenly
248        // the wrong UI component starts to react because it was inherited.
249        false
250    }
251
252    pub fn can_trigger_relayout(&self) -> bool {
253        self.default_value.get_type().can_trigger_relayout()
254    }
255}
256
257/// One block of rules that applies a bunch of rules to a "path" in the style, i.e.
258/// `div#myid.myclass -> { ("justify-content", "center") }`
259#[derive(Debug, Clone, PartialOrd, PartialEq)]
260pub struct CssRuleBlock {
261    /// The css path (full selector) of the style ruleset
262    pub path: CssPath,
263    /// `"justify-content: center"` =>
264    /// `CssDeclaration::Static(CssProperty::JustifyContent(LayoutJustifyContent::Center))`
265    pub declarations: Vec<CssDeclaration>,
266}
267
268impl CssRuleBlock {
269    pub const fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
270        Self {
271            path,
272            declarations,
273        }
274    }
275}
276
277pub type CssContentGroup<'a> = Vec<&'a CssPathSelector>;
278
279/// Signifies the type (i.e. the discriminant value) of a DOM node
280/// without carrying any of its associated data
281#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
282pub enum NodeTypePath {
283    Body,
284    Div,
285    P,
286    Img,
287    Texture,
288    IFrame,
289}
290
291#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
292pub enum NodeTypePathParseError<'a> {
293    Invalid(&'a str),
294}
295
296impl<'a> fmt::Display for NodeTypePathParseError<'a> {
297    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
298        match &self {
299            NodeTypePathParseError::Invalid(e) => write!(f, "Invalid node type: {}", e),
300        }
301    }
302}
303
304const NODE_TYPE_PATH_MAP: [(NodeTypePath, &'static str); 6] = [
305    (NodeTypePath::Body, "body"),
306    (NodeTypePath::Div, "div"),
307    (NodeTypePath::P, "p"),
308    (NodeTypePath::Img, "img"),
309    (NodeTypePath::Texture, "texture"),
310    (NodeTypePath::IFrame, "iframe"),
311];
312
313/// Parses the node type from a CSS string such as `"div"` => `NodeTypePath::Div`
314impl NodeTypePath {
315    pub fn from_str(css_key: &str) -> Result<Self, NodeTypePathParseError> {
316        NODE_TYPE_PATH_MAP.iter()
317        .find(|(_, k)| css_key == *k)
318        .and_then(|(v, _)| Some(*v))
319        .ok_or(NodeTypePathParseError::Invalid(css_key))
320    }
321}
322
323impl fmt::Display for NodeTypePath {
324    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
325        let display_string = NODE_TYPE_PATH_MAP.iter()
326            .find(|(v, _)| *self == *v)
327            .and_then(|(_, k)| Some(*k))
328            .unwrap();
329
330        write!(f, "{}", display_string)?;
331        Ok(())
332    }
333}
334
335/// Represents a full CSS path (i.e. the "div#id.class" selector belonging to
336///  a CSS "content group" (the following key-value block)).
337///
338/// ```no_run,ignore
339/// "#div > .my_class:focus" ==
340/// [
341///   CssPathSelector::Type(NodeTypePath::Div),
342///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::LimitChildren),
343///   CssPathSelector::Class("my_class"),
344///   CssPathSelector::PseudoSelector(CssPathPseudoSelector::Focus),
345/// ]
346#[derive(Clone, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
347pub struct CssPath {
348    pub selectors: Vec<CssPathSelector>,
349}
350
351impl CssPath {
352    pub const fn new(selectors: Vec<CssPathSelector>) -> Self {
353        Self { selectors }
354    }
355}
356
357impl fmt::Display for CssPath {
358    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
359        for selector in &self.selectors {
360            write!(f, "{}", selector)?;
361        }
362        Ok(())
363    }
364}
365
366impl fmt::Debug for CssPath {
367    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
368        write!(f, "{}", self)
369    }
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
373pub enum CssPathSelector {
374    /// Represents the `*` selector
375    Global,
376    /// `div`, `p`, etc.
377    Type(NodeTypePath),
378    /// `.something`
379    Class(String),
380    /// `#something`
381    Id(String),
382    /// `:something`
383    PseudoSelector(CssPathPseudoSelector),
384    /// Represents the `>` selector
385    DirectChildren,
386    /// Represents the ` ` selector
387    Children,
388}
389
390impl Default for CssPathSelector {
391    fn default() -> Self {
392        CssPathSelector::Global
393    }
394}
395
396impl fmt::Display for CssPathSelector {
397    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
398        use self::CssPathSelector::*;
399        match &self {
400            Global => write!(f, "*"),
401            Type(n) => write!(f, "{}", n),
402            Class(c) => write!(f, ".{}", c),
403            Id(i) => write!(f, "#{}", i),
404            PseudoSelector(p) => write!(f, ":{}", p),
405            DirectChildren => write!(f, ">"),
406            Children => write!(f, " "),
407        }
408    }
409}
410
411#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
412pub enum CssPathPseudoSelector {
413    /// `:first`
414    First,
415    /// `:last`
416    Last,
417    /// `:nth-child`
418    NthChild(CssNthChildSelector),
419    /// `:hover` - mouse is over element
420    Hover,
421    /// `:active` - mouse is pressed and over element
422    Active,
423    /// `:focus` - element has received focus
424    Focus,
425}
426
427#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
428pub enum CssNthChildSelector {
429    Number(usize),
430    Even,
431    Odd,
432    Pattern { repeat: usize, offset: usize },
433}
434
435impl fmt::Display for CssNthChildSelector {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        use self::CssNthChildSelector::*;
438        match &self {
439            Number(u) => write!(f, "{}", u),
440            Even => write!(f, "even"),
441            Odd => write!(f, "odd"),
442            Pattern { repeat, offset } => write!(f, "{}n + {}", repeat, offset),
443        }
444    }
445}
446
447impl fmt::Display for CssPathPseudoSelector {
448    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449        use self::CssPathPseudoSelector::*;
450        match &self {
451            First => write!(f, "first"),
452            Last => write!(f, "last"),
453            NthChild(u) => write!(f, "nth-child({})", u),
454            Hover => write!(f, "hover"),
455            Active => write!(f, "active"),
456            Focus => write!(f, "focus"),
457        }
458    }
459}
460
461impl Css {
462
463    /// Creates a new, empty CSS with no stylesheets
464    pub fn empty() -> Self {
465        Default::default()
466    }
467
468    pub fn append(&mut self, css: Self) {
469        for stylesheet in css.stylesheets {
470            self.append_stylesheet(stylesheet);
471        }
472    }
473
474    pub fn append_stylesheet(&mut self, styles: Stylesheet) {
475        self.stylesheets.push(styles);
476    }
477
478    pub fn sort_by_specificity(&mut self) {
479        for stylesheet in &mut self.stylesheets {
480            stylesheet.sort_by_specificity()
481        }
482    }
483
484    pub fn rules<'a>(&'a self) -> RuleIterator<'a> {
485        RuleIterator {
486            current_stylesheet: 0,
487            current_rule: 0,
488            css: self,
489        }
490    }
491}
492
493pub struct RuleIterator<'a> {
494    current_stylesheet: usize,
495    current_rule: usize,
496    css: &'a Css,
497}
498
499impl<'a> Iterator for RuleIterator<'a> {
500    type Item = &'a CssRuleBlock;
501    fn next(&mut self) -> Option<&'a CssRuleBlock> {
502        let current_stylesheet = self.css.stylesheets.get(self.current_stylesheet)?;
503        match current_stylesheet.rules.get(self.current_rule) {
504            Some(s) => {
505                self.current_rule += 1;
506                Some(s)
507            },
508            None => {
509                self.current_rule = 0;
510                self.current_stylesheet += 1;
511                self.next()
512            }
513        }
514    }
515}
516
517
518impl Stylesheet {
519
520    /// Creates a new stylesheet with no style rules.
521    pub fn empty() -> Self {
522        Default::default()
523    }
524
525    /// Sort the style rules by their weight, so that the rules are applied in the correct order.
526    /// Should always be called when a new style is loaded from an external source.
527    pub fn sort_by_specificity(&mut self) {
528        self.rules.sort_by(|a, b| get_specificity(&a.path).cmp(&get_specificity(&b.path)));
529    }
530}
531
532/// Returns specificity of the given css path. Further information can be found on
533/// [the w3 website](http://www.w3.org/TR/selectors/#specificity).
534fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
535    let id_count = path.selectors.iter().filter(|x|     if let CssPathSelector::Id(_) = x {     true } else { false }).count();
536    let class_count = path.selectors.iter().filter(|x|  if let CssPathSelector::Class(_) = x {  true } else { false }).count();
537    let div_count = path.selectors.iter().filter(|x|    if let CssPathSelector::Type(_) = x {   true } else { false }).count();
538    (id_count, class_count, div_count, path.selectors.len())
539}
540
541#[test]
542fn test_specificity() {
543    use self::CssPathSelector::*;
544    assert_eq!(get_specificity(&CssPath { selectors: vec![Id("hello".into())] }), (1, 0, 0, 1));
545    assert_eq!(get_specificity(&CssPath { selectors: vec![Class("hello".into())] }), (0, 1, 0, 1));
546    assert_eq!(get_specificity(&CssPath { selectors: vec![Type(NodeTypePath::Div)] }), (0, 0, 1, 1));
547    assert_eq!(get_specificity(&CssPath { selectors: vec![Id("hello".into()), Type(NodeTypePath::Div)] }), (1, 0, 1, 2));
548}
549
550// Assert that order of the style items is correct (in order of CSS path specificity, lowest-to-highest)
551#[test]
552fn test_specificity_sort() {
553    use self::CssPathSelector::*;
554    use crate::NodeTypePath::*;
555
556    let mut input_style = Stylesheet {
557        rules: vec![
558            // Rules are sorted from lowest-specificity to highest specificity
559            CssRuleBlock { path: CssPath { selectors: vec![Global] }, declarations: Vec::new() },
560            CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Class("my_class".into()), Id("my_id".into())] }, declarations: Vec::new() },
561            CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Id("my_id".into())] }, declarations: Vec::new() },
562            CssRuleBlock { path: CssPath { selectors: vec![Global, Id("my_id".into())] }, declarations: Vec::new() },
563            CssRuleBlock { path: CssPath { selectors: vec![Type(Div), Class("my_class".into()), Class("specific".into()), Id("my_id".into())] }, declarations: Vec::new() },
564        ],
565    };
566
567    input_style.sort_by_specificity();
568
569    let expected_style = Stylesheet {
570        rules: vec![
571            // Rules are sorted from lowest-specificity to highest specificity
572            CssRuleBlock { path: CssPath { selectors: vec![Global] }, declarations: Vec::new() },
573            CssRuleBlock { path: CssPath { selectors: vec![Global, Id("my_id".into())] }, declarations: Vec::new() },
574            CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Id("my_id".into())] }, declarations: Vec::new() },
575            CssRuleBlock { path: CssPath { selectors: vec![Global, Type(Div), Class("my_class".into()), Id("my_id".into())] }, declarations: Vec::new() },
576            CssRuleBlock { path: CssPath { selectors: vec![Type(Div), Class("my_class".into()), Class("specific".into()), Id("my_id".into())] }, declarations: Vec::new() },
577        ],
578    };
579
580    assert_eq!(input_style, expected_style);
581}