Skip to main content

ferroui_base/styling/
control_theme.rs

1use super::{DuplicateSetterError, SelectorMatchResult, Setter, StyleBase, StyleBaseImpl};
2use crate::property_store::FrameType;
3use crate::{ferro_class, instantiate, FerroObjectImpl, ObjectType, Ref, StyledElement, TypeInfo};
4use std::cell::{Cell, RefCell};
5use std::rc::Rc;
6
7/// Defines a switchable theme for a control.
8#[repr(C)]
9pub struct ControlTheme {
10    base: StyleBase,
11    target_type: Cell<Option<&'static TypeInfo>>,
12    based_on: RefCell<Option<Ref<ControlTheme>>>,
13}
14
15ferro_class!(ControlTheme: StyleBase);
16crate::ferro_class_info!(ControlTheme { new: ControlTheme::new });
17
18impl FerroObjectImpl for ControlTheme {}
19
20impl StyleBaseImpl for ControlTheme {
21    fn set_parent(_this: &Self, _parent: Option<&Ref<StyleBase>>) {
22        panic!("ControlThemes cannot be added as a nested style.");
23    }
24
25    fn to_display_string(this: &Self) -> String {
26        match this.target_type.get() {
27            Some(target_type) => target_type.name().to_string(),
28            None => "ControlTheme".to_string(),
29        }
30    }
31}
32
33impl ControlTheme {
34    /// Creates the class data; see [`FerroObject::construct`](crate::FerroObject::construct).
35    pub fn construct() -> Self {
36        Self { base: StyleBase::construct(), target_type: Cell::new(None), based_on: RefCell::new(None) }
37    }
38
39    /// Creates a control theme without a target type.
40    pub fn new() -> Ref<Self> {
41        instantiate(Self::construct())
42    }
43
44    /// Creates a control theme for the given control type.
45    pub fn with_target_type(target_type: &'static TypeInfo) -> Ref<Self> {
46        let theme = Self::new();
47        theme.set_target_type(Some(target_type));
48        theme
49    }
50
51    /// Creates a control theme for control class `T`.
52    pub fn for_type<T: ObjectType>() -> Ref<Self> {
53        Self::with_target_type(T::TYPE)
54    }
55
56    /// The type for which this control theme is intended.
57    pub fn target_type(&self) -> Option<&'static TypeInfo> {
58        self.target_type.get()
59    }
60
61    pub fn set_target_type(&self, value: Option<&'static TypeInfo>) {
62        self.target_type.set(value)
63    }
64
65    /// A control theme that is the basis of the current theme.
66    pub fn based_on(&self) -> Option<Ref<ControlTheme>> {
67        self.based_on.borrow().clone()
68    }
69
70    pub fn set_based_on(&self, value: Option<Ref<ControlTheme>>) {
71        *self.based_on.borrow_mut() = value;
72    }
73
74    /// Creates a control theme for the given control type with setters.
75    pub fn with_setters(
76        target_type: &'static TypeInfo,
77        setters: impl IntoIterator<Item = Rc<Setter>>,
78    ) -> Ref<Self> {
79        let theme = Self::with_target_type(target_type);
80        for setter in setters {
81            theme.add_setter(setter);
82        }
83        theme
84    }
85
86    /// As [`try_attach_checked`](Self::try_attach_checked); a duplicate
87    /// setter panics with the message of the error.
88    #[allow(dead_code)]
89    pub(crate) fn try_attach(&self, target: &StyledElement, type_: FrameType) -> SelectorMatchResult {
90        match self.try_attach_checked(target, type_) {
91            Ok(result) => result,
92            Err(error) => panic!("{error}"),
93        }
94    }
95
96    /// Attaches the theme to `target` if it applies to it. A theme with two
97    /// setters for the same property is an error.
98    pub(crate) fn try_attach_checked(
99        &self,
100        target: &StyledElement,
101        type_: FrameType,
102    ) -> Result<SelectorMatchResult, DuplicateSetterError> {
103        debug_assert!(matches!(type_, FrameType::Theme | FrameType::TemplatedParentTheme));
104
105        let Some(target_type) = self.target_type.get() else {
106            panic!("ControlTheme has no TargetType.");
107        };
108        crate::perf_count!(ControlThemesEvaluated);
109
110        if self.has_setters_or_animations() && target_type.is_assignable_from(target.style_key()) {
111            crate::perf_count!(ControlThemesMatched);
112            self.try_attach_instance(target, None, type_, true)?;
113            return Ok(SelectorMatchResult::AlwaysThisType);
114        }
115
116        Ok(SelectorMatchResult::NeverThisType)
117    }
118}