Skip to main content

ferroui_base/
styled_element.rs

1use crate::animation::Animatable;
2use crate::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
3use crate::controls::{
4    Classes, IPseudoClasses, IResourceHost, IResourceNode, ResourceDictionary, ResourceHostRef, ResourceKey,
5    ResourceValue, ResourcesChangedEventArgs,
6};
7use crate::data::BindingPriority;
8use crate::logical_tree::{IChildIndexProvider, LogicalTreeAttachmentEventArgs};
9use crate::property_store::FrameType;
10use crate::reactive::{Disposable, IDisposable};
11use crate::styling::{
12    DuplicateSetterError, ContainerQuery, ControlTheme, IStyle, IStyleHost, IThemeVariantHost, Style, StyleBase, StyleHostRef,
13    StyleInstance, Styles, ThemeVariant,
14};
15use crate::utilities::HandlerList;
16use crate::InitializationError;
17use crate::{
18    ferro_class, ferro_property, instantiate, AnyValue, BoxedValue, DirectProperty, FerroObject, FerroObjectImpl,
19    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, StyledProperty,
20    StyledPropertyOptions, TypeInfo, WeakRef,
21};
22use std::cell::{Cell, OnceCell, RefCell};
23use std::rc::Rc;
24
25/// Extends an [`Animatable`] with the following features:
26///
27/// - An inherited `DataContext`.
28/// - Implements the logical tree.
29/// - A collection of class strings for custom styling.
30/// - Implements styling: tracks the frames applied by styles and themes.
31#[repr(C)]
32pub struct StyledElement {
33    base: Animatable,
34    init_count: Cell<u32>,
35    name: RefCell<Option<String>>,
36    classes: OnceCell<Classes>,
37    logical_root: RefCell<Option<WeakRef<StyledElement>>>,
38    logical_children: OnceCell<FerroList<Ref<StyledElement>>>,
39    parent: RefCell<Option<WeakRef<StyledElement>>>,
40    resources: RefCell<Option<Ref<ResourceDictionary>>>,
41    styles: OnceCell<Ref<Styles>>,
42    styles_applied: Cell<bool>,
43    theme_applied: Cell<bool>,
44    templated_parent_theme_applied: Cell<bool>,
45    templated_parent: RefCell<Option<WeakRef<FerroObject>>>,
46    data_context_updating: Cell<bool>,
47    implicit_theme: RefCell<ImplicitTheme>,
48    last_resources_changed_event_args: Cell<ResourcesChangedEventArgs>,
49    is_initialized: Cell<bool>,
50    /// True when this element has been removed from a logical children
51    /// collection and not added back since. Avoids a costly quadratic
52    /// `contains` check when attaching to the logical tree.
53    removed_from_logical_parent: Cell<bool>,
54    attached_to_logical_tree: HandlerList<dyn Fn(&LogicalTreeAttachmentEventArgs)>,
55    detached_from_logical_tree: HandlerList<dyn Fn(&LogicalTreeAttachmentEventArgs)>,
56    data_context_changed: HandlerList<dyn Fn()>,
57    initialized: HandlerList<dyn Fn()>,
58    resources_changed: HandlerList<dyn Fn(&ResourcesChangedEventArgs)>,
59    actual_theme_variant_changed: HandlerList<dyn Fn()>,
60}
61
62/// The control theme found for an element whose `Theme` property is not set.
63enum ImplicitTheme {
64    /// The theme has not been looked up yet.
65    Unknown,
66    /// The lookup found no theme.
67    Invalid,
68    Theme(Ref<ControlTheme>),
69}
70
71ferro_class! {
72    StyledElement: Animatable, virtuals StyledElementImpl: FerroObjectImpl {
73        /// Signals the beginning of a batch of initialization: styling is
74        /// deferred until the matching `end_init`.
75        fn begin_init(this);
76        /// Signals the end of a batch of initialization: the element is
77        /// styled (if it is attached to a rooted logical tree) and
78        /// initialized. A style or control theme that cannot be applied (two
79        /// setters for the same property) is an error, the counterpart of
80        /// the invalid operation `EndInit` raises in the managed original;
81        /// what follows the failure is skipped. This is the overridable
82        /// member; [`end_init`](StyledElement::end_init) is the form that
83        /// panics on the error.
84        fn try_end_init(this) -> Result<(), InitializationError>;
85        /// Called when the logical children collection changes.
86        fn logical_children_collection_changed(this, e: &NotifyCollectionChangedEventArgs<'_, Ref<StyledElement>>);
87        /// Called when the styled element is added to a rooted logical tree.
88        fn on_attached_to_logical_tree(this, e: &LogicalTreeAttachmentEventArgs);
89        /// Called when the styled element is removed from a rooted logical tree.
90        fn on_detached_from_logical_tree(this, e: &LogicalTreeAttachmentEventArgs);
91        /// Called when the data context changes.
92        fn on_data_context_changed(this);
93        /// Called when the data context begins updating.
94        fn on_data_context_begin_update(this);
95        /// Called when the data context finishes updating.
96        fn on_data_context_end_update(this);
97        /// Called when the control finishes initialization.
98        fn on_initialized(this);
99        /// The type by which the element is styled. Defaults to the element's
100        /// own type; override to be styled as a base class.
101        fn style_key_override(this) -> &'static TypeInfo;
102        /// Called when the control theme changes.
103        fn on_control_theme_changed(this);
104        /// Called when the templated parent's control theme changes.
105        fn on_templated_parent_control_theme_changed(this);
106        /// Detaches all styles from the element (and optionally from its
107        /// inheritance children) so that they are re-applied.
108        fn invalidate_styles(this, recurse: bool);
109        /// Whether the element is the root of a logical tree.
110        fn is_logical_root(this) -> bool;
111        /// The parent style host of the element: by default the inheritance
112        /// parent, when it is a styled element. The root of a tree overrides
113        /// this to return the host of the global styles.
114        fn styling_parent(this) -> Option<StyleHostRef>;
115        /// Notifies the children of the element that the resources visible to
116        /// them have changed. The base method notifies the logical children.
117        fn notify_child_resources_changed(this, e: ResourcesChangedEventArgs);
118        /// The child index provider implemented by the element, if any. Used
119        /// to match the children of the element by their position.
120        fn child_index_provider(this) -> Option<Rc<dyn IChildIndexProvider>>;
121        /// Whether the element is a root of theme variant resolution: at a
122        /// root the default theme variant resolves to the variant of the
123        /// platform instead of being inherited.
124        fn is_theme_variant_root(this) -> bool;
125    }
126}
127crate::ferro_class_info!(StyledElement {
128    new: StyledElement::new,
129    interfaces: [
130        Rc<dyn crate::ISupportInitialize>,
131        Rc<dyn crate::INamed>,
132        crate::controls::ResourceHostRef,
133        crate::styling::StyleHostRef,
134    ],
135});
136
137crate::ferro_overrides! { impl FerroObjectImpl for StyledElement {
138    fn constructed(this: &Self) {
139        Self::parent_constructed(this);
140        if this.is_logical_root() {
141            *this.logical_root.borrow_mut() = Some(this.to_ref().downgrade());
142        }
143    }
144
145    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
146        Self::parent_on_property_changed(this, change);
147
148        if change.property() == Self::theme_property().as_property() {
149            this.on_control_theme_changed();
150        } else if change.property() == ThemeVariant::actual_theme_variant_property().as_property()
151            && !this.actual_theme_variant_changed.is_empty()
152        {
153            for (_, handler) in this.actual_theme_variant_changed.snapshot().iter() {
154                handler();
155            }
156        }
157    }
158} }
159
160impl StyledElementImpl for StyledElement {
161    fn begin_init(this: &Self) {
162        this.init_count.set(this.init_count.get() + 1);
163    }
164
165    fn try_end_init(this: &Self) -> Result<(), InitializationError> {
166        let count = this.init_count.get();
167        assert!(count != 0, "BeginInit was not called.");
168        this.init_count.set(count - 1);
169        if count == 1 && this.logical_root.borrow().is_some() {
170            this.try_apply_styling()?;
171            this.initialize_if_needed();
172        }
173        Ok(())
174    }
175
176    fn logical_children_collection_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<StyledElement>>) {
177        match e.action {
178            NotifyCollectionChangedAction::Add => this.set_logical_parent_of(e.new_items),
179            NotifyCollectionChangedAction::Remove => this.clear_logical_parent_of(e.old_items),
180            NotifyCollectionChangedAction::Replace => {
181                this.clear_logical_parent_of(e.old_items);
182                this.set_logical_parent_of(e.new_items);
183            }
184            NotifyCollectionChangedAction::Reset => {
185                panic!("Reset should not be signaled on LogicalChildren collection")
186            }
187            NotifyCollectionChangedAction::Move => {}
188        }
189    }
190
191    fn on_attached_to_logical_tree(_this: &Self, _e: &LogicalTreeAttachmentEventArgs) {}
192
193    fn on_detached_from_logical_tree(_this: &Self, _e: &LogicalTreeAttachmentEventArgs) {}
194
195    fn on_data_context_changed(this: &Self) {
196        if !this.data_context_changed.is_empty() {
197            for (_, handler) in this.data_context_changed.snapshot().iter() {
198                handler();
199            }
200        }
201    }
202
203    fn on_data_context_begin_update(_this: &Self) {}
204
205    fn on_data_context_end_update(_this: &Self) {}
206
207    fn on_initialized(_this: &Self) {}
208
209    fn style_key_override(this: &Self) -> &'static TypeInfo {
210        this.get_type()
211    }
212
213    fn on_control_theme_changed(this: &Self) {
214        let values = this.values();
215        values.begin_styling();
216        values.remove_frames(this, FrameType::Theme);
217        values.end_styling(this);
218        this.theme_applied.set(false);
219    }
220
221    fn on_templated_parent_control_theme_changed(this: &Self) {
222        let values = this.values();
223        values.begin_styling();
224        values.remove_frames(this, FrameType::TemplatedParentTheme);
225        values.end_styling(this);
226        this.templated_parent_theme_applied.set(false);
227    }
228
229    fn invalidate_styles(this: &Self, recurse: bool) {
230        let values = this.values();
231        values.begin_styling();
232        values.remove_frames(this, FrameType::Style);
233        values.end_styling(this);
234        this.styles_applied.set(false);
235
236        if recurse {
237            for child in this.inheritance_children() {
238                if let Some(child) = child.downcast_ref::<StyledElement>() {
239                    child.invalidate_styles(recurse);
240                }
241            }
242        }
243    }
244
245    fn is_logical_root(_this: &Self) -> bool {
246        false
247    }
248
249    fn styling_parent(this: &Self) -> Option<StyleHostRef> {
250        // The handle of the inheritance parent becomes the handle of the
251        // host: the walks up the tree (styles, resources, the logical root)
252        // ask for it at every step, and a second handle made and dropped
253        // there is a count up and down per step.
254        this.inheritance_parent().and_then(|p| p.downcast::<StyledElement>().ok()).map(StyleHostRef::Element)
255    }
256
257    fn is_theme_variant_root(_this: &Self) -> bool {
258        false
259    }
260
261    fn notify_child_resources_changed(this: &Self, e: ResourcesChangedEventArgs) {
262        if let Some(children) = this.logical_children.get() {
263            if !children.is_empty() {
264                for child in children.snapshot().iter() {
265                    child.notify_resources_changed(e, true);
266                }
267            }
268        }
269    }
270
271    fn child_index_provider(_this: &Self) -> Option<Rc<dyn IChildIndexProvider>> {
272        None
273    }
274}
275
276// The theme variant properties are declared with the theme variant type and
277// owned by this class.
278crate::ferro_properties! { impl StyledElement, also [
279    crate::styling::ThemeVariant::actual_theme_variant_property,
280    crate::styling::ThemeVariant::requested_theme_variant_property,
281] {
282    ferro_property!(
283        /// Defines the `DataContext` property.
284        pub fn data_context_property() -> StyledProperty<Option<BoxedValue>> {
285            let property = FerroProperty::register_with::<StyledElement, _>(
286                "DataContext",
287                StyledPropertyOptions::new(None).inherits(true).notifying(Self::data_context_notifying),
288            );
289            property.changed().add_class_handler::<StyledElement>(|x, _| x.on_data_context_changed());
290            property
291        }
292    );
293
294    ferro_property!(
295        /// Defines the `Name` property.
296        pub fn name_property() -> DirectProperty<StyledElement, Option<String>> {
297            FerroProperty::register_direct::<StyledElement, _>("Name", |o| o.name(), Some(|o, v| o.set_name(v)), None)
298        }
299    );
300
301    ferro_property!(
302        /// Defines the `Parent` property.
303        pub fn parent_property() -> DirectProperty<StyledElement, Option<Ref<StyledElement>>> {
304            FerroProperty::register_direct::<StyledElement, _>("Parent", |o| o.parent(), None, None)
305        }
306    );
307
308    ferro_property!(
309        /// Defines the `TemplatedParent` property.
310        pub fn templated_parent_property() -> DirectProperty<StyledElement, Option<Ref<FerroObject>>> {
311            FerroProperty::register_direct::<StyledElement, _>(
312                "TemplatedParent",
313                |o| o.templated_parent(),
314                None,
315                None,
316            )
317        }
318    );
319
320    ferro_property!(
321        /// Defines the `Theme` property.
322        pub fn theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
323            FerroProperty::register::<StyledElement, _>("Theme", None)
324        }
325    );
326} }
327
328impl StyledElement {
329    /// Creates the class data; see [`FerroObject::construct`].
330    pub fn construct() -> Self {
331        Self {
332            base: Animatable::construct(),
333            init_count: Cell::new(0),
334            name: RefCell::new(None),
335            classes: OnceCell::new(),
336            logical_root: RefCell::new(None),
337            logical_children: OnceCell::new(),
338            parent: RefCell::new(None),
339            resources: RefCell::new(None),
340            styles: OnceCell::new(),
341            styles_applied: Cell::new(false),
342            theme_applied: Cell::new(false),
343            templated_parent_theme_applied: Cell::new(false),
344            templated_parent: RefCell::new(None),
345            data_context_updating: Cell::new(false),
346            implicit_theme: RefCell::new(ImplicitTheme::Unknown),
347            last_resources_changed_event_args: Cell::new(ResourcesChangedEventArgs::default()),
348            is_initialized: Cell::new(false),
349            removed_from_logical_parent: Cell::new(false),
350            attached_to_logical_tree: HandlerList::new(),
351            detached_from_logical_tree: HandlerList::new(),
352            data_context_changed: HandlerList::new(),
353            initialized: HandlerList::new(),
354            resources_changed: HandlerList::new(),
355            actual_theme_variant_changed: HandlerList::new(),
356        }
357    }
358
359    pub fn new() -> Ref<Self> {
360        instantiate(Self::construct())
361    }
362
363    // --- events -----------------------------------------------------------
364
365    /// Raised when the styled element is attached to a rooted logical tree.
366    pub fn attached_to_logical_tree(
367        &self,
368        handler: impl Fn(&LogicalTreeAttachmentEventArgs) + 'static,
369    ) -> Rc<dyn IDisposable> {
370        let token = self.attached_to_logical_tree.add(Rc::new(handler));
371        let weak = self.to_ref().downgrade();
372        Disposable::create(move || {
373            if let Some(this) = weak.upgrade() {
374                this.attached_to_logical_tree.remove(token);
375            }
376        })
377    }
378
379    /// Raised when the styled element is detached from a rooted logical tree.
380    pub fn detached_from_logical_tree(
381        &self,
382        handler: impl Fn(&LogicalTreeAttachmentEventArgs) + 'static,
383    ) -> Rc<dyn IDisposable> {
384        let token = self.detached_from_logical_tree.add(Rc::new(handler));
385        let weak = self.to_ref().downgrade();
386        Disposable::create(move || {
387            if let Some(this) = weak.upgrade() {
388                this.detached_from_logical_tree.remove(token);
389            }
390        })
391    }
392
393    /// Occurs when the data context property changes.
394    pub fn data_context_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
395        let token = self.data_context_changed.add(Rc::new(handler));
396        let weak = self.to_ref().downgrade();
397        Disposable::create(move || {
398            if let Some(this) = weak.upgrade() {
399                this.data_context_changed.remove(token);
400            }
401        })
402    }
403
404    /// Occurs when the styled element has finished initialization.
405    pub fn initialized(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
406        let token = self.initialized.add(Rc::new(handler));
407        let weak = self.to_ref().downgrade();
408        Disposable::create(move || {
409            if let Some(this) = weak.upgrade() {
410                this.initialized.remove(token);
411            }
412        })
413    }
414
415    /// Raised when the resources change on the element or an ancestor of the
416    /// element.
417    pub fn resources_changed(
418        &self,
419        handler: impl Fn(&ResourcesChangedEventArgs) + 'static,
420    ) -> Rc<dyn IDisposable> {
421        self.subscribe_resources_changed(Rc::new(handler))
422    }
423
424    fn subscribe_resources_changed(&self, handler: Rc<dyn Fn(&ResourcesChangedEventArgs)>) -> Rc<dyn IDisposable> {
425        let token = self.resources_changed.add(handler);
426        let weak = self.to_ref().downgrade();
427        Disposable::create(move || {
428            if let Some(this) = weak.upgrade() {
429                this.resources_changed.remove(token);
430            }
431        })
432    }
433
434    /// Raised when the theme variant in effect for the element changes.
435    pub fn actual_theme_variant_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
436        self.subscribe_actual_theme_variant_changed(Rc::new(handler))
437    }
438
439    fn subscribe_actual_theme_variant_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
440        let token = self.actual_theme_variant_changed.add(handler);
441        let weak = self.to_ref().downgrade();
442        Disposable::create(move || {
443            if let Some(this) = weak.upgrade() {
444                this.actual_theme_variant_changed.remove(token);
445            }
446        })
447    }
448
449    // --- properties -------------------------------------------------------
450
451    /// The name of the styled element.
452    ///
453    /// An element's name is used to uniquely identify it within its name
454    /// scope. Once the element has been styled its name may not be changed.
455    pub fn name(&self) -> Option<String> {
456        self.name.borrow().clone()
457    }
458
459    pub fn set_name(&self, value: Option<String>) {
460        assert!(!self.styles_applied.get(), "Cannot set Name : styled element already styled.");
461        *self.name.borrow_mut() = value;
462    }
463
464    /// The styled element's classes.
465    ///
466    /// The collection is returned by handle: clones refer to the same
467    /// collection.
468    pub fn classes(&self) -> Classes {
469        self.classes_ref().clone()
470    }
471
472    #[inline]
473    fn classes_ref(&self) -> &Classes {
474        self.classes.get_or_init(Classes::new)
475    }
476
477    /// The pseudoclasses of the element, settable by the control itself.
478    pub fn pseudo_classes(&self) -> &dyn IPseudoClasses {
479        self.classes_ref()
480    }
481
482    /// The control's data context.
483    ///
484    /// The data context is an inherited property that specifies the default
485    /// object that will be used for data binding.
486    pub fn data_context(&self) -> Option<BoxedValue> {
487        self.get_value(Self::data_context_property())
488    }
489
490    pub fn set_data_context(&self, value: Option<BoxedValue>) {
491        self.set_value(Self::data_context_property(), value)
492    }
493
494    /// Whether the element has finished initialization.
495    ///
496    /// For more information about when this happens, see the `initialized`
497    /// event.
498    pub fn is_initialized(&self) -> bool {
499        self.is_initialized.get()
500    }
501
502    /// The styles for the styled element.
503    ///
504    /// Styles for the entire application are added to the application's
505    /// styles collection.
506    pub fn styles(&self) -> Ref<Styles> {
507        self.styles.get_or_init(|| Styles::with_owner(self.to_ref())).clone()
508    }
509
510    /// Whether the styles collection of the element has been created.
511    pub fn is_styles_initialized(&self) -> bool {
512        self.styles.get().is_some()
513    }
514
515    /// The type by which the element is styled.
516    pub fn style_key(&self) -> &'static TypeInfo {
517        self.style_key_override()
518    }
519
520    /// The styled element's resource dictionary.
521    pub fn resources(&self) -> Ref<ResourceDictionary> {
522        if let Some(resources) = &*self.resources.borrow() {
523            return resources.clone();
524        }
525        let resources = ResourceDictionary::with_owner(self.to_ref());
526        *self.resources.borrow_mut() = Some(resources.clone());
527        resources
528    }
529
530    pub fn set_resources(&self, value: Ref<ResourceDictionary>) {
531        let this = ResourceHostRef::Element(self.to_ref());
532        let old = self.resources.borrow_mut().take();
533        if let Some(old) = old {
534            old.remove_owner(&this);
535        }
536        *self.resources.borrow_mut() = Some(value.clone());
537        value.add_owner(&this);
538    }
539
540    /// The theme to be applied to the element.
541    ///
542    /// By default the theme is looked up as a resource keyed by the element's
543    /// style key; setting this property takes precedence over that lookup.
544    pub fn theme(&self) -> Option<Ref<ControlTheme>> {
545        self.get_value(Self::theme_property())
546    }
547
548    pub fn set_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
549        self.set_value(Self::theme_property(), value.into().0)
550    }
551
552    /// The UI theme variant that is currently used by the element, which
553    /// might be different than the requested theme variant. `None` until a
554    /// theme variant has been established for the element or an ancestor.
555    pub fn actual_theme_variant(&self) -> Option<ThemeVariant> {
556        self.get_value(ThemeVariant::actual_theme_variant_property())
557    }
558
559    /// Whether the element or its styles have resources.
560    pub fn has_resources(&self) -> bool {
561        self.resources.borrow().as_ref().is_some_and(|r| r.has_resources())
562            || self.styles.get().is_some_and(|s| s.has_resources())
563    }
564
565    /// Tries to find a resource within the element: in its resource
566    /// dictionary, then in its styles.
567    pub fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
568        let resources = self.resources.borrow().clone();
569        if let Some(resources) = resources {
570            if let Some(value) = resources.try_get_resource(key, theme) {
571                return Some(value);
572            }
573        }
574        match self.styles.get() {
575            Some(styles) => styles.try_get_resource(key, theme),
576            None => None,
577        }
578    }
579
580    /// Tries to find a resource by searching up the logical tree and then
581    /// the global styles.
582    pub fn try_find_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
583        let host: &dyn IResourceHost = self;
584        host.try_find_resource(key, theme)
585    }
586
587    /// Finds a resource by searching up the logical tree and then the global
588    /// styles. Returns the unset value marker if it is not found.
589    pub fn find_resource(&self, key: &ResourceKey) -> ResourceValue {
590        let host: &dyn IResourceHost = self;
591        host.find_resource(key)
592    }
593
594    /// The styled element whose lookless template this element is part of.
595    pub fn templated_parent(&self) -> Option<Ref<FerroObject>> {
596        self.templated_parent.borrow().as_ref().and_then(WeakRef::upgrade)
597    }
598
599    /// Sets the templated parent. Called by the template that creates the
600    /// element.
601    pub fn set_templated_parent(&self, value: impl Into<Nullable<FerroObject>>) {
602        let value = value.into().0;
603        let old = self.templated_parent();
604        if old != value {
605            *self.templated_parent.borrow_mut() = value.as_ref().map(Ref::downgrade);
606            self.raise_direct_property_changed(Self::templated_parent_property(), &old, &value);
607        }
608    }
609
610    /// The styled element's logical children.
611    pub fn logical_children(&self) -> &FerroList<Ref<StyledElement>> {
612        self.logical_children.get_or_init(|| {
613            let list = FerroList::new();
614            list.set_reset_behavior(ResetBehavior::Remove);
615            let weak = self.to_ref().downgrade();
616            list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<StyledElement>>| {
617                if let Some(this) = weak.upgrade() {
618                    this.logical_children_collection_changed(e);
619                }
620            }));
621            list
622        })
623    }
624
625    /// Whether the element is attached to a rooted logical tree.
626    pub fn is_attached_to_logical_tree(&self) -> bool {
627        self.logical_root.borrow().is_some()
628    }
629
630    /// The styled element's logical parent.
631    pub fn parent(&self) -> Option<Ref<StyledElement>> {
632        self.parent.borrow().as_ref().and_then(WeakRef::upgrade)
633    }
634
635    // --- initialization and styling ---------------------------------------
636
637    /// Applies styling to the control if the control is initialized and
638    /// styling is not already applied.
639    ///
640    /// The styling system will automatically apply styling when required, so
641    /// this method need only be called by tests or in rare special cases.
642    ///
643    /// Returns true if styling is applied to the control, false if the
644    /// control is still initializing.
645    pub fn apply_styling(&self) -> bool {
646        match self.try_apply_styling() {
647            Ok(applied) => applied,
648            Err(error) => panic!("{error}"),
649        }
650    }
651
652    /// As [`apply_styling`](Self::apply_styling), reporting a style or
653    /// control theme that cannot be applied (two setters for the same
654    /// property: the invalid operation of the managed original) instead of
655    /// panicking. After an error the styling pass is ended and the element
656    /// is not marked as styled, as when the managed original throws.
657    pub fn try_apply_styling(&self) -> Result<bool, DuplicateSetterError> {
658        if self.init_count.get() == 0
659            && (!self.styles_applied.get()
660                || !self.theme_applied.get()
661                || !self.templated_parent_theme_applied.get())
662        {
663            let values = self.values();
664            values.begin_styling();
665            let _end_styling = EndStyling(self);
666            if !self.theme_applied.get() {
667                self.apply_control_theme()?;
668                self.theme_applied.set(true);
669            }
670            if !self.templated_parent_theme_applied.get() {
671                self.apply_templated_parent_control_theme()?;
672                self.templated_parent_theme_applied.set(true);
673            }
674            if !self.styles_applied.get() {
675                self.apply_styles(&StyleHostRef::Element(self.to_ref()))?;
676                self.styles_applied.set(true);
677            }
678        }
679        Ok(self.styles_applied.get())
680    }
681
682    /// Signals the end of a batch of initialization
683    /// ([`try_end_init`](Self::try_end_init)). A style or control theme that
684    /// cannot be applied panics with the message of the error.
685    pub fn end_init(&self) {
686        if let Err(error) = self.try_end_init() {
687            panic!("{error}");
688        }
689    }
690
691    /// Marks the element as initialized and raises the initialization
692    /// notifications, if initialization is complete.
693    pub fn initialize_if_needed(&self) {
694        if self.init_count.get() == 0 && !self.is_initialized.get() {
695            self.is_initialized.set(true);
696            self.on_initialized();
697            if !self.initialized.is_empty() {
698                for (_, handler) in self.initialized.snapshot().iter() {
699                    handler();
700                }
701            }
702        }
703    }
704
705    // --- logical tree -----------------------------------------------------
706
707    /// Sets the element's logical parent.
708    ///
709    /// You should not usually need to call this method: it is called when an
710    /// element is added to or removed from a logical children collection.
711    pub fn set_parent(&self, parent: impl Into<Nullable<StyledElement>>) {
712        let parent = parent.into().0;
713        let old = self.parent();
714        if parent == old {
715            return;
716        }
717        assert!(old.is_none() || parent.is_none(), "The Control already has a parent.");
718
719        if self.inheritance_parent().is_none() || parent.is_none() {
720            self.set_inheritance_parent(parent.clone().map(Ref::upcast::<FerroObject>));
721        }
722
723        *self.parent.borrow_mut() = parent.as_ref().map(Ref::downgrade);
724
725        let this = self.to_ref();
726        let old_root = self.logical_root();
727        if let Some(root) = old_root {
728            let e = LogicalTreeAttachmentEventArgs::new(root, this.clone(), old.clone());
729            self.on_detached_from_logical_tree_core(&e);
730        }
731
732        if let Some(new_root) = self.find_logical_root() {
733            let e = LogicalTreeAttachmentEventArgs::new(new_root, this, parent.clone());
734            self.on_attached_to_logical_tree_core(&e);
735        } else if parent.is_none() {
736            // If we were attached to the logical tree, we piggyback on the
737            // tree traversal there to raise resources changed notifications.
738            // If we're being removed from the logical tree, then traverse the
739            // tree raising notifications now.
740            //
741            // We don't raise resources changed notifications if we're being
742            // attached to a non-rooted control because it's unlikely that
743            // dynamic resources need to be correct until the control is added
744            // to the tree, and it causes a *lot* of notifications.
745            self.notify_resources_changed(ResourcesChangedEventArgs::create(), true);
746        }
747
748        self.raise_direct_property_changed(Self::parent_property(), &old, &parent);
749    }
750
751    /// Notifies the element that it has been attached to a rooted logical
752    /// tree. Called by a parent for children that are part of its logical
753    /// tree without being in its logical children collection (the template
754    /// child of a templated control).
755    pub fn notify_attached_to_logical_tree(&self, e: &LogicalTreeAttachmentEventArgs) {
756        self.on_attached_to_logical_tree_core(e);
757    }
758
759    /// Notifies the element that it has been detached from a rooted logical
760    /// tree. See [`notify_attached_to_logical_tree`](Self::notify_attached_to_logical_tree).
761    pub fn notify_detached_from_logical_tree(&self, e: &LogicalTreeAttachmentEventArgs) {
762        self.on_detached_from_logical_tree_core(e);
763    }
764
765    fn logical_root(&self) -> Option<Ref<StyledElement>> {
766        self.logical_root.borrow().as_ref().and_then(WeakRef::upgrade)
767    }
768
769    fn find_logical_root(&self) -> Option<Ref<StyledElement>> {
770        if self.is_logical_root() {
771            return Some(self.to_ref());
772        }
773        let mut current = self.styling_parent();
774        while let Some(host) = current {
775            if let StyleHostRef::Element(e) = &host {
776                if e.is_logical_root() {
777                    return Some(e.clone());
778                }
779            }
780            current = host.styling_parent();
781        }
782        None
783    }
784
785    fn on_attached_to_logical_tree_core(&self, e: &LogicalTreeAttachmentEventArgs) {
786        if self.parent().is_none() && !self.is_logical_root() {
787            panic!(
788                "AttachedToLogicalTreeCore called for '{}' but control has no logical parent.",
789                self.get_type().name()
790            );
791        }
792
793        // This method can be called when a control is already attached to the
794        // logical tree in the following scenario:
795        // - ListBox gets assigned Items containing ListBoxItem
796        // - ListBox makes ListBoxItem a logical child
797        // - ListBox template gets applied; making its Panel get attached to
798        //   the logical tree
799        // - That AttachedToLogicalTree signal travels down to the ListBoxItem
800        if self.logical_root.borrow().is_none() {
801            crate::perf_count!(LogicalTreeAttachments);
802            *self.logical_root.borrow_mut() = Some(e.root().downgrade());
803            self.reevaluate_implicit_theme();
804            self.apply_styling();
805            self.notify_resources_changed(ResourcesChangedEventArgs::create(), false);
806            self.on_attached_to_logical_tree(e);
807            if !self.attached_to_logical_tree.is_empty() {
808                for (_, handler) in self.attached_to_logical_tree.snapshot().iter() {
809                    handler(e);
810                }
811            }
812        }
813
814        if let Some(children) = self.logical_children.get() {
815            // An event handler may have modified the children while we're
816            // enumerating a snapshot of the collection.
817            for child in children.snapshot().iter() {
818                let attached_to_root = child.logical_root().is_some_and(|r| r == *e.root());
819                if !attached_to_root && (child.parent().is_some() || !child.removed_from_logical_parent.get()) {
820                    child.on_attached_to_logical_tree_core(e);
821                }
822            }
823        }
824    }
825
826    fn on_detached_from_logical_tree_core(&self, e: &LogicalTreeAttachmentEventArgs) {
827        if self.logical_root.borrow().is_some() {
828            crate::perf_count!(LogicalTreeDetachments);
829            *self.logical_root.borrow_mut() = None;
830            self.invalidate_styles(false);
831            self.on_detached_from_logical_tree(e);
832            if !self.detached_from_logical_tree.is_empty() {
833                for (_, handler) in self.detached_from_logical_tree.snapshot().iter() {
834                    handler(e);
835                }
836            }
837
838            if let Some(children) = self.logical_children.get() {
839                for child in children.snapshot().iter() {
840                    child.on_detached_from_logical_tree_core(e);
841                }
842            }
843        }
844    }
845
846    fn set_logical_parent_of(&self, children: &[Ref<StyledElement>]) {
847        for child in children {
848            child.removed_from_logical_parent.set(false);
849            if child.parent().is_none() {
850                child.set_parent(self.to_ref());
851            }
852        }
853    }
854
855    fn clear_logical_parent_of(&self, children: &[Ref<StyledElement>]) {
856        let this = self.to_ref();
857        for child in children {
858            child.removed_from_logical_parent.set(true);
859            if child.parent().is_some_and(|p| p == this) {
860                child.set_parent(None);
861            }
862        }
863    }
864
865    // --- styling ----------------------------------------------------------
866
867    /// The theme in effect for the element: the `Theme` property if set,
868    /// otherwise the control theme resource keyed by the element's style key
869    /// that is visible to the element (a resource holding a
870    /// `Ref<ControlTheme>`).
871    pub fn get_effective_theme(&self) -> Option<Ref<ControlTheme>> {
872        // Explicitly set Theme property takes precedence.
873        if let Some(theme) = self.theme() {
874            return Some(theme);
875        }
876
877        // If the Theme property is not set, try to find a ControlTheme
878        // resource with our StyleKey.
879        if matches!(*self.implicit_theme.borrow(), ImplicitTheme::Unknown) {
880            crate::perf_count!(ImplicitThemeLookups);
881            let key = ResourceKey::Type(self.style_key());
882            let found = self.try_find_resource(&key, None).flatten().and_then(|value| {
883                let value: &dyn AnyValue = &*value;
884                value.downcast_ref::<Ref<ControlTheme>>().cloned()
885            });
886            *self.implicit_theme.borrow_mut() = match found {
887                Some(theme) => ImplicitTheme::Theme(theme),
888                None => ImplicitTheme::Invalid,
889            };
890        }
891
892        match &*self.implicit_theme.borrow() {
893            ImplicitTheme::Theme(theme) => Some(theme.clone()),
894            _ => None,
895        }
896    }
897
898    fn apply_control_theme(&self) -> Result<(), DuplicateSetterError> {
899        if let Some(theme) = self.get_effective_theme() {
900            self.apply_control_theme_core(&theme, FrameType::Theme)?;
901        }
902        Ok(())
903    }
904
905    fn apply_templated_parent_control_theme(&self) -> Result<(), DuplicateSetterError> {
906        // Most elements have no templated parent: avoid upgrading the weak
907        // reference for them.
908        if self.templated_parent.borrow().is_none() {
909            return Ok(());
910        }
911        let parent_theme = self
912            .templated_parent()
913            .and_then(|p| p.downcast_ref::<StyledElement>().and_then(StyledElement::get_effective_theme));
914        if let Some(parent_theme) = parent_theme {
915            self.apply_control_theme_core(&parent_theme, FrameType::TemplatedParentTheme)?;
916        }
917        Ok(())
918    }
919
920    fn apply_control_theme_core(&self, theme: &ControlTheme, type_: FrameType) -> Result<(), DuplicateSetterError> {
921        debug_assert!(matches!(type_, FrameType::Theme | FrameType::TemplatedParentTheme));
922
923        if let Some(based_on) = theme.based_on() {
924            self.apply_control_theme_core(&based_on, type_)?;
925        }
926
927        theme.try_attach_checked(self, type_)?;
928
929        if theme.has_children() {
930            let children = theme.children().snapshot();
931            for child in children.iter() {
932                self.apply_style(&**child, None, type_)?;
933            }
934        }
935        Ok(())
936    }
937
938    fn apply_styles(&self, host: &StyleHostRef) -> Result<(), DuplicateSetterError> {
939        if let Some(parent) = host.styling_parent() {
940            self.apply_styles(&parent)?;
941        }
942        crate::perf_count!(StyleHostsWalked);
943
944        if host.is_styles_initialized() {
945            let styles = host.styles().snapshot();
946            for style in styles.iter() {
947                self.apply_style(&**style, Some(host), FrameType::Style)?;
948            }
949        }
950        Ok(())
951    }
952
953    fn apply_style(
954        &self,
955        style: &dyn IStyle,
956        host: Option<&StyleHostRef>,
957        type_: FrameType,
958    ) -> Result<(), DuplicateSetterError> {
959        if let Some(base) = style.as_object().and_then(|o| o.downcast_ref::<StyleBase>()) {
960            if let Some(s) = base.downcast_ref::<Style>() {
961                s.try_attach_checked(self, host, type_)?;
962            } else if let Some(m) = base.downcast_ref::<ContainerQuery>() {
963                m.try_attach_checked(self, host, type_)?;
964            }
965
966            // The common case of a style without nested styles.
967            if !base.has_children() {
968                return Ok(());
969            }
970        }
971
972        let children = style.children();
973        for child in children.iter() {
974            self.apply_style(&**child, host, type_)?;
975        }
976        Ok(())
977    }
978
979    fn reevaluate_implicit_theme(&self) {
980        // We only need to check if the theme has changed when Theme isn't set
981        // (i.e. when we have an implicit theme).
982        if self.theme().is_some() {
983            return;
984        }
985
986        // Refetch the implicit theme.
987        let old_implicit_theme = match self.implicit_theme.replace(ImplicitTheme::Unknown) {
988            ImplicitTheme::Theme(theme) => Some(theme),
989            _ => None,
990        };
991        let new_implicit_theme = self.get_effective_theme();
992
993        // If the implicit theme has changed, detach the existing theme.
994        if new_implicit_theme != old_implicit_theme {
995            self.on_control_theme_changed();
996            self.theme_applied.set(false);
997        }
998    }
999
1000    /// Called when styles are added to the styles collection of the element
1001    /// or a nested styles collection.
1002    pub fn styles_added(&self, styles: &[Rc<dyn IStyle>]) {
1003        if Self::has_setters_or_animations(styles) {
1004            self.invalidate_styles(true);
1005        }
1006    }
1007
1008    /// Called when styles are removed from the styles collection of the
1009    /// element or a nested styles collection.
1010    pub fn styles_removed(&self, styles: &[Rc<dyn IStyle>]) {
1011        let mut all_styles = Vec::new();
1012        Self::flatten_styles(styles, &mut all_styles);
1013        if !all_styles.is_empty() {
1014            self.detach_styles(&all_styles);
1015        }
1016    }
1017
1018    fn detach_styles(&self, styles: &[Ref<StyleBase>]) {
1019        let values = self.values();
1020        values.begin_styling();
1021        values.remove_frames_where(self, |frame| {
1022            frame
1023                .as_any()
1024                .downcast_ref::<StyleInstance>()
1025                .is_some_and(|instance| styles.iter().any(|style| instance.is_source(style)))
1026        });
1027        values.end_styling(self);
1028
1029        if let Some(children) = self.logical_children.get() {
1030            if !children.is_empty() {
1031                for child in children.snapshot().iter() {
1032                    child.detach_styles(styles);
1033                }
1034            }
1035        }
1036    }
1037
1038    /// Notifies the element, and with `propagate` its children, that the
1039    /// resources visible to it have changed.
1040    pub fn notify_resources_changed(&self, e: ResourcesChangedEventArgs, propagate: bool) {
1041        // We already got a notification for this element, ignore.
1042        if e == self.last_resources_changed_event_args.get() {
1043            return;
1044        }
1045
1046        self.last_resources_changed_event_args.set(e);
1047
1048        if !self.resources_changed.is_empty() {
1049            for (_, handler) in self.resources_changed.snapshot().iter() {
1050                handler(&e);
1051            }
1052        }
1053
1054        if propagate {
1055            self.notify_child_resources_changed(e);
1056        }
1057    }
1058
1059    fn flatten_styles(styles: &[Rc<dyn IStyle>], result: &mut Vec<Ref<StyleBase>>) {
1060        for style in styles {
1061            if let Some(s) = style.as_object().and_then(|o| o.downcast_ref::<Style>()) {
1062                result.push(s.to_ref().upcast());
1063            }
1064            let children = style.children();
1065            if !children.is_empty() {
1066                Self::flatten_styles(&children, result);
1067            }
1068        }
1069    }
1070
1071    fn has_setters_or_animations(styles: &[Rc<dyn IStyle>]) -> bool {
1072        styles.iter().any(|style| {
1073            if style
1074                .as_object()
1075                .and_then(|o| o.downcast_ref::<StyleBase>())
1076                .is_some_and(StyleBase::has_setters_or_animations)
1077            {
1078                return true;
1079            }
1080            let children = style.children();
1081            !children.is_empty() && Self::has_setters_or_animations(&children)
1082        })
1083    }
1084
1085    // --- data context -----------------------------------------------------
1086
1087    fn data_context_notifying(o: &FerroObject, update_started: bool) {
1088        if let Some(element) = o.downcast_ref::<StyledElement>() {
1089            element.data_context_notifying_core(update_started);
1090        }
1091    }
1092
1093    fn data_context_notifying_core(&self, update_started: bool) {
1094        if update_started {
1095            if !self.data_context_updating.get() {
1096                self.data_context_updating.set(true);
1097                self.on_data_context_begin_update();
1098
1099                if let Some(children) = self.logical_children.get() {
1100                    let this: &FerroObject = self;
1101                    for child in children.snapshot().iter() {
1102                        if child.is_inheritance_parent(this) && !child.is_set(Self::data_context_property())
1103                        {
1104                            child.data_context_notifying_core(update_started);
1105                        }
1106                    }
1107                }
1108            }
1109        } else if self.data_context_updating.get() {
1110            self.on_data_context_end_update();
1111            self.data_context_updating.set(false);
1112        }
1113    }
1114
1115    /// The priority-aware setter used by templates.
1116    pub fn set_data_context_with_priority(
1117        &self,
1118        value: Option<BoxedValue>,
1119        priority: BindingPriority,
1120    ) -> Option<Rc<dyn IDisposable>> {
1121        self.set_value_with_priority(Self::data_context_property(), value, priority)
1122    }
1123}
1124
1125/// Ends a styling pass when dropped, so that the pass also ends when applying
1126/// a style panics.
1127struct EndStyling<'a>(&'a StyledElement);
1128
1129impl Drop for EndStyling<'_> {
1130    fn drop(&mut self) {
1131        self.0.values().end_styling(self.0);
1132    }
1133}
1134
1135impl IResourceNode for StyledElement {
1136    fn has_resources(&self) -> bool {
1137        StyledElement::has_resources(self)
1138    }
1139
1140    fn try_get_resource(&self, key: &ResourceKey, theme: Option<&ThemeVariant>) -> Option<ResourceValue> {
1141        StyledElement::try_get_resource(self, key, theme)
1142    }
1143}
1144
1145impl IResourceHost for StyledElement {
1146    fn resources_changed(&self, handler: Rc<dyn Fn(&ResourcesChangedEventArgs)>) -> Rc<dyn IDisposable> {
1147        self.subscribe_resources_changed(handler)
1148    }
1149
1150    fn notify_hosted_resources_changed(&self, e: ResourcesChangedEventArgs) {
1151        self.notify_resources_changed(e, true)
1152    }
1153
1154    fn as_style_host(&self) -> Option<&dyn IStyleHost> {
1155        Some(self)
1156    }
1157
1158    fn as_theme_variant_host(&self) -> Option<&dyn IThemeVariantHost> {
1159        Some(self)
1160    }
1161}
1162
1163impl IStyleHost for StyledElement {
1164    fn is_styles_initialized(&self) -> bool {
1165        StyledElement::is_styles_initialized(self)
1166    }
1167
1168    fn styles(&self) -> Ref<Styles> {
1169        StyledElement::styles(self)
1170    }
1171
1172    fn styling_parent(&self) -> Option<StyleHostRef> {
1173        StyledElement::styling_parent(self)
1174    }
1175
1176    fn styles_added(&self, styles: &[Rc<dyn IStyle>]) {
1177        StyledElement::styles_added(self, styles)
1178    }
1179
1180    fn styles_removed(&self, styles: &[Rc<dyn IStyle>]) {
1181        StyledElement::styles_removed(self, styles)
1182    }
1183}
1184
1185impl IThemeVariantHost for StyledElement {
1186    fn actual_theme_variant(&self) -> Option<ThemeVariant> {
1187        StyledElement::actual_theme_variant(self)
1188    }
1189
1190    fn actual_theme_variant_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
1191        self.subscribe_actual_theme_variant_changed(handler)
1192    }
1193}