Skip to main content

ferroui_base/styling/
style_query.rs

1use super::activators::AndActivatorBuilder;
2use super::{ContainerQuery, SelectorMatch, SelectorMatchResult, StyleBase};
3use crate::StyledElement;
4use std::fmt;
5use std::rc::Rc;
6
7/// The behaviour of one kind of style query.
8pub(crate) trait StyleQueryNode: 'static {
9    /// Whether this query is a combinator.
10    ///
11    /// A combinator is a query such as child or descendent which links simple
12    /// queries.
13    fn is_combinator(&self) -> bool;
14
15    /// Gets a string representing the query, with the nesting separator
16    /// (`^`) replaced with the parent query.
17    fn to_string(&self, owner: Option<&ContainerQuery>) -> String;
18
19    /// Evaluates the query for a match.
20    fn evaluate(
21        &self,
22        control: &StyledElement,
23        parent: Option<&StyleBase>,
24        subscribe: bool,
25        container_name: Option<&str>,
26    ) -> SelectorMatch;
27
28    /// Moves to the previous query.
29    fn move_previous(&self) -> Option<&StyleQuery>;
30
31    /// Moves to the previous query or the parent query.
32    #[allow(dead_code)]
33    fn move_previous_or_parent(&self) -> Option<&StyleQuery>;
34}
35
36/// A query in a [`ContainerQuery`].
37///
38/// Queries are built with the functions of [`StyleQueries`](super::StyleQueries)
39/// and the chaining methods of this type.
40#[derive(Clone)]
41pub struct StyleQuery(pub(crate) Rc<dyn StyleQueryNode>);
42
43
44/// Compares by identity (reference equality), as the reference type this
45/// mirrors.
46impl PartialEq for StyleQuery {
47    #[inline]
48    fn eq(&self, other: &Self) -> bool {
49        std::ptr::addr_eq(Rc::as_ptr(&self.0), Rc::as_ptr(&other.0))
50    }
51}
52
53impl StyleQuery {
54    pub(crate) fn new(node: impl StyleQueryNode) -> Self {
55        StyleQuery(Rc::new(node))
56    }
57
58    #[inline]
59    pub(crate) fn is_combinator(&self) -> bool {
60        self.0.is_combinator()
61    }
62
63    /// Tries to match the query with a control.
64    ///
65    /// `parent` is the parent container, if the container holding the query
66    /// is a nested container. With `subscribe`, a match whose state can
67    /// change over time carries an activator that tracks it; without, the
68    /// match is only evaluated for the current state. `container_name` is the
69    /// name of the container to query.
70    pub fn match_(
71        &self,
72        control: &StyledElement,
73        parent: Option<&StyleBase>,
74        subscribe: bool,
75        container_name: Option<&str>,
76    ) -> SelectorMatch {
77        // First match the query until a combinator is found. Queries are
78        // stored from right-to-left, so the recursion reverses this order
79        // because the type query will be on the left.
80        let mut combinator = None;
81        let mut activators = AndActivatorBuilder::new();
82        let result =
83            Self::match_query(control, self, parent, subscribe, &mut activators, &mut combinator, container_name);
84        let mut match_ = if result == SelectorMatchResult::Sometimes {
85            SelectorMatch::sometimes(activators.get())
86        } else {
87            SelectorMatch::new(result)
88        };
89
90        // If the pre-combinator query matches, we can now match the
91        // combinator, if any.
92        if match_.is_match() {
93            if let Some(combinator) = combinator {
94                match_ = match_.and(combinator.match_(control, parent, subscribe, container_name));
95
96                // If we have a combinator then we can never say that we always
97                // match a control of this type, because by definition the
98                // combinator matches on things outside of the control.
99                match_ = match match_.result() {
100                    SelectorMatchResult::AlwaysThisType => SelectorMatch::ALWAYS_THIS_INSTANCE,
101                    SelectorMatchResult::NeverThisType => SelectorMatch::NEVER_THIS_INSTANCE,
102                    _ => match_,
103                };
104            }
105        }
106
107        match_
108    }
109
110    /// Evaluates the query for a match.
111    pub(crate) fn evaluate(
112        &self,
113        control: &StyledElement,
114        parent: Option<&StyleBase>,
115        subscribe: bool,
116        container_name: Option<&str>,
117    ) -> SelectorMatch {
118        self.0.evaluate(control, parent, subscribe, container_name)
119    }
120
121    /// Gets a string representing the query.
122    pub fn to_string_with_owner(&self, owner: Option<&ContainerQuery>) -> String {
123        self.0.to_string(owner)
124    }
125
126    fn match_query<'a>(
127        control: &StyledElement,
128        query: &'a StyleQuery,
129        parent: Option<&StyleBase>,
130        subscribe: bool,
131        activators: &mut AndActivatorBuilder,
132        combinator: &mut Option<&'a StyleQuery>,
133        container_name: Option<&str>,
134    ) -> SelectorMatchResult {
135        let previous = query.0.move_previous();
136
137        // Queries are stored from right-to-left, so we recurse into the query
138        // in order to reverse this order, because the type query will be on
139        // the left and is our best opportunity to exit early.
140        if let Some(previous) = previous {
141            if !previous.is_combinator() {
142                let previous_match =
143                    Self::match_query(control, previous, parent, subscribe, activators, combinator, container_name);
144
145                if previous_match < SelectorMatchResult::Sometimes {
146                    return previous_match;
147                }
148            }
149        }
150
151        // Match this query.
152        let match_ = query.0.evaluate(control, parent, subscribe, container_name);
153
154        if !match_.is_match() {
155            *combinator = None;
156            return match_.result();
157        }
158
159        let result = match_.result();
160        activators.add(match_.into_activator());
161
162        if let Some(previous) = previous {
163            if previous.is_combinator() {
164                *combinator = Some(previous);
165            }
166        }
167
168        result
169    }
170}
171
172impl fmt::Display for StyleQuery {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        f.write_str(&self.0.to_string(None))
175    }
176}
177
178impl fmt::Debug for StyleQuery {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.write_str(&self.0.to_string(None))
181    }
182}