Skip to main content

floem/style/
props.rs

1//! Style property traits, classes, and keys.
2//!
3//! This module provides the core traits and types for defining and working with
4//! style properties:
5//! - [`StyleClass`] - trait for defining style classes
6//! - [`StyleProp`] - trait for defining style properties
7//! - [`StylePropReader`] - trait for reading properties from styles
8//! - [`StyleKey`] - unique identifier for style entries
9//! - Macros: `style_class!`, `prop!`, `prop_extractor!`
10
11use std::any::{Any, type_name};
12use std::fmt::{self, Debug};
13use std::hash::{Hash, Hasher};
14use std::ptr;
15use std::rc::Rc;
16
17#[cfg(not(target_arch = "wasm32"))]
18use std::time::Instant;
19#[cfg(target_arch = "wasm32")]
20use web_time::Instant;
21
22use crate::view::{IntoView, View};
23use crate::views::Label;
24
25use super::Style;
26use super::selectors::StyleSelectors;
27use super::transition::TransitionState;
28use super::values::{StyleMapValue, StylePropValue, StyleValue};
29
30// ============================================================================
31// StyleClass
32// ============================================================================
33
34pub trait StyleClass: Default + Copy + 'static {
35    fn key() -> StyleKey;
36    fn class_ref() -> StyleClassRef {
37        StyleClassRef { key: Self::key() }
38    }
39}
40
41pub trait StyleDebugGroup: Default + Copy + 'static {
42    fn key() -> StyleKey;
43    fn group_ref() -> StyleDebugGroupRef {
44        StyleDebugGroupRef { key: Self::key() }
45    }
46    fn member_props() -> Vec<StyleKey>;
47    fn debug_view(style: &Style) -> Option<Box<dyn View>>;
48}
49
50#[derive(Debug, Clone)]
51pub struct StyleClassInfo {
52    pub(crate) name: fn() -> &'static str,
53}
54
55impl StyleClassInfo {
56    pub const fn new<Name>() -> Self {
57        StyleClassInfo {
58            name: || std::any::type_name::<Name>(),
59        }
60    }
61}
62
63#[derive(Copy, Clone, Debug, PartialEq)]
64pub struct StyleClassRef {
65    pub key: StyleKey,
66}
67
68#[derive(Debug, Clone)]
69pub struct StyleDebugGroupInfo {
70    pub(crate) name: fn() -> &'static str,
71    pub(crate) inherited: bool,
72    pub(crate) member_props: fn() -> Vec<StyleKey>,
73    pub(crate) debug_view: fn(style: &Style) -> Option<Box<dyn View>>,
74}
75
76impl StyleDebugGroupInfo {
77    pub const fn new<Name>(
78        inherited: bool,
79        member_props: fn() -> Vec<StyleKey>,
80        debug_view: fn(style: &Style) -> Option<Box<dyn View>>,
81    ) -> Self {
82        StyleDebugGroupInfo {
83            name: || std::any::type_name::<Name>(),
84            inherited,
85            member_props,
86            debug_view,
87        }
88    }
89}
90
91#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
92pub struct StyleDebugGroupRef {
93    pub key: StyleKey,
94}
95
96macro_rules! style_key_selector {
97    ($v:vis $name:ident, $sel:expr) => {
98        fn $name() -> $crate::style::StyleKey {
99            static INFO: $crate::style::StyleKeyInfo = $crate::style::StyleKeyInfo::Selector($sel);
100            $crate::style::StyleKey { info: &INFO }
101        }
102    };
103}
104
105pub(crate) use style_key_selector;
106
107#[macro_export]
108macro_rules! style_class {
109    ($(#[$meta:meta])* $v:vis $name:ident) => {
110        $(#[$meta])*
111        #[derive(Default, Copy, Clone)]
112        $v struct $name;
113
114        impl $crate::style::StyleClass for $name {
115            fn key() -> $crate::style::StyleKey {
116                static INFO: $crate::style::StyleKeyInfo = $crate::style::StyleKeyInfo::Class(
117                    $crate::style::StyleClassInfo::new::<$name>()
118                );
119                $crate::style::StyleKey { info: &INFO }
120            }
121        }
122    };
123}
124
125#[macro_export]
126macro_rules! style_debug_group {
127    ($(#[$meta:meta])* $v:vis $name:ident $(, inherited = $inherited:ident)?, members = [$($prop:ty),* $(,)?], view = $view:path) => {
128        $(#[$meta])*
129        #[derive(Default, Copy, Clone)]
130        $v struct $name;
131
132        impl $crate::style::StyleDebugGroup for $name {
133            fn key() -> $crate::style::StyleKey {
134                static INFO: $crate::style::StyleKeyInfo =
135                    $crate::style::StyleKeyInfo::DebugGroup(
136                        $crate::style::StyleDebugGroupInfo::new::<$name>(
137                            style_debug_group!(@inherited $($inherited)?),
138                            || vec![$(<$prop as $crate::style::StyleProp>::key()),*],
139                            $view,
140                        )
141                    );
142                $crate::style::StyleKey { info: &INFO }
143            }
144
145            fn member_props() -> Vec<$crate::style::StyleKey> {
146                vec![$(<$prop as $crate::style::StyleProp>::key()),*]
147            }
148
149            fn debug_view(style: &$crate::style::Style) -> Option<Box<dyn $crate::view::View>> {
150                $view(style)
151            }
152        }
153    };
154    (@inherited inherited) => {
155        true
156    };
157    (@inherited) => {
158        false
159    };
160}
161
162// ============================================================================
163// StyleProp
164// ============================================================================
165
166pub trait StyleProp: Default + Copy + 'static {
167    type Type: StylePropValue;
168    fn key() -> StyleKey;
169    fn prop_ref() -> StylePropRef {
170        StylePropRef { key: Self::key() }
171    }
172    fn default_value() -> Self::Type;
173}
174
175pub(crate) type InterpolateFn =
176    fn(val1: &dyn Any, val2: &dyn Any, time: f64) -> Option<Rc<dyn Any>>;
177
178/// Function pointer type for computing content hash of a style value.
179pub(crate) type HashAnyFn = fn(val: &dyn Any) -> u64;
180
181/// Function pointer type for comparing two style values for equality.
182pub(crate) type EqAnyFn = fn(val1: &dyn Any, val2: &dyn Any) -> bool;
183/// Function pointer type for resolving a stored inherited property into a concrete value.
184pub(crate) type ResolveInheritedAnyFn = fn(val: &dyn Any, style: &Style) -> Rc<dyn Any>;
185
186#[derive(Debug)]
187pub struct StylePropInfo {
188    pub(crate) name: fn() -> &'static str,
189    pub(crate) inherited: bool,
190    #[allow(unused)]
191    pub(crate) default_as_any: fn() -> Rc<dyn Any>,
192    pub(crate) interpolate: InterpolateFn,
193    pub(crate) debug_any: fn(val: &dyn Any) -> String,
194    pub(crate) debug_view: fn(val: &dyn Any) -> Option<Box<dyn View>>,
195    pub(crate) transition_key: StyleKey,
196    /// Computes a content-based hash for a style value.
197    pub(crate) hash_any: HashAnyFn,
198    /// Compares two style values for equality.
199    pub(crate) eq_any: EqAnyFn,
200    /// Resolves a stored property value for inheritance propagation.
201    pub(crate) resolve_inherited_any: ResolveInheritedAnyFn,
202}
203
204impl StylePropInfo {
205    pub const fn new<Name, T: StylePropValue + 'static>(
206        inherited: bool,
207        default_as_any: fn() -> Rc<dyn Any>,
208        transition_key: StyleKey,
209    ) -> Self {
210        StylePropInfo {
211            name: || std::any::type_name::<Name>(),
212            inherited,
213            default_as_any,
214            debug_any: |val| {
215                if let Some(v) = val.downcast_ref::<StyleMapValue<T>>() {
216                    match v {
217                        StyleMapValue::Val(v) | StyleMapValue::Animated(v) => format!("{v:?}"),
218                        StyleMapValue::Context(_) => "Context(..)".to_owned(),
219                        StyleMapValue::Unset => "Unset".to_owned(),
220                    }
221                } else {
222                    panic!(
223                        "expected type {} for property {}",
224                        type_name::<T>(),
225                        std::any::type_name::<Name>(),
226                    )
227                }
228            },
229            interpolate: |val1, val2, time| {
230                if let (Some(v1), Some(v2)) = (
231                    val1.downcast_ref::<StyleMapValue<T>>(),
232                    val2.downcast_ref::<StyleMapValue<T>>(),
233                ) {
234                    if let (
235                        StyleMapValue::Val(v1) | StyleMapValue::Animated(v1),
236                        StyleMapValue::Val(v2) | StyleMapValue::Animated(v2),
237                    ) = (v1, v2)
238                    {
239                        v1.interpolate(v2, time)
240                            .map(|val| Rc::new(StyleMapValue::Animated(val)) as Rc<dyn Any>)
241                    } else {
242                        None
243                    }
244                } else {
245                    panic!(
246                        "expected type {} for property {}. Got typeids {:?} and {:?}",
247                        type_name::<T>(),
248                        std::any::type_name::<Name>(),
249                        val1.type_id(),
250                        val2.type_id()
251                    )
252                }
253            },
254            debug_view: |val| {
255                if let Some(v) = val.downcast_ref::<StyleMapValue<T>>() {
256                    match v {
257                        StyleMapValue::Val(v) | StyleMapValue::Animated(v) => v.debug_view(),
258                        StyleMapValue::Context(_) => Some(Label::new("Context(..)").into_any()),
259                        StyleMapValue::Unset => Some(Label::new("Unset").into_any()),
260                    }
261                } else {
262                    panic!(
263                        "expected type {} for property {}",
264                        type_name::<T>(),
265                        std::any::type_name::<Name>(),
266                    )
267                }
268            },
269            transition_key,
270            hash_any: |val| {
271                if let Some(v) = val.downcast_ref::<StyleMapValue<T>>() {
272                    match v {
273                        StyleMapValue::Val(v) | StyleMapValue::Animated(v) => v.content_hash(),
274                        StyleMapValue::Context(_) => 1,
275                        StyleMapValue::Unset => 0, // Stable hash for unset
276                    }
277                } else {
278                    panic!(
279                        "expected type {} for property {}",
280                        type_name::<T>(),
281                        std::any::type_name::<Name>(),
282                    )
283                }
284            },
285            eq_any: |val1, val2| {
286                if let (Some(v1), Some(v2)) = (
287                    val1.downcast_ref::<StyleMapValue<T>>(),
288                    val2.downcast_ref::<StyleMapValue<T>>(),
289                ) {
290                    match (v1, v2) {
291                        (
292                            StyleMapValue::Val(a) | StyleMapValue::Animated(a),
293                            StyleMapValue::Val(b) | StyleMapValue::Animated(b),
294                        ) => a == b,
295                        (StyleMapValue::Unset, StyleMapValue::Unset) => true,
296                        _ => false,
297                    }
298                } else {
299                    panic!(
300                        "expected type {} for property {}. Got typeids {:?} and {:?}",
301                        type_name::<T>(),
302                        std::any::type_name::<Name>(),
303                        val1.type_id(),
304                        val2.type_id()
305                    )
306                }
307            },
308            resolve_inherited_any: |val, style| {
309                let resolved = match val.downcast_ref::<StyleMapValue<T>>().unwrap_or_else(|| {
310                    panic!(
311                        "expected type {} for property {}",
312                        type_name::<T>(),
313                        std::any::type_name::<Name>(),
314                    )
315                }) {
316                    StyleMapValue::Val(value) | StyleMapValue::Animated(value) => {
317                        StyleMapValue::Val(value.clone())
318                    }
319                    StyleMapValue::Context(context_value) => {
320                        StyleMapValue::Val(context_value.resolve(style))
321                    }
322                    StyleMapValue::Unset => StyleMapValue::Unset,
323                };
324                Rc::new(resolved)
325            },
326        }
327    }
328}
329
330#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
331pub struct StylePropRef {
332    pub key: StyleKey,
333}
334
335impl StylePropRef {
336    pub(crate) fn info(&self) -> &StylePropInfo {
337        if let StyleKeyInfo::Prop(prop) = self.key.info {
338            prop
339        } else {
340            panic!()
341        }
342    }
343}
344
345// ============================================================================
346// StylePropReader
347// ============================================================================
348
349pub trait StylePropReader {
350    type State: Debug;
351    type Type: Clone;
352
353    /// Reads the property from the style.
354    /// Returns true if the property changed.
355    fn read(
356        state: &mut Self::State,
357        style: &Style,
358        now: &Instant,
359        request_transition: &mut bool,
360    ) -> bool;
361
362    fn get(state: &Self::State) -> Self::Type;
363    fn new() -> Self::State;
364}
365
366impl<P: StyleProp> StylePropReader for P {
367    type State = (P::Type, TransitionState<P::Type>);
368    type Type = P::Type;
369
370    // returns true if the value has changed
371    fn read(
372        state: &mut Self::State,
373        style: &Style,
374        now: &Instant,
375        request_transition: &mut bool,
376    ) -> bool {
377        // get the style property
378        let style_value = style.get_prop_style_value::<P>();
379        let mut prop_animated = false;
380        let new = match style_value {
381            StyleValue::Context(_) => {
382                unreachable!("context values should resolve during property reads")
383            }
384            StyleValue::Animated(val) => {
385                *request_transition = true;
386                prop_animated = true;
387                val
388            }
389            StyleValue::Val(val) => val,
390            StyleValue::Unset | StyleValue::Base => P::default_value(),
391        };
392        // set the transition state to the transition if one is found
393        state.1.read(style.get_transition::<P>());
394
395        // there is a previously stored value in state.0. if the values are different, a transition should be started if there is one
396        let changed = new != state.0;
397        if changed && !prop_animated {
398            state.1.transition(&Self::get(state), &new);
399            state.0 = new;
400        } else if prop_animated {
401            state.0 = new;
402        }
403        changed | state.1.step(now, request_transition)
404    }
405
406    // get the current value from the transition state if one is active, else just return the value that was read from the style map
407    fn get(state: &Self::State) -> Self::Type {
408        state.1.get(&state.0)
409    }
410
411    fn new() -> Self::State {
412        (P::default_value(), TransitionState::default())
413    }
414}
415
416impl<P: StyleProp> StylePropReader for Option<P> {
417    type State = Option<P::Type>;
418    type Type = Option<P::Type>;
419    fn read(
420        state: &mut Self::State,
421        style: &Style,
422        _now: &Instant,
423        _transition: &mut bool,
424    ) -> bool {
425        let new = style.get_prop::<P>();
426        let changed = new != *state;
427        *state = new;
428        changed
429    }
430    fn get(state: &Self::State) -> Self::Type {
431        state.clone()
432    }
433    fn new() -> Self::State {
434        None
435    }
436}
437
438// ============================================================================
439// ExtractorField
440// ============================================================================
441
442#[derive(Clone)]
443pub struct ExtractorField<R: StylePropReader> {
444    state: R::State,
445}
446
447impl<R: StylePropReader> Debug for ExtractorField<R> {
448    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449        self.state.fmt(f)
450    }
451}
452
453impl<R: StylePropReader> ExtractorField<R> {
454    pub fn read(&mut self, style: &Style, now: &Instant, request_transition: &mut bool) -> bool {
455        R::read(&mut self.state, style, now, request_transition)
456    }
457    pub fn get(&self) -> R::Type {
458        R::get(&self.state)
459    }
460    #[allow(clippy::new_without_default)]
461    pub fn new() -> Self {
462        Self { state: R::new() }
463    }
464}
465
466impl<R: StylePropReader> PartialEq for ExtractorField<R>
467where
468    R::Type: PartialEq,
469{
470    fn eq(&self, other: &Self) -> bool {
471        self.get() == other.get()
472    }
473}
474
475impl<R: StylePropReader> Eq for ExtractorField<R> where R::Type: Eq {}
476
477impl<R: StylePropReader> std::hash::Hash for ExtractorField<R>
478where
479    R::Type: std::hash::Hash,
480{
481    fn hash<H: Hasher>(&self, state: &mut H) {
482        self.get().hash(state)
483    }
484}
485
486// ============================================================================
487// Macros
488// ============================================================================
489
490#[macro_export]
491macro_rules! prop {
492    ($(#[$meta:meta])* $v:vis $name:ident: $ty:ty { $($options:tt)* } = $default:expr
493    ) => {
494        $(#[$meta])*
495        #[derive(Default, Copy, Clone)]
496        #[allow(missing_docs)]
497        $v struct $name;
498        impl $crate::style::StyleProp for $name {
499            type Type = $ty;
500            fn key() -> $crate::style::StyleKey {
501                static TRANSITION_INFO: $crate::style::StyleKeyInfo = $crate::style::StyleKeyInfo::Transition;
502                static INFO: $crate::style::StyleKeyInfo = $crate::style::StyleKeyInfo::Prop($crate::style::StylePropInfo::new::<$name, $ty>(
503                    prop!([impl inherited][$($options)*]),
504                    || std::rc::Rc::new($crate::style::StyleMapValue::Val($name::default_value())),
505                    $crate::style::StyleKey { info: &TRANSITION_INFO },
506                ));
507                $crate::style::StyleKey { info: &INFO }
508            }
509            fn default_value() -> Self::Type {
510                $default
511            }
512        }
513    };
514    ([impl inherited][inherited]) => {
515        true
516    };
517    ([impl inherited][]) => {
518        false
519    };
520}
521
522#[macro_export]
523macro_rules! prop_extractor {
524    (
525        $(#[$attrs:meta])* $vis:vis $name:ident {
526            $($prop_vis:vis $prop:ident: $reader:ty),*
527            $(,)?
528        }
529    ) => {
530        #[derive(Debug, Clone)]
531        $(#[$attrs])?
532        $vis struct $name {
533            $(
534                $prop_vis $prop: $crate::style::ExtractorField<$reader>,
535            )*
536        }
537
538        impl $name {
539            #[allow(dead_code)]
540            $vis fn read_style(&mut self, cx: &mut $crate::context::StyleCx, style: &$crate::style::Style) -> bool {
541                self.read_style_for(cx, style, cx.current_view().get_element_id())
542            }
543
544            #[allow(dead_code)]
545            $vis fn read_style_for(
546                &mut self,
547                cx: &mut $crate::context::StyleCx,
548                style: &$crate::style::Style,
549                target: impl Into<$crate::ElementId>,
550            ) -> bool {
551                let mut transition = false;
552                let changed = false $(
553                    | self.$prop.read(style, &cx.now(), &mut transition)
554                )*;
555                if transition {
556                    cx.request_transition_for(target);
557                }
558                changed
559            }
560
561           #[allow(dead_code)]
562            $vis fn read(&mut self, cx: &mut $crate::context::StyleCx) -> bool {
563                self.read_for(cx, cx.current_view().get_element_id())
564            }
565
566           #[allow(dead_code)]
567            $vis fn read_for(
568                &mut self,
569                cx: &mut $crate::context::StyleCx,
570                target: impl Into<$crate::ElementId>,
571            ) -> bool {
572                let mut transition = false;
573                let changed = self.read_explicit(
574                    &cx.direct_style(),
575                    &cx.now(),
576                    &mut transition,
577                );
578                if transition {
579                    cx.request_transition_for(target);
580                }
581                changed
582            }
583
584            #[allow(dead_code)]
585            $vis fn read_explicit(
586                &mut self,
587                style: &$crate::style::Style,
588                #[cfg(not(target_arch = "wasm32"))]
589                now: &std::time::Instant,
590                #[cfg(target_arch = "wasm32")]
591                now: &web_time::Instant,
592                request_transition: &mut bool
593            ) -> bool {
594                false $(| self.$prop.read(style, now, request_transition))*
595            }
596
597            $($prop_vis fn $prop(&self) -> <$reader as $crate::style::StylePropReader>::Type
598            {
599                self.$prop.get()
600            })*
601        }
602
603        impl Default for $name {
604            fn default() -> Self {
605                Self {
606                    $(
607                        $prop: $crate::style::ExtractorField::new(),
608                    )*
609                }
610            }
611        }
612    };
613}
614
615// ============================================================================
616// StyleKey
617// ============================================================================
618
619#[derive(Debug)]
620pub enum StyleKeyInfo {
621    Transition,
622    Prop(StylePropInfo),
623    Selector(StyleSelectors),
624    Class(StyleClassInfo),
625    DebugGroup(StyleDebugGroupInfo),
626    DeferredEffects,
627    /// Storage for parameterized structural selectors (`:first-child`, `:nth-child(...)`, etc.).
628    StructuralSelectors,
629    /// Storage for parameterized responsive selectors (`min/max/range` window width).
630    ResponsiveSelectors,
631}
632
633pub(crate) static STRUCTURAL_SELECTORS_INFO: StyleKeyInfo = StyleKeyInfo::StructuralSelectors;
634pub(crate) static RESPONSIVE_SELECTORS_INFO: StyleKeyInfo = StyleKeyInfo::ResponsiveSelectors;
635
636#[derive(Copy, Clone)]
637pub struct StyleKey {
638    pub info: &'static StyleKeyInfo,
639}
640
641impl StyleKey {
642    pub(crate) fn debug_any(&self, value: &dyn Any) -> String {
643        match self.info {
644            StyleKeyInfo::Selector(selectors) => selectors.debug_string(),
645            StyleKeyInfo::Transition
646            | StyleKeyInfo::DebugGroup(_)
647            | StyleKeyInfo::DeferredEffects
648            | StyleKeyInfo::StructuralSelectors
649            | StyleKeyInfo::ResponsiveSelectors => String::new(),
650            StyleKeyInfo::Class(info) => (info.name)().to_string(),
651            StyleKeyInfo::Prop(v) => (v.debug_any)(value),
652        }
653    }
654    pub(crate) fn inherited(&self) -> bool {
655        match self.info {
656            StyleKeyInfo::Selector(..)
657            | StyleKeyInfo::Transition
658            | StyleKeyInfo::DeferredEffects
659            | StyleKeyInfo::StructuralSelectors
660            | StyleKeyInfo::ResponsiveSelectors => false,
661            StyleKeyInfo::Class(..) => true,
662            StyleKeyInfo::DebugGroup(v) => v.inherited,
663            StyleKeyInfo::Prop(v) => v.inherited,
664        }
665    }
666}
667
668impl PartialEq for StyleKey {
669    fn eq(&self, other: &Self) -> bool {
670        ptr::eq(self.info, other.info)
671    }
672}
673
674impl Hash for StyleKey {
675    fn hash<H: Hasher>(&self, state: &mut H) {
676        state.write_usize(self.info as *const _ as usize)
677    }
678}
679
680impl Eq for StyleKey {}
681
682impl Debug for StyleKey {
683    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684        match self.info {
685            StyleKeyInfo::Selector(selectors) => {
686                write!(f, "selectors: {}", selectors.debug_string())
687            }
688            StyleKeyInfo::Transition => write!(f, "transition"),
689            StyleKeyInfo::DeferredEffects => write!(f, "DeferredEffects"),
690            StyleKeyInfo::StructuralSelectors => write!(f, "StructuralSelectors"),
691            StyleKeyInfo::ResponsiveSelectors => write!(f, "ResponsiveSelectors"),
692            StyleKeyInfo::Class(v) => write!(f, "{}", (v.name)()),
693            StyleKeyInfo::DebugGroup(v) => write!(f, "{}", (v.name)()),
694            StyleKeyInfo::Prop(v) => write!(f, "{}", (v.name)()),
695        }
696    }
697}