Skip to main content

ferroui_base/styling/
style.rs

1use super::{
2    DuplicateSetterError, ContainerQuery, ControlTheme, OrSelector, Selector, SelectorMatch, SelectorMatchResult, Setter, StyleBase,
3    StyleBaseImpl, StyleBaseImplExt, StyleHostRef, TemplateSelector,
4};
5use crate::property_store::FrameType;
6use crate::{ferro_class, instantiate, FerroObjectImpl, Ref, StyledElement};
7use std::cell::RefCell;
8use std::rc::Rc;
9
10/// Defines a style.
11#[repr(C)]
12pub struct Style {
13    base: StyleBase,
14    selector: RefCell<Option<Selector>>,
15}
16
17ferro_class!(Style: StyleBase);
18crate::ferro_class_info!(Style { new: Style::new });
19
20impl FerroObjectImpl for Style {}
21
22impl StyleBaseImpl for Style {
23    fn set_parent(this: &Self, parent: Option<&Ref<StyleBase>>) {
24        if let Some(parent) = parent {
25            let parent_has_selector = parent.downcast_ref::<Style>().is_some_and(|s| s.selector.borrow().is_some());
26            if parent_has_selector {
27                match &*this.selector.borrow() {
28                    None => panic!("Child styles must have a selector."),
29                    Some(selector) => selector.validate_nesting_selector(false),
30                }
31            } else if parent.is::<ControlTheme>() {
32                match &*this.selector.borrow() {
33                    None => panic!("Child styles must have a selector."),
34                    Some(selector) => selector.validate_nesting_selector(true),
35                }
36            }
37        }
38
39        Self::parent_set_parent(this, parent);
40    }
41
42    /// Returns a string representation of the style.
43    fn to_display_string(this: &Self) -> String {
44        match &*this.selector.borrow() {
45            Some(selector) => selector.to_string_with_owner(Some(this)),
46            None => "Style".to_string(),
47        }
48    }
49}
50
51impl Style {
52    /// Creates the class data; see [`FerroObject::construct`](crate::FerroObject::construct).
53    pub fn construct() -> Self {
54        Self { base: StyleBase::construct(), selector: RefCell::new(None) }
55    }
56
57    /// Creates a style without a selector.
58    pub fn new() -> Ref<Self> {
59        instantiate(Self::construct())
60    }
61
62    /// Creates a style with a selector.
63    pub fn with_selector(selector: Selector) -> Ref<Self> {
64        let style = Self::new();
65        style.set_selector(Some(selector));
66        style
67    }
68
69    /// Creates a style whose selector is built by `selector`.
70    pub fn with_selector_fn(selector: impl FnOnce(Option<Selector>) -> Selector) -> Ref<Self> {
71        Self::with_selector(selector(None))
72    }
73
74    /// The style's selector.
75    pub fn selector(&self) -> Option<Selector> {
76        self.selector.borrow().clone()
77    }
78
79    pub fn set_selector(&self, value: Option<Selector>) {
80        *self.selector.borrow_mut() = Self::validate_selector(value);
81    }
82
83    /// Creates a style with a selector and setters.
84    pub fn with_setters(selector: Selector, setters: impl IntoIterator<Item = Rc<Setter>>) -> Ref<Self> {
85        let style = Self::with_selector(selector);
86        for setter in setters {
87            style.add_setter(setter);
88        }
89        style
90    }
91
92    /// As [`try_attach_checked`](Self::try_attach_checked); a duplicate
93    /// setter panics with the message of the error.
94    #[allow(dead_code)]
95    pub(crate) fn try_attach(
96        &self,
97        target: &StyledElement,
98        host: Option<&StyleHostRef>,
99        type_: FrameType,
100    ) -> SelectorMatchResult {
101        match self.try_attach_checked(target, host, type_) {
102            Ok(result) => result,
103            Err(error) => panic!("{error}"),
104        }
105    }
106
107    /// Attaches the style to `target` if it matches. A style with two
108    /// setters for the same property is an error.
109    pub(crate) fn try_attach_checked(
110        &self,
111        target: &StyledElement,
112        host: Option<&StyleHostRef>,
113        type_: FrameType,
114    ) -> Result<SelectorMatchResult, DuplicateSetterError> {
115        let mut result = SelectorMatchResult::NeverThisType;
116
117        if self.has_setters_or_animations() {
118            crate::perf_count!(StylesEvaluated);
119            let parent = self.parent();
120            let (match_, can_share) = {
121                let selector = self.selector.borrow();
122                match &*selector {
123                    Some(selector) => {
124                        (selector.match_(target, parent.as_deref(), true), selector.node::<OrSelector>().is_none())
125                    }
126                    None => {
127                        let is_host =
128                            host.and_then(StyleHostRef::as_element).is_some_and(|h| std::ptr::eq::<StyledElement>(&**h, target));
129                        let match_ = if is_host {
130                            match parent.as_deref().and_then(|p| p.downcast_ref::<ContainerQuery>()) {
131                                Some(container_query) => match container_query.query() {
132                                    Some(query) => {
133                                        let query_parent = container_query.parent();
134                                        query.match_(
135                                            target,
136                                            query_parent.as_deref(),
137                                            true,
138                                            container_query.name().as_deref(),
139                                        )
140                                    }
141                                    None => SelectorMatch::NEVER_THIS_INSTANCE,
142                                },
143                                None => SelectorMatch::ALWAYS_THIS_INSTANCE,
144                            }
145                        } else {
146                            SelectorMatch::NEVER_THIS_INSTANCE
147                        };
148                        (match_, true)
149                    }
150                }
151            };
152
153            result = match_.result();
154
155            if match_.is_match() {
156                crate::perf_count!(StylesMatched);
157                self.try_attach_instance(target, match_.into_activator(), type_, can_share)?;
158            }
159        }
160
161        Ok(result)
162    }
163
164    fn validate_selector(selector: Option<Selector>) -> Option<Selector> {
165        if selector.as_ref().is_some_and(|s| s.node::<TemplateSelector>().is_some()) {
166            panic!("Invalid selector: Template selector must be followed by control selector.");
167        }
168        selector
169    }
170}