Skip to main content

ferroui_base/styling/
container_query.rs

1use super::{
2    DuplicateSetterError, ControlTheme, SelectorMatch, SelectorMatchResult, StyleBase, StyleBaseImpl, StyleBaseImplExt, StyleHostRef,
3    StyleQuery,
4};
5use crate::property_store::FrameType;
6use crate::{ferro_class, instantiate, FerroObjectImpl, Ref, StyledElement};
7use std::cell::RefCell;
8
9/// Defines a container: a set of styles that apply while a query on an
10/// ancestor container of the control matches.
11#[repr(C)]
12pub struct ContainerQuery {
13    base: StyleBase,
14    query: RefCell<Option<StyleQuery>>,
15    name: RefCell<Option<String>>,
16}
17
18ferro_class!(ContainerQuery: StyleBase);
19crate::ferro_class_info!(ContainerQuery { new: ContainerQuery::new });
20
21impl FerroObjectImpl for ContainerQuery {}
22
23impl StyleBaseImpl for ContainerQuery {
24    fn set_parent(this: &Self, parent: Option<&Ref<StyleBase>>) {
25        if parent.is_some_and(|p| p.is::<ControlTheme>()) {
26            Self::parent_set_parent(this, parent);
27        } else {
28            panic!("Container cannot be added as a nested style.");
29        }
30    }
31
32    /// Returns a string representation of the container.
33    fn to_display_string(this: &Self) -> String {
34        match &*this.query.borrow() {
35            Some(query) => query.to_string_with_owner(Some(this)),
36            None => "ContainerQuery".to_string(),
37        }
38    }
39}
40
41impl ContainerQuery {
42    /// Creates the class data; see [`FerroObject::construct`](crate::FerroObject::construct).
43    pub fn construct() -> Self {
44        Self { base: StyleBase::construct(), query: RefCell::new(None), name: RefCell::new(None) }
45    }
46
47    /// Creates a container without a query.
48    pub fn new() -> Ref<Self> {
49        instantiate(Self::construct())
50    }
51
52    /// Creates a container with a query and, optionally, the name of the
53    /// container to query.
54    pub fn with_query(query: StyleQuery, container_name: Option<String>) -> Ref<Self> {
55        let result = Self::new();
56        result.set_query(Some(query));
57        result.set_name(container_name);
58        result
59    }
60
61    /// Creates a container from a function that builds its query and,
62    /// optionally, the name of the container to query.
63    pub fn with_query_fn(
64        query: impl FnOnce(Option<StyleQuery>) -> StyleQuery,
65        container_name: Option<String>,
66    ) -> Ref<Self> {
67        Self::with_query(query(None), container_name)
68    }
69
70    /// The container's query.
71    pub fn query(&self) -> Option<StyleQuery> {
72        self.query.borrow().clone()
73    }
74
75    pub fn set_query(&self, value: Option<StyleQuery>) {
76        *self.query.borrow_mut() = value;
77    }
78
79    /// The name of the container to query.
80    pub fn name(&self) -> Option<String> {
81        self.name.borrow().clone()
82    }
83
84    pub fn set_name(&self, value: Option<String>) {
85        *self.name.borrow_mut() = value;
86    }
87
88    /// As [`try_attach_checked`](Self::try_attach_checked); a duplicate
89    /// setter panics with the message of the error.
90    #[allow(dead_code)]
91    pub(crate) fn try_attach(
92        &self,
93        target: &StyledElement,
94        host: Option<&StyleHostRef>,
95        type_: FrameType,
96    ) -> SelectorMatchResult {
97        match self.try_attach_checked(target, host, type_) {
98            Ok(result) => result,
99            Err(error) => panic!("{error}"),
100        }
101    }
102
103    /// Attaches the style to `target` if it matches. A style with two
104    /// setters for the same property is an error.
105    pub(crate) fn try_attach_checked(
106        &self,
107        target: &StyledElement,
108        host: Option<&StyleHostRef>,
109        type_: FrameType,
110    ) -> Result<SelectorMatchResult, DuplicateSetterError> {
111        let mut result = SelectorMatchResult::NeverThisType;
112
113        if self.has_children() {
114            crate::perf_count!(StylesEvaluated);
115            let parent = self.parent();
116            let match_ = match self.query() {
117                Some(query) => query.match_(target, parent.as_deref(), true, self.name().as_deref()),
118                None => {
119                    let is_host = host
120                        .and_then(StyleHostRef::as_element)
121                        .is_some_and(|h| std::ptr::eq::<StyledElement>(&**h, target));
122                    if is_host {
123                        SelectorMatch::ALWAYS_THIS_INSTANCE
124                    } else {
125                        SelectorMatch::NEVER_THIS_INSTANCE
126                    }
127                }
128            };
129
130            result = match_.result();
131
132            if match_.is_match() {
133                crate::perf_count!(StylesMatched);
134                self.try_attach_instance(target, match_.into_activator(), type_, true)?;
135            }
136        }
137
138        Ok(result)
139    }
140}