Skip to main content

ferroui_base/styling/
style_base.rs

1use super::activators::IStyleActivator;
2use super::{
3    empty_styles, DuplicateSetterError, IStyle, Setter, SetterBase, StyleChildren, StyleInstance, ThemeVariant,
4};
5use crate::metadata::{from_markup_value, IAddChild};
6use crate::controls::{
7    IResourceNode, IResourceProvider, ResourceDictionary, ResourceHostRef, ResourceKey, ResourceValue,
8    ResourcesChangedEventArgs, WeakResourceHost,
9};
10use crate::property_store::{FrameType, ValueFrame};
11use crate::reactive::{Disposable, IDisposable};
12use crate::animation::IAnimation;
13use crate::collections::FerroList;
14use crate::utilities::HandlerList;
15use crate::{ferro_class, BoxedValue, FerroObject, FerroObjectImpl, ObjectType, Ref, StyledElement, Upcast, WeakRef};
16use std::cell::{Cell, OnceCell, RefCell};
17use std::rc::Rc;
18
19/// Base class for [`Style`](super::Style), [`ControlTheme`](super::ControlTheme)
20/// and [`ContainerQuery`](super::ContainerQuery).
21#[repr(C)]
22pub struct StyleBase {
23    base: FerroObject,
24    owner: RefCell<Option<WeakResourceHost>>,
25    children: OnceCell<StyleChildren>,
26    resources: RefCell<Option<Ref<ResourceDictionary>>>,
27    setters: OnceCell<FerroList<Rc<dyn SetterBase>>>,
28    animations: OnceCell<FerroList<Rc<dyn IAnimation>>>,
29    // Shared style instances are cached per frame type. Most styles only ever have a single shared instance,
30    // so a plain field serves that case, upgraded lazily to an array indexed by frame type when a second type is shared.
31    shared_instance: RefCell<Option<Rc<StyleInstance>>>,
32    shared_instance_type: Cell<FrameType>,
33    shared_instances: RefCell<Option<Box<[Option<Rc<StyleInstance>>; 3]>>>,
34    parent: RefCell<Option<WeakRef<StyleBase>>>,
35    owner_changed: HandlerList<dyn Fn()>,
36}
37
38ferro_class! {
39    StyleBase: FerroObject, virtuals StyleBaseImpl: FerroObjectImpl {
40        /// Sets the parent style. Called when the style is added to or
41        /// removed from the children of another style; validates that the
42        /// style can be nested in `parent`.
43        fn set_parent(this, parent: Option<&Ref<StyleBase>>);
44        /// A string describing the style: its selector, target type or
45        /// query.
46        fn to_display_string(this) -> String;
47    }
48}
49
50crate::ferro_class_info!(StyleBase {
51    interfaces: [Rc<dyn IStyle>, Rc<dyn IResourceProvider>, Rc<dyn IAddChild<BoxedValue>>],
52});
53
54/// Implements the child-adding contract (the untyped one: the child is any
55/// object) for the style behind a class handle.
56struct StyleBaseAddChild(Ref<StyleBase>);
57
58impl IAddChild<BoxedValue> for StyleBaseAddChild {
59    fn add_child(&self, child: BoxedValue) {
60        self.0.add_child(&child)
61    }
62
63    fn reference_id(&self) -> usize {
64        &*self.0 as *const StyleBase as usize
65    }
66}
67
68impl From<Ref<StyleBase>> for Rc<dyn IAddChild<BoxedValue>> {
69    fn from(value: Ref<StyleBase>) -> Self {
70        Rc::new(StyleBaseAddChild(value))
71    }
72}
73
74impl FerroObjectImpl for StyleBase {}
75
76impl StyleBaseImpl for StyleBase {
77    fn set_parent(this: &Self, parent: Option<&Ref<StyleBase>>) {
78        *this.parent.borrow_mut() = parent.map(Ref::downgrade);
79    }
80
81    fn to_display_string(this: &Self) -> String {
82        this.get_type().name().to_string()
83    }
84}
85
86impl StyleBase {
87    /// Creates the class data; see [`FerroObject::construct`].
88    pub fn construct() -> Self {
89        Self {
90            base: FerroObject::construct(),
91            owner: RefCell::new(None),
92            children: OnceCell::new(),
93            resources: RefCell::new(None),
94            setters: OnceCell::new(),
95            animations: OnceCell::new(),
96            shared_instance: RefCell::new(None),
97            shared_instance_type: Cell::new(FrameType::Style),
98            shared_instances: RefCell::new(None),
99            parent: RefCell::new(None),
100            owner_changed: HandlerList::new(),
101        }
102    }
103
104    /// The children of the style.
105    pub fn children(&self) -> StyleChildren {
106        self.children.get_or_init(|| StyleChildren::new(self.to_ref().downgrade())).clone()
107    }
108
109    /// The owner of the style: the resource host whose styles collection the
110    /// style, or an ancestor of it, was added to.
111    pub fn owner(&self) -> Option<ResourceHostRef> {
112        let owner = self.owner.borrow().clone();
113        owner.and_then(|o| o.upgrade())
114    }
115
116    fn set_owner(&self, value: Option<&ResourceHostRef>) {
117        let old = self.owner();
118        if old.as_ref() != value {
119            *self.owner.borrow_mut() = value.map(ResourceHostRef::downgrade);
120            if !self.owner_changed.is_empty() {
121                for (_, handler) in self.owner_changed.snapshot().iter() {
122                    handler();
123                }
124            }
125        }
126    }
127
128    /// Raised when the owner of the style changes.
129    pub fn owner_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
130        self.subscribe_owner_changed(Rc::new(handler))
131    }
132
133    fn subscribe_owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
134        let token = self.owner_changed.add(handler);
135        let weak = self.to_ref().downgrade();
136        Disposable::create(move || {
137            if let Some(this) = weak.upgrade() {
138                this.owner_changed.remove(token);
139            }
140        })
141    }
142
143    /// The parent style, if the style is a nested style.
144    pub fn parent(&self) -> Option<Ref<StyleBase>> {
145        let parent = self.parent.borrow();
146        parent.as_ref().and_then(WeakRef::upgrade)
147    }
148
149    /// The dictionary of style resources.
150    pub fn resources(&self) -> Ref<ResourceDictionary> {
151        if let Some(resources) = &*self.resources.borrow() {
152            return resources.clone();
153        }
154        let resources = ResourceDictionary::new();
155        self.set_resources(resources.clone());
156        resources
157    }
158
159    pub fn set_resources(&self, value: Ref<ResourceDictionary>) {
160        let had_resources = self.resources.borrow().as_ref().is_some_and(|r| r.has_resources());
161
162        *self.resources.borrow_mut() = Some(value.clone());
163
164        if let Some(owner) = self.owner() {
165            value.add_owner(&owner);
166
167            if had_resources || value.has_resources() {
168                owner.notify_hosted_resources_changed(ResourcesChangedEventArgs::create());
169            }
170        }
171    }
172
173    /// The style's setters. The list is returned by handle: clones refer
174    /// to the same list.
175    pub fn setters(&self) -> FerroList<Rc<dyn SetterBase>> {
176        self.setters.get_or_init(FerroList::new).clone()
177    }
178
179    /// Adds a setter to the style.
180    pub fn add_setter(&self, setter: Rc<dyn SetterBase>) {
181        self.setters().add(setter);
182    }
183
184    /// Removes all setters from the style.
185    pub fn clear_setters(&self) {
186        if let Some(setters) = self.setters.get() {
187            setters.clear();
188        }
189    }
190
191    /// Adds a setter to the style.
192    pub fn add(&self, setter: Rc<dyn SetterBase>) {
193        self.add_setter(setter)
194    }
195
196    /// The style's animations. The list is returned by handle: clones
197    /// refer to the same list.
198    pub fn animations(&self) -> FerroList<Rc<dyn IAnimation>> {
199        self.animations.get_or_init(FerroList::new).clone()
200    }
201
202    /// Adds an animation to the style: it runs on every control the style
203    /// applies to, while the style is active.
204    pub fn add_animation(&self, animation: impl Into<Rc<dyn IAnimation>>) {
205        self.animations().add(animation.into());
206    }
207
208    /// Removes all animations from the style.
209    pub fn clear_animations(&self) {
210        if let Some(animations) = self.animations.get() {
211            animations.clear();
212        }
213    }
214
215    /// Adds an untyped child: a setter is added to the setters, a style to
216    /// the child styles. Panics for anything else.
217    pub fn add_child(&self, child: &BoxedValue) {
218        if let Some(setter) = child.downcast_ref::<Rc<Setter>>() {
219            let setter: Rc<dyn SetterBase> = setter.clone();
220            self.setters().add(setter);
221            return;
222        }
223        let untyped = Some(child.clone());
224        if let Some(setter) = from_markup_value::<Rc<dyn SetterBase>>(&untyped) {
225            self.setters().add(setter);
226        } else if let Some(style) = from_markup_value::<Rc<dyn IStyle>>(&untyped) {
227            self.children().add(style);
228        } else {
229            panic!("Cannot add {} to a style.", child.type_name());
230        }
231    }
232
233    /// Adds a child style.
234    pub fn add_style(&self, style: impl Into<Rc<dyn IStyle>>) {
235        self.children().add(style)
236    }
237
238    #[inline]
239    pub(crate) fn has_children(&self) -> bool {
240        self.children.get().is_some_and(|c| !c.is_empty())
241    }
242
243    #[inline]
244    pub(crate) fn has_setters_or_animations(&self) -> bool {
245        self.setters.get().is_some_and(|s| !s.is_empty()) || self.animations.get().is_some_and(|a| !a.is_empty())
246    }
247
248    /// Tries to find a resource in the style's resources and then in its
249    /// child styles.
250    pub fn try_get_resource(&self, key: &ResourceKey, theme_variant: Option<&ThemeVariant>) -> Option<ResourceValue> {
251        let resources = self.resources.borrow().clone();
252        if let Some(resources) = resources {
253            if let Some(result) = resources.try_get_resource(key, theme_variant) {
254                return Some(result);
255            }
256        }
257
258        if let Some(children) = self.children.get() {
259            if !children.is_empty() {
260                for child in children.snapshot().iter() {
261                    if let Some(result) = child.try_get_resource(key, theme_variant) {
262                        return Some(result);
263                    }
264                }
265            }
266        }
267
268        None
269    }
270
271    /// As [`try_attach_instance`](Self::try_attach_instance); a duplicate
272    /// setter panics with the message of the error.
273    #[allow(dead_code)]
274    pub(crate) fn attach(
275        &self,
276        target: &StyledElement,
277        activator: Option<Rc<dyn IStyleActivator>>,
278        type_: FrameType,
279        can_share_instance: bool,
280    ) -> Rc<dyn ValueFrame> {
281        match self.try_attach_instance(target, activator, type_, can_share_instance) {
282            Ok(frame) => frame,
283            Err(error) => panic!("{error}"),
284        }
285    }
286
287    /// Instances the style on `target` and adds the instance to its value
288    /// store. A style with two setters for the same property is an error:
289    /// nothing is added to the value store then.
290    pub(crate) fn try_attach_instance(
291        &self,
292        target: &StyledElement,
293        activator: Option<Rc<dyn IStyleActivator>>,
294        type_: FrameType,
295        mut can_share_instance: bool,
296    ) -> Result<Rc<dyn ValueFrame>, DuplicateSetterError> {
297        let shared = if can_share_instance { self.get_shared_instance(type_) } else { None };
298
299        let instance = match shared {
300            Some(instance) => instance,
301            None => {
302                crate::perf_count!(StyleInstancesCreated);
303                can_share_instance &= activator.is_none();
304
305                let instance = StyleInstance::new(self.to_ref().downgrade(), activator, type_);
306
307                let setters = self.setters.get().map(FerroList::snapshot).unwrap_or_default();
308                for setter in setters.iter() {
309                    let setter_instance = setter.clone().instance(&instance, target);
310                    can_share_instance &= setter_instance.is_setter;
311                    instance.try_add(setter_instance)?;
312                }
313
314                let animations = self.animations.get().map(FerroList::snapshot).unwrap_or_default();
315                if !animations.is_empty() {
316                    instance.add_animations(&animations);
317                }
318
319                if can_share_instance {
320                    instance.make_shared();
321                    self.set_shared_instance(type_, instance.clone());
322                }
323
324                instance
325            }
326        };
327
328        crate::perf_count!(StyleInstancesAttached);
329        target.values().add_frame(target, instance.clone());
330        instance.apply_animations(target);
331        Ok(instance)
332    }
333
334    fn get_shared_instance(&self, type_: FrameType) -> Option<Rc<StyleInstance>> {
335        if let Some(instances) = &*self.shared_instances.borrow() {
336            return instances[type_ as usize].clone();
337        }
338
339        if self.shared_instance_type.get() == type_ {
340            self.shared_instance.borrow().clone()
341        } else {
342            None
343        }
344    }
345
346    fn set_shared_instance(&self, type_: FrameType, instance: Rc<StyleInstance>) {
347        if let Some(instances) = &mut *self.shared_instances.borrow_mut() {
348            instances[type_ as usize] = Some(instance);
349            return;
350        }
351
352        let mut shared = self.shared_instance.borrow_mut();
353        match shared.take() {
354            None => {
355                *shared = Some(instance);
356                self.shared_instance_type.set(type_);
357            }
358            Some(existing) => {
359                let mut instances: Box<[Option<Rc<StyleInstance>>; 3]> = Box::new([None, None, None]);
360                instances[self.shared_instance_type.get() as usize] = Some(existing);
361                instances[type_ as usize] = Some(instance);
362                *self.shared_instances.borrow_mut() = Some(instances);
363            }
364        }
365    }
366
367    /// Adds an owner to the style.
368    pub fn add_owner(&self, owner: &ResourceHostRef) {
369        if self.owner().is_some() {
370            panic!("The Style already has a parent.");
371        }
372
373        self.set_owner(Some(owner));
374        let resources = self.resources.borrow().clone();
375        if let Some(resources) = resources {
376            resources.add_owner(owner);
377        }
378    }
379
380    /// Removes the owner of the style.
381    pub fn remove_owner(&self, owner: &ResourceHostRef) {
382        if self.owner().as_ref() == Some(owner) {
383            self.set_owner(None);
384            let resources = self.resources.borrow().clone();
385            if let Some(resources) = resources {
386                resources.remove_owner(owner);
387            }
388        }
389    }
390}
391
392/// The style and resource provider interfaces of a style object.
393struct StyleBaseHandle(Ref<StyleBase>);
394
395impl IResourceNode for StyleBaseHandle {
396    fn reference_id(&self) -> *const () {
397        let object: &FerroObject = (*self.0).upcast();
398        object as *const FerroObject as *const ()
399    }
400
401    fn has_resources(&self) -> bool {
402        self.0.resources.borrow().as_ref().is_some_and(|r| r.count() > 0)
403    }
404
405    fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
406        self.0.try_get_resource(key, theme)
407    }
408}
409
410impl IResourceProvider for StyleBaseHandle {
411    fn owner(&self) -> Option<ResourceHostRef> {
412        self.0.owner()
413    }
414
415    fn owner_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
416        self.0.subscribe_owner_changed(handler)
417    }
418
419    fn add_owner(&self, owner: &ResourceHostRef) {
420        self.0.add_owner(owner)
421    }
422
423    fn remove_owner(&self, owner: &ResourceHostRef) {
424        self.0.remove_owner(owner)
425    }
426
427    fn as_object(&self) -> Option<&FerroObject> {
428        Some((*self.0).upcast())
429    }
430}
431
432impl IStyle for StyleBaseHandle {
433    fn children(&self) -> Rc<Vec<Rc<dyn IStyle>>> {
434        match self.0.children.get() {
435            Some(children) => children.snapshot(),
436            None => empty_styles(),
437        }
438    }
439
440    fn as_object(&self) -> Option<&FerroObject> {
441        Some((*self.0).upcast())
442    }
443
444    fn as_resource_provider(&self) -> Option<&dyn IResourceProvider> {
445        Some(self)
446    }
447}
448
449impl From<Ref<StyleBase>> for Rc<dyn IResourceProvider> {
450    fn from(value: Ref<StyleBase>) -> Self {
451        Rc::new(StyleBaseHandle(value))
452    }
453}
454
455impl From<&Ref<StyleBase>> for Rc<dyn IResourceProvider> {
456    fn from(value: &Ref<StyleBase>) -> Self {
457        Rc::new(StyleBaseHandle(value.clone()))
458    }
459}
460
461impl<T: ObjectType + Upcast<StyleBase>> From<Ref<T>> for Rc<dyn IStyle> {
462    fn from(value: Ref<T>) -> Self {
463        Rc::new(StyleBaseHandle(value.upcast()))
464    }
465}
466
467impl<T: ObjectType + Upcast<StyleBase>> From<&Ref<T>> for Rc<dyn IStyle> {
468    fn from(value: &Ref<T>) -> Self {
469        Rc::new(StyleBaseHandle(value.clone().upcast()))
470    }
471}