Skip to main content

ferroui_base/styling/
selector.rs

1use super::activators::AndActivatorBuilder;
2use super::{
3    ContainerQuery, NestingSelector, SelectorMatch, SelectorMatchResult, Style, StyleBase, TemplateSelector,
4};
5use crate::{StyledElement, TypeInfo};
6use std::any::Any;
7use std::fmt;
8use std::rc::Rc;
9
10/// The behaviour of one kind of selector.
11pub trait SelectorNode: 'static {
12    /// Whether this selector is in a template.
13    fn in_template(&self) -> bool;
14
15    /// Whether this selector is a combinator.
16    ///
17    /// A combinator is a selector such as child or descendent which links
18    /// simple selectors.
19    fn is_combinator(&self) -> bool;
20
21    /// The target type of the selector, if available.
22    fn target_type(&self) -> Option<&'static TypeInfo>;
23
24    /// Gets a string representing the selector, with the nesting separator
25    /// (`^`) replaced with the parent selector.
26    fn to_string(&self, owner: Option<&Style>) -> String;
27
28    fn to_string_with_next(&self, owner: Option<&Style>, _has_next: bool) -> String {
29        self.to_string(owner)
30    }
31
32    /// Evaluates the selector for a match.
33    fn evaluate(&self, control: &StyledElement, parent: Option<&StyleBase>, subscribe: bool) -> SelectorMatch;
34
35    /// Moves to the previous selector.
36    fn move_previous(&self) -> Option<&Selector>;
37
38    /// Moves to the previous selector or the parent selector.
39    fn move_previous_or_parent(&self) -> Option<&Selector>;
40
41    fn validate_nesting_selector(&self, in_control_theme: bool, mut template_count: i32) {
42        if in_control_theme {
43            if !self.in_template() && self.is_combinator() {
44                panic!("ControlTheme style may not directly contain a child or descendent selector.");
45            }
46            if self.as_any().is::<TemplateSelector>() {
47                let count = template_count;
48                template_count += 1;
49                if count > 0 {
50                    panic!("ControlTemplate styles cannot contain multiple template selectors.");
51                }
52            }
53        }
54
55        match self.move_previous_or_parent() {
56            None => {
57                if !self.as_any().is::<NestingSelector>() {
58                    panic!("Child styles must have a nesting selector.");
59                }
60            }
61            Some(previous) => previous.0.validate_nesting_selector(in_control_theme, template_count),
62        }
63    }
64
65    fn as_any(&self) -> &dyn Any;
66}
67
68/// A selector in a [`Style`].
69///
70/// Selectors are built with the functions of [`Selectors`](super::Selectors)
71/// and the chaining methods of this type. A selector is a shared, immutable
72/// description: cloning the handle is cheap.
73#[derive(Clone)]
74pub struct Selector(pub(crate) Rc<dyn SelectorNode>);
75
76
77/// Compares by identity (reference equality), as the reference type this
78/// mirrors.
79impl PartialEq for Selector {
80    #[inline]
81    fn eq(&self, other: &Self) -> bool {
82        std::ptr::addr_eq(Rc::as_ptr(&self.0), Rc::as_ptr(&other.0))
83    }
84}
85
86impl Selector {
87    pub(crate) fn new(node: impl SelectorNode) -> Self {
88        Selector(Rc::new(node))
89    }
90
91    /// Whether this selector is in a template.
92    #[inline]
93    pub(crate) fn in_template(&self) -> bool {
94        self.0.in_template()
95    }
96
97    /// Whether this selector is a combinator.
98    #[inline]
99    pub(crate) fn is_combinator(&self) -> bool {
100        self.0.is_combinator()
101    }
102
103    /// The target type of the selector, if available.
104    #[inline]
105    pub fn target_type(&self) -> Option<&'static TypeInfo> {
106        self.0.target_type()
107    }
108
109    pub(crate) fn node<T: 'static>(&self) -> Option<&T> {
110        self.0.as_any().downcast_ref::<T>()
111    }
112
113    /// Tries to match the selector with a control.
114    ///
115    /// `parent` is the parent style, if the style containing the selector is
116    /// a nested style. With `subscribe`, a match whose state can change over
117    /// time carries an activator that tracks it; without, the match is only
118    /// evaluated for the current state.
119    pub fn match_(&self, control: &StyledElement, parent: Option<&StyleBase>, subscribe: bool) -> SelectorMatch {
120        // First match the selector until a combinator is found. Selectors are
121        // stored from right-to-left, so the recursion reverses this order
122        // because the type selector will be on the left.
123        let (mut match_, combinator) = Self::match_until_combinator(control, self, parent, subscribe);
124
125        // If the pre-combinator selector matches, we can now match the
126        // combinator, if any.
127        if match_.is_match() {
128            if let Some(combinator) = combinator {
129                match_ = match_.and(combinator.match_(control, parent, subscribe));
130
131                // If we have a combinator then we can never say that we always
132                // match a control of this type, because by definition the
133                // combinator matches on things outside of the control.
134                match_ = match match_.result() {
135                    SelectorMatchResult::AlwaysThisType => SelectorMatch::ALWAYS_THIS_INSTANCE,
136                    SelectorMatchResult::NeverThisType => SelectorMatch::NEVER_THIS_INSTANCE,
137                    _ => match_,
138                };
139            }
140        }
141
142        match_
143    }
144
145    /// Gets a string representing the selector, with the nesting separator
146    /// (`^`) replaced with the parent selector.
147    pub fn to_string_with_owner(&self, owner: Option<&Style>) -> String {
148        self.0.to_string(owner)
149    }
150
151    pub(crate) fn to_string_with_next(&self, owner: Option<&Style>, has_next: bool) -> String {
152        self.0.to_string_with_next(owner, has_next)
153    }
154
155    pub(crate) fn validate_nesting_selector(&self, in_control_theme: bool) {
156        self.0.validate_nesting_selector(in_control_theme, 0)
157    }
158
159    fn match_until_combinator<'a>(
160        control: &StyledElement,
161        start: &'a Selector,
162        parent: Option<&StyleBase>,
163        subscribe: bool,
164    ) -> (SelectorMatch, Option<&'a Selector>) {
165        let mut combinator = None;
166        let mut activators = AndActivatorBuilder::new();
167        let result = Self::match_selector(control, start, parent, subscribe, &mut activators, &mut combinator);
168        let match_ = if result == SelectorMatchResult::Sometimes {
169            SelectorMatch::sometimes(activators.get())
170        } else {
171            SelectorMatch::new(result)
172        };
173        (match_, combinator)
174    }
175
176    fn match_selector<'a>(
177        control: &StyledElement,
178        selector: &'a Selector,
179        parent: Option<&StyleBase>,
180        subscribe: bool,
181        activators: &mut AndActivatorBuilder,
182        combinator: &mut Option<&'a Selector>,
183    ) -> SelectorMatchResult {
184        let previous = selector.0.move_previous();
185
186        // Selectors are stored from right-to-left, so we recurse into the
187        // selector in order to reverse this order, because the type selector
188        // will be on the left and is our best opportunity to exit early.
189        if let Some(previous) = previous {
190            if !previous.is_combinator() {
191                let previous_match =
192                    Self::match_selector(control, previous, parent, subscribe, activators, combinator);
193
194                if previous_match < SelectorMatchResult::Sometimes {
195                    return previous_match;
196                }
197            }
198        }
199
200        let mut container_matches_sometimes = false;
201
202        // Match any parent container query.
203        if let Some(container) = parent.and_then(|p| p.downcast_ref::<ContainerQuery>()) {
204            let match_ = match container.query() {
205                Some(query) => {
206                    let container_parent = container.parent();
207                    query.evaluate(control, container_parent.as_deref(), subscribe, container.name().as_deref())
208                }
209                None => SelectorMatch::NEVER_THIS_INSTANCE,
210            };
211
212            if !match_.is_match() {
213                return match_.result();
214            }
215
216            container_matches_sometimes = match_.result() == SelectorMatchResult::Sometimes;
217
218            if container_matches_sometimes {
219                activators.add(match_.into_activator());
220            }
221        }
222
223        // Match this selector.
224        let match_ = selector.0.evaluate(control, parent, subscribe);
225
226        if !match_.is_match() {
227            *combinator = None;
228            return match_.result();
229        }
230
231        let result = match_.result();
232        activators.add(match_.into_activator());
233
234        if let Some(previous) = previous {
235            if previous.is_combinator() {
236                *combinator = Some(previous);
237            }
238        }
239
240        if container_matches_sometimes {
241            SelectorMatchResult::Sometimes
242        } else {
243            result
244        }
245    }
246}
247
248impl fmt::Display for Selector {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        f.write_str(&self.0.to_string(None))
251    }
252}
253
254impl fmt::Debug for Selector {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.write_str(&self.0.to_string(None))
257    }
258}