azul_css/
css.rs

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