Skip to main content

ferroui_controls/
control.rs

1use crate::automation::peers::{AutomationPeer, NoneAutomationPeer};
2use crate::primitives::{AdornerLayer, FlyoutBase};
3use crate::templates::{DataTemplates, IDataTemplateHost, ITemplateOf};
4use crate::Panel;
5use crate::ContextMenu;
6use ferroui_base::data::core::ValueTypes;
7use ferroui_base::reactive::IDisposable;
8use crate::{RequestBringIntoViewEventArgs, SizeChangedEventArgs};
9use ferroui_base::input::{
10    ContextRequestedEventArgs, FocusChangedEventArgs, InputElement, InputElementImpl, InputElementImplExt, KeyEventArgs,
11    MouseButton, NavigationMethod,
12    PointerReleasedEventArgs,
13};
14use ferroui_base::interactivity::{
15    Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
16};
17use ferroui_base::layout::LayoutableImpl;
18use ferroui_base::styling::{ISetterValue, Setter};
19use ferroui_base::threading::{Dispatcher, DispatcherPriority};
20use ferroui_base::{
21    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AnyValue, BoxedValue,
22    FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, IntoRef, Nullable, Rect,
23    Ref, Size, StyledElement, StyledElementImpl, StyledProperty, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
24};
25use std::cell::{Cell, OnceCell, RefCell};
26use std::collections::{HashSet, VecDeque};
27use std::rc::Rc;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30enum LoadState {
31    Unloaded,
32    Loaded,
33    LoadPending,
34}
35
36// Note the following:
37// `queue`:
38//   Is the queue where any control will be added to indicate that its loaded
39//   event should be scheduled and called later.
40// `processing_queue`:
41//   Contains a copied snapshot of `queue` at the time when processing starts
42//   and individual events are being fired. This is needed because new
43//   controls can be added in the Loaded event itself.
44#[derive(Default)]
45struct LoadedQueues {
46    is_loaded_processing: bool,
47    /// The queued controls in insertion order.
48    queue: Vec<Ref<Control>>,
49    /// The members of `queue`, for constant time lookups.
50    queued: HashSet<Ref<Control>>,
51    processing_queue: VecDeque<Ref<Control>>,
52}
53
54impl LoadedQueues {
55    fn add(&mut self, control: Ref<Control>) -> bool {
56        if self.queued.insert(control.clone()) {
57            self.queue.push(control);
58            true
59        } else {
60            false
61        }
62    }
63
64    fn remove(&mut self, control: &Ref<Control>) {
65        if self.queued.remove(control) {
66            self.queue.retain(|c| c != control);
67        }
68    }
69}
70
71ferroui_base::ferro_thread_local! {
72    static LOADED: RefCell<LoadedQueues> = RefCell::new(LoadedQueues::default());
73}
74
75/// Base class for controls.
76///
77/// The control class extends [`InputElement`] and adds the following
78/// features:
79///
80/// - A `Tag` property to allow user-defined data to be attached to the
81///   control.
82#[repr(C)]
83pub struct Control {
84    base: InputElement,
85    load_state: Cell<LoadState>,
86    data_templates: OnceCell<DataTemplates>,
87    focus_adorner: RefCell<Option<Ref<Control>>>,
88    context_flyout_handlers: Cell<Option<(RoutedEventHandlerToken, RoutedEventHandlerToken)>>,
89    context_menu_handlers: Cell<Option<ContextMenuHandlers>>,
90    automation_peer: RefCell<Option<Ref<AutomationPeer>>>,
91}
92
93/// The handlers the context menu support attaches to a control: the context
94/// requested and context canceled handlers and the subscriptions to the
95/// attachment to and the detachment from the visual tree.
96pub(crate) type ContextMenuHandlers =
97    (RoutedEventHandlerToken, RoutedEventHandlerToken, Rc<dyn IDisposable>, Rc<dyn IDisposable>);
98
99ferro_class! {
100    Control: InputElement, virtuals ControlImpl: InputElementImpl {
101        /// Gets the element that receives the focus adorner.
102        fn get_template_focus_target(this) -> Option<Ref<Control>>;
103        /// Raises the `Loaded` event.
104        fn on_loaded(this, e: &RoutedEventArgs);
105        /// Raises the `Unloaded` event.
106        fn on_unloaded(this, e: &RoutedEventArgs);
107        /// Raises the `SizeChanged` event.
108        fn on_size_changed(this, e: &SizeChangedEventArgs);
109        /// Returns a new, type-specific automation peer implementation for
110        /// the control.
111        fn on_create_automation_peer(this) -> Ref<AutomationPeer>;
112        /// The flyout a context request on the control shows (`for_request`), or
113        /// that its keys and cancellation dismiss: `ContextFlyout` by default. A
114        /// control whose context flyout belongs to another element (a linked text
115        /// container) resolves it here, and may show none for a request. An
116        /// addition (`docs/porting/DEVIATIONS.md`, Popup).
117        fn effective_context_flyout(this, for_request: bool) -> Option<Ref<FlyoutBase>>;
118        /// A counter the control advances when it raises a newer context request
119        /// of its own while an older one is being handled; `None` by default. The
120        /// context menu and flyout drop the older request when it changed. An
121        /// addition (`docs/porting/DEVIATIONS.md`, Popup).
122        fn context_request_generation(this) -> Option<u64>;
123    }
124}
125ferroui_base::ferro_class_info!(Control { new: Control::new });
126
127ferro_impl_classes!(Control: StyledElementImpl, LayoutableImpl, InteractiveImpl);
128
129ferroui_base::ferro_overrides! { impl FerroObjectImpl for Control {
130    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
131        Self::parent_on_property_changed(this, change);
132
133        if change.property() == Visual::bounds_property().as_property() {
134            let (old_value, new_value) = change.get_old_and_new_value::<Rect>();
135
136            // Bounds is a rect with a position as well as a height and width.
137            // This means it is possible for the rect to change position but
138            // not size. Therefore, we want to explicitly check only the size
139            // and raise an event only when that size has changed.
140            if new_value.size() != old_value.size() {
141                let size_changed_event_args = SizeChangedEventArgs::new(
142                    Self::size_changed_event(),
143                    this.to_ref(),
144                    Size::new(old_value.width, old_value.height),
145                    Size::new(new_value.width, new_value.height),
146                );
147
148                this.on_size_changed(&size_changed_event_args);
149            }
150        }
151    }
152
153    fn update_data_validation(
154        this: &Self,
155        property: &'static ferroui_base::FerroProperty,
156        state: ferroui_base::data::BindingValueType,
157        error: Option<&ferroui_base::data::BindingError>,
158    ) {
159        crate::DataValidationErrors::set_error(this, error);
160        Self::parent_update_data_validation(this, property, state, error);
161    }
162} }
163
164impl VisualImpl for Control {
165    fn on_attached_to_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
166        Self::parent_on_attached_to_visual_tree_core(this, e);
167
168        this.initialize_if_needed();
169
170        this.schedule_on_loaded_core();
171    }
172
173    fn on_detached_from_visual_tree_core(this: &Self, e: &VisualTreeAttachmentEventArgs) {
174        Self::parent_on_detached_from_visual_tree_core(this, e);
175
176        this.on_unloaded_core();
177    }
178
179    fn ensure_initialized_for_visual_brush(this: &Self) {
180        this.ensure_initialized();
181    }
182}
183
184impl InputElementImpl for Control {
185    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
186        Self::parent_on_got_focus(this, e);
187
188        if this.is_focused()
189            && (e.navigation_method == NavigationMethod::Tab || e.navigation_method == NavigationMethod::Directional)
190        {
191            if let Some(adorner_layer) = AdornerLayer::get_adorner_layer(this) {
192                if this.focus_adorner.borrow().is_none() {
193                    let template = if this.is_set(Self::focus_adorner_property().as_property()) {
194                        this.get_value(Self::focus_adorner_property())
195                    } else {
196                        adorner_layer.default_focus_adorner()
197                    };
198
199                    if let Some(template) = template {
200                        *this.focus_adorner.borrow_mut() = template.build_typed();
201                    }
202                }
203
204                let focus_adorner = this.focus_adorner.borrow().clone();
205                if let (Some(focus_adorner), Some(target)) = (focus_adorner, this.get_template_focus_target()) {
206                    AdornerLayer::set_adorned_element(&focus_adorner, target);
207                    adorner_layer.children().add(focus_adorner);
208                }
209            }
210        }
211
212        // Reveal focus (winui-materials.md): a glow around the focus visual.
213        if this.is_focused() {
214            let target = if this.focus_adorner.borrow().is_some() { this.get_template_focus_target() } else { None };
215            crate::media::on_control_got_focus(
216                target.as_ref().map(|target| {
217                    let visual: &ferroui_base::Visual = target;
218                    visual
219                }),
220                e,
221            );
222        }
223    }
224
225    fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
226        Self::parent_on_lost_focus(this, e);
227
228        let target = this.get_template_focus_target();
229        crate::media::on_control_lost_focus(
230            this,
231            target.as_ref().map(|target| {
232                let visual: &ferroui_base::Visual = target;
233                visual
234            }),
235        );
236
237        let focus_adorner = this.focus_adorner.borrow().clone();
238        if let Some(focus_adorner) = focus_adorner {
239            if let Some(adorner_layer) = focus_adorner.parent().and_then(|parent| parent.cast::<Panel>()) {
240                adorner_layer.children().remove(focus_adorner);
241                *this.focus_adorner.borrow_mut() = None;
242            }
243        }
244    }
245
246    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
247        Self::parent_on_pointer_released(this, e);
248
249        if e.is_source(this) && !e.handled() && e.initial_press_mouse_button() == MouseButton::Right {
250            let args = ContextRequestedEventArgs::from_pointer_event_args(e);
251            this.raise_event(&args);
252            e.set_handled(args.handled());
253        }
254    }
255
256    fn on_key_up(this: &Self, e: &KeyEventArgs) {
257        Self::parent_on_key_up(this, e);
258
259        if e.is_source(this) && !e.handled() {
260            let Some(settings) = this.get_platform_settings() else { return };
261            let configuration = settings.hotkey_configuration();
262            let keymap = &configuration.open_context_menu;
263
264            let matches = keymap.iter().any(|key| key.matches(Some(e)));
265
266            if matches {
267                let args = ContextRequestedEventArgs::new();
268                this.raise_event(&args);
269                e.set_handled(args.handled());
270            }
271        }
272    }
273}
274
275impl ControlImpl for Control {
276    fn get_template_focus_target(this: &Self) -> Option<Ref<Control>> {
277        Some(this.to_ref())
278    }
279
280    fn on_loaded(this: &Self, e: &RoutedEventArgs) {
281        this.raise_event(e);
282    }
283
284    fn on_unloaded(this: &Self, e: &RoutedEventArgs) {
285        this.raise_event(e);
286    }
287
288    fn on_create_automation_peer(this: &Self) -> Ref<AutomationPeer> {
289        NoneAutomationPeer::new(this).upcast()
290    }
291
292    fn on_size_changed(this: &Self, e: &SizeChangedEventArgs) {
293        this.raise_event(e);
294    }
295
296    fn effective_context_flyout(this: &Self, _for_request: bool) -> Option<Ref<FlyoutBase>> {
297        this.context_flyout()
298    }
299
300    fn context_request_generation(_this: &Self) -> Option<u64> {
301        None
302    }
303}
304
305ferroui_base::ferro_properties! { impl Control {
306    ferro_property!(
307        /// Defines the `FocusAdorner` property.
308        pub fn focus_adorner_property() -> StyledProperty<Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>>> {
309            FerroProperty::register::<Control, _>("FocusAdorner", None)
310        }
311    );
312
313    ferro_property!(
314        /// Defines the `Tag` property.
315        pub fn tag_property() -> StyledProperty<Option<BoxedValue>> {
316            FerroProperty::register::<Control, _>("Tag", None)
317        }
318    );
319
320    ferro_property!(
321        /// Defines the `ContextMenu` property.
322        pub fn context_menu_property() -> StyledProperty<Option<Ref<ContextMenu>>> {
323            FerroProperty::register::<Control, _>("ContextMenu", None)
324        }
325    );
326
327    ferro_property!(
328        /// Defines the `ContextFlyout` property.
329        pub fn context_flyout_property() -> StyledProperty<Option<Ref<FlyoutBase>>> {
330            FerroProperty::register::<Control, _>("ContextFlyout", None)
331        }
332    );
333} }
334
335impl Control {
336    ferro_routed_event!(
337        /// Event raised when an element wishes to be scrolled into view.
338        pub fn request_bring_into_view_event() -> RoutedEvent<RequestBringIntoViewEventArgs> {
339            RoutedEvent::register::<Control, _>("RequestBringIntoView", RoutingStrategies::BUBBLE)
340        }
341    );
342
343    ferro_routed_event!(
344        /// Defines the `Loaded` event.
345        pub fn loaded_event() -> RoutedEvent<RoutedEventArgs> {
346            RoutedEvent::register::<Control, _>("Loaded", RoutingStrategies::DIRECT)
347        }
348    );
349
350    ferro_routed_event!(
351        /// Defines the `Unloaded` event.
352        pub fn unloaded_event() -> RoutedEvent<RoutedEventArgs> {
353            RoutedEvent::register::<Control, _>("Unloaded", RoutingStrategies::DIRECT)
354        }
355    );
356
357    ferro_routed_event!(
358        /// Defines the `SizeChanged` event.
359        pub fn size_changed_event() -> RoutedEvent<SizeChangedEventArgs> {
360            RoutedEvent::register::<Control, _>("SizeChanged", RoutingStrategies::DIRECT)
361        }
362    );
363
364    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
365    pub fn construct() -> Self {
366        Self { base: InputElement::construct(), load_state: Cell::new(LoadState::Unloaded), data_templates: OnceCell::new(), focus_adorner: RefCell::new(None), context_flyout_handlers: Cell::new(None), context_menu_handlers: Cell::new(None), automation_peer: RefCell::new(None) }
367    }
368
369    pub fn new() -> Ref<Self> {
370        instantiate(Self::construct())
371    }
372
373    /// Gets the automation peer of the control, if it has been created.
374    pub(crate) fn get_automation_peer(&self) -> Option<Ref<AutomationPeer>> {
375        self.verify_access();
376        self.automation_peer.borrow().clone()
377    }
378
379    /// Gets the automation peer of the control, creating it with
380    /// `on_create_automation_peer` if it does not exist yet.
381    pub(crate) fn get_or_create_automation_peer(&self) -> Ref<AutomationPeer> {
382        self.verify_access();
383
384        if let Some(peer) = self.automation_peer.borrow().clone() {
385            return peer;
386        }
387
388        let peer = self.on_create_automation_peer();
389        *self.automation_peer.borrow_mut() = Some(peer.clone());
390        peer
391    }
392
393    /// The control's focus adorner.
394    pub fn focus_adorner(&self) -> Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>> {
395        self.get_value(Self::focus_adorner_property())
396    }
397
398    pub fn set_focus_adorner(&self, value: Option<Rc<dyn ITemplateOf<Option<Ref<Control>>>>>) {
399        self.set_value(Self::focus_adorner_property(), value)
400    }
401
402    /// The data templates for the control.
403    ///
404    /// Each control may define data templates which are applied to the
405    /// control itself and its children.
406    pub fn data_templates(&self) -> DataTemplates {
407        self.data_templates.get_or_init(DataTemplates::new).clone()
408    }
409
410    /// Whether the data templates collection has been created.
411    pub fn is_data_templates_initialized(&self) -> bool {
412        self.data_templates.get().is_some()
413    }
414
415    /// A context menu to the control.
416    pub fn context_menu(&self) -> Option<Ref<ContextMenu>> {
417        self.get_value(Self::context_menu_property())
418    }
419
420    pub fn set_context_menu(&self, value: impl Into<Nullable<ContextMenu>>) {
421        self.set_value(Self::context_menu_property(), value.into().0)
422    }
423
424    /// The handlers the context menu support attached to this control.
425    pub(crate) fn context_menu_handlers(&self) -> &Cell<Option<ContextMenuHandlers>> {
426        &self.context_menu_handlers
427    }
428
429    /// A context flyout to the control.
430    pub fn context_flyout(&self) -> Option<Ref<FlyoutBase>> {
431        self.get_value(Self::context_flyout_property())
432    }
433
434    pub fn set_context_flyout(&self, value: impl Into<Nullable<FlyoutBase>>) {
435        self.set_value(Self::context_flyout_property(), value.into().0)
436    }
437
438    /// The handlers the context flyout support attached to this control
439    /// for its context requested and context canceled events.
440    pub(crate) fn context_flyout_handlers(&self) -> &Cell<Option<(RoutedEventHandlerToken, RoutedEventHandlerToken)>> {
441        &self.context_flyout_handlers
442    }
443
444    /// Whether the control is fully constructed in the visual tree and both
445    /// layout and render are complete.
446    ///
447    /// This is set to true while raising the `Loaded` event.
448    pub fn is_loaded(&self) -> bool {
449        self.load_state.get() == LoadState::Loaded
450    }
451
452    /// A user-defined object attached to the control.
453    pub fn tag(&self) -> Option<BoxedValue> {
454        self.get_value(Self::tag_property())
455    }
456
457    pub fn set_tag(&self, value: Option<BoxedValue>) {
458        self.set_value(Self::tag_property(), value)
459    }
460
461    /// Occurs when the control has been fully constructed in the visual tree
462    /// and both layout and render are complete.
463    ///
464    /// This event is guaranteed to occur after the control template is
465    /// applied and references to objects created after the template is
466    /// applied are available. This makes it different from
467    /// `on_attached_to_visual_tree` which doesn't have these references. This
468    /// event occurs at the latest possible time in the control creation
469    /// life-cycle.
470    pub fn loaded(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
471        self.add_handler(Self::loaded_event(), handler)
472    }
473
474    /// Occurs when the control is removed from the visual tree.
475    ///
476    /// This is API symmetrical with `loaded` and exists for compatibility
477    /// with other XAML frameworks; however, it behaves the same as
478    /// `on_detached_from_visual_tree`.
479    pub fn unloaded(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
480        self.add_handler(Self::unloaded_event(), handler)
481    }
482
483    /// Occurs when the bounds (actual size) of the control have changed.
484    pub fn size_changed(
485        &self,
486        handler: impl Fn(&Interactive, &SizeChangedEventArgs) + 'static,
487    ) -> RoutedEventHandlerToken {
488        self.add_handler(Self::size_changed_event(), handler)
489    }
490
491    /// The boxing convention for a control held in an untyped value (a
492    /// `Content`, a `Tag`, an item): the value holds a `Ref<Control>`.
493    pub fn boxed(control: impl IntoRef<Control>) -> BoxedValue {
494        Rc::new(control.into_ref())
495    }
496
497    /// The control held by an untyped value, if it holds one (`value as
498    /// Control` upstream). See [`boxed`](Self::boxed).
499    ///
500    /// The value is a control whatever handle it holds it by: the
501    /// `Ref<Control>` of the boxing convention, the handle of another class
502    /// of the object model (a `Ref<Border>`, a `Ref<FerroObject>`), or an
503    /// interface handle that holds an object
504    /// ([`ValueTypes::register_interface_object`]); a value that a binding
505    /// passes on keeps the static type of its source.
506    pub fn from_boxed(value: &BoxedValue) -> Option<Ref<Control>> {
507        let value: &dyn AnyValue = &**value;
508        if let Some(control) = value.downcast_ref::<Ref<Control>>() {
509            return Some(control.clone());
510        }
511        ValueTypes::as_object(value)?.cast::<Control>()
512    }
513
514    /// The logical element held by an untyped value, if it holds one
515    /// (`value as ILogical` upstream): an element held by any handle, as
516    /// [`from_boxed`](Self::from_boxed) takes it.
517    pub fn logical_from_boxed(value: &BoxedValue) -> Option<Ref<StyledElement>> {
518        let value: &dyn AnyValue = &**value;
519        if let Some(control) = value.downcast_ref::<Ref<Control>>() {
520            return Some(control.clone().upcast());
521        }
522        if let Some(element) = value.downcast_ref::<Ref<StyledElement>>() {
523            return Some(element.clone());
524        }
525        ValueTypes::as_object(value)?.cast::<StyledElement>()
526    }
527
528    /// Ensures that the control is initialized and laid out when it is used
529    /// as the source of a visual brush while not attached to a visual root.
530    pub fn ensure_initialized(&self) {
531        if self.visual_root().is_none() {
532            if !self.is_initialized() {
533                for i in self.get_self_and_visual_descendants() {
534                    if let Some(c) = i.downcast_ref::<Control>() {
535                        if !c.is_initialized() {
536                            c.begin_init();
537                            c.end_init();
538                        }
539                    }
540                }
541            }
542
543            if !self.is_arrange_valid() {
544                self.measure(Size::INFINITY);
545                self.arrange(Rect::from_size(self.desired_size()));
546            }
547        }
548    }
549
550    fn loaded_processing_action() {
551        // Copy the loaded queue for processing. Two collections are used so
552        // that while one is being processed the other accepts adding new
553        // controls to process next: new controls can be added within the
554        // Loaded callback/event itself.
555        LOADED.with(|loaded| {
556            let mut loaded = loaded.borrow_mut();
557            let queue = std::mem::take(&mut loaded.queue);
558            loaded.queued.clear();
559            loaded.processing_queue.extend(queue);
560        });
561
562        // A panic from `on_loaded_core` (a Loaded handler or override)
563        // propagates to the dispatcher as usual, but the processing machinery
564        // must recover: controls left unprocessed are requeued and a new
565        // dispatcher job is posted for them, so a single faulty handler
566        // cannot stop Loaded from ever being raised again.
567        struct Finally;
568
569        impl Drop for Finally {
570            fn drop(&mut self) {
571                let restart = LOADED.with(|loaded| {
572                    let mut loaded = loaded.borrow_mut();
573                    let remaining = std::mem::take(&mut loaded.processing_queue);
574                    for control in remaining {
575                        loaded.add(control);
576                    }
577                    loaded.is_loaded_processing = false;
578
579                    // Restart if any controls were added to the queue while
580                    // processing.
581                    if !loaded.queue.is_empty() {
582                        loaded.is_loaded_processing = true;
583                        true
584                    } else {
585                        false
586                    }
587                });
588
589                if restart {
590                    Dispatcher::ui_thread().post_local(Control::loaded_processing_action, DispatcherPriority::LOADED);
591                }
592            }
593        }
594
595        let _finally = Finally;
596
597        loop {
598            let next = LOADED.with(|loaded| loaded.borrow_mut().processing_queue.pop_front());
599            match next {
600                Some(control) => control.on_loaded_core(),
601                None => break,
602            }
603        }
604    }
605
606    /// Schedules `on_loaded_core` to be called for this control. For
607    /// performance, it will be queued with other controls.
608    pub(crate) fn schedule_on_loaded_core(&self) {
609        if self.load_state.get() == LoadState::Unloaded {
610            let post = LOADED.with(|loaded| {
611                let mut loaded = loaded.borrow_mut();
612                let is_added = loaded.add(self.to_ref());
613                if is_added && !loaded.is_loaded_processing {
614                    loaded.is_loaded_processing = true;
615                    true
616                } else {
617                    false
618                }
619            });
620            self.load_state.set(LoadState::LoadPending);
621
622            if post {
623                Dispatcher::ui_thread().post_local(Self::loaded_processing_action, DispatcherPriority::LOADED);
624            }
625        }
626    }
627
628    /// Invoked as the first step of marking the control as loaded and
629    /// raising the `Loaded` event.
630    pub(crate) fn on_loaded_core(&self) {
631        if self.load_state.get() == LoadState::LoadPending && self.is_attached_to_logical_tree() {
632            self.load_state.set(LoadState::Loaded);
633
634            self.on_loaded(&RoutedEventArgs::with_event_and_source(Self::loaded_event(), self.to_ref()));
635        } else {
636            // We somehow got here while being detached?
637            self.load_state.set(LoadState::Unloaded);
638        }
639    }
640
641    /// Invoked as the first step of marking the control as unloaded and
642    /// raising the `Unloaded` event.
643    pub(crate) fn on_unloaded_core(&self) {
644        match self.load_state.get() {
645            LoadState::Loaded => {
646                self.load_state.set(LoadState::Unloaded);
647
648                self.on_unloaded(&RoutedEventArgs::with_event_and_source(Self::unloaded_event(), self.to_ref()));
649            }
650            LoadState::LoadPending => {
651                // Remove from the loaded event queue here as a failsafe in
652                // case the control is detached before the dispatcher runs
653                // the Loaded jobs.
654                let this = self.to_ref();
655                LOADED.with(|loaded| loaded.borrow_mut().remove(&this));
656
657                self.load_state.set(LoadState::Unloaded);
658            }
659            LoadState::Unloaded => {}
660        }
661    }
662
663    /// Clears the queue of controls waiting for their `Loaded` event. Since
664    /// tests reset the dispatcher instance, the callback might never arrive.
665    #[doc(hidden)]
666    pub fn reset_loaded_queue_for_unit_tests() {
667        LOADED.with(|loaded| {
668            let mut loaded = loaded.borrow_mut();
669            loaded.queue.clear();
670            loaded.queued.clear();
671            loaded.processing_queue.clear();
672            loaded.is_loaded_processing = false;
673        });
674    }
675}
676
677impl IDataTemplateHost for Control {
678    fn data_templates(&self) -> DataTemplates {
679        Control::data_templates(self)
680    }
681
682    fn is_data_templates_initialized(&self) -> bool {
683        Control::is_data_templates_initialized(self)
684    }
685}
686
687impl ISetterValue for Control {
688    fn initialize(&self, setter: &Setter) {
689        // A context menu implements the contract itself.
690        if let Some(context_menu) = self.downcast_ref::<ContextMenu>() {
691            return context_menu.initialize(setter);
692        }
693
694        if setter.property() == Some(Self::context_flyout_property().as_property()) {
695            return; // Allow ContextFlyout to not need wrapping in a template
696        }
697
698        panic!("Cannot use a control as a Setter value. Wrap the control in a <Template>.");
699    }
700}