Skip to main content

ferroui_controls/
context_menu.rs

1use crate::diagnostics::IPopupHostProvider;
2use crate::platform::{DefaultMenuInteractionHandler, FeedbackAction, IMenuInteractionHandler, PlatformFeedbackExtensions};
3use crate::primitives::popup_positioning::{
4    CustomPopupPlacementCallback, PopupAnchor, PopupGravity, PopupPositionerConstraintAdjustment,
5};
6use crate::primitives::{CustomPopupPlacementCallbackValue, IPopupHost, Popup, SelectingItemsControlImpl, TemplatedControlImpl};
7use crate::{Application, Control, ControlImpl, ItemsControlImpl, MenuBase, MenuBaseImpl, MenuItem, PlacementMode};
8use ferroui_base::input::{ContextRequestedEventArgs, FocusManager, InputElement, InputElementImpl, KeyEventArgs};
9use ferroui_base::interactivity::{Interactive, InteractiveImpl, RoutedEventArgs};
10use ferroui_base::layout::LayoutableImpl;
11use ferroui_base::reactive::{Disposable, IDisposable};
12use ferroui_base::styling::{ISetterValue, Setter};
13use ferroui_base::utilities::{CancelEventArgs, HandlerList};
14use ferroui_base::{
15    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, ElementRef, FerroObjectImpl,
16    FerroObjectImplExt, FerroPropertyChangedEventArgs, Nullable, Rect, Ref, StyledElement, StyledElementImpl,
17    StyledProperty, VisualImpl, VisualTreeAttachmentEventArgs, WeakRef,
18};
19use std::cell::RefCell;
20use std::rc::Rc;
21
22/// A control context menu.
23#[repr(C)]
24pub struct ContextMenu {
25    base: MenuBase,
26    popup: RefCell<Option<Ref<Popup>>>,
27    /// The controls the menu is attached to. The controls own the menu (it
28    /// is the value of their `ContextMenu` property), so the references
29    /// back are weak.
30    attached_controls: RefCell<Option<Vec<WeakRef<Control>>>>,
31    /// The element that had the focus when the popup opened. Only written,
32    /// as in the reference; held weakly since it is not owned by the menu.
33    previous_focus: RefCell<Option<WeakRef<InputElement>>>,
34    popup_host_changed_handler: HandlerList<dyn Fn(Option<&Rc<dyn IPopupHost>>)>,
35    opening: HandlerList<dyn Fn(&CancelEventArgs)>,
36    closing: HandlerList<dyn Fn(&CancelEventArgs)>,
37}
38
39ferro_class!(ContextMenu: MenuBase);
40ferro_class_info!(ContextMenu { new: ContextMenu::new });
41
42ferro_impl_classes!(
43    ContextMenu: StyledElementImpl,
44    VisualImpl,
45    LayoutableImpl,
46    InteractiveImpl,
47    InputElementImpl,
48    ControlImpl,
49    TemplatedControlImpl,
50    ItemsControlImpl,
51    SelectingItemsControlImpl
52);
53
54impl FerroObjectImpl for ContextMenu {
55    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
56        Self::parent_on_property_changed(this, change);
57
58        if change.property() == Self::window_manager_add_shadow_hint_property().as_property() {
59            let popup = this.popup.borrow().clone();
60            if let Some(popup) = popup {
61                popup.set_window_manager_add_shadow_hint(change.get_new_value::<bool>());
62            }
63        }
64    }
65}
66
67impl MenuBaseImpl for ContextMenu {
68    /// Opens the menu.
69    fn open(this: &Self) {
70        this.open_at(None);
71    }
72
73    /// Closes the menu.
74    fn close(this: &Self) {
75        if !this.is_open() {
76            return;
77        }
78
79        let popup = this.popup.borrow().clone();
80        if let Some(popup) = popup {
81            if popup.is_visible() {
82                popup.set_is_open(false);
83            }
84        }
85    }
86}
87
88impl ISetterValue for ContextMenu {
89    fn initialize(&self, setter: &Setter) {
90        // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
91        // the behavior defined in Control which requires controls to be wrapped in a <template>.
92        if setter.property() != Some(Control::context_menu_property().as_property()) {
93            panic!("Cannot use a control as a Setter value. Wrap the control in a <Template>.");
94        }
95    }
96}
97
98ferro_properties! {
99    impl ContextMenu {
100        /// Defines the `HorizontalOffset` property.
101        pub fn horizontal_offset_property() -> StyledProperty<f64> {
102            Popup::horizontal_offset_property().add_owner::<ContextMenu>()
103        }
104
105        /// Defines the `VerticalOffset` property.
106        pub fn vertical_offset_property() -> StyledProperty<f64> {
107            Popup::vertical_offset_property().add_owner::<ContextMenu>()
108        }
109
110        /// Defines the `PlacementAnchor` property.
111        pub fn placement_anchor_property() -> StyledProperty<PopupAnchor> {
112            Popup::placement_anchor_property().add_owner::<ContextMenu>()
113        }
114
115        /// Defines the `PlacementConstraintAdjustment` property.
116        pub fn placement_constraint_adjustment_property() -> StyledProperty<PopupPositionerConstraintAdjustment> {
117            Popup::placement_constraint_adjustment_property().add_owner::<ContextMenu>()
118        }
119
120        /// Defines the `PlacementGravity` property.
121        pub fn placement_gravity_property() -> StyledProperty<PopupGravity> {
122            Popup::placement_gravity_property().add_owner::<ContextMenu>()
123        }
124
125        /// Defines the `Placement` property.
126        pub fn placement_property() -> StyledProperty<PlacementMode> {
127            Popup::placement_property().add_owner::<ContextMenu>()
128        }
129
130        /// Defines the `PlacementRect` property.
131        pub fn placement_rect_property() -> StyledProperty<Option<Rect>> {
132            Popup::placement_rect_property().add_owner::<ContextMenu>()
133        }
134
135        /// Defines the `WindowManagerAddShadowHint` property.
136        pub fn window_manager_add_shadow_hint_property() -> StyledProperty<bool> {
137            Popup::window_manager_add_shadow_hint_property().add_owner::<ContextMenu>()
138        }
139
140        /// Defines the `PlacementTarget` property.
141        ///
142        /// The menu does not own its placement target, so the value is an
143        /// element reference.
144        pub fn placement_target_property() -> StyledProperty<Option<ElementRef<Control>>> {
145            Popup::placement_target_property().add_owner::<ContextMenu>()
146        }
147
148        /// Defines the `CustomPopupPlacementCallback` property.
149        pub fn custom_popup_placement_callback_property() -> StyledProperty<Option<CustomPopupPlacementCallbackValue>> {
150            Popup::custom_popup_placement_callback_property().add_owner::<ContextMenu>()
151        }
152    }
153}
154
155impl ContextMenu {
156    /// Initializes static members of the [`ContextMenu`] class.
157    fn static_constructor() {
158        Self::placement_property().override_default_value::<ContextMenu>(PlacementMode::Pointer);
159        let _ = Control::context_menu_property().changed().subscribe(Self::context_menu_changed);
160        crate::automation::AutomationProperties::accessibility_view_property()
161            .override_default_value::<ContextMenu>(crate::automation::AccessibilityView::Control);
162        crate::automation::AutomationProperties::control_type_override_property()
163            .override_default_value::<ContextMenu>(Some(crate::automation::peers::AutomationControlType::Menu));
164    }
165
166    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
167    pub fn construct() -> Self {
168        Self::construct_with(DefaultMenuInteractionHandler::new(true))
169    }
170
171    /// Creates the class data of a context menu that uses
172    /// `interaction_handler`.
173    pub fn construct_with(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Self {
174        Self {
175            base: MenuBase::construct_with(interaction_handler),
176            popup: RefCell::new(None),
177            attached_controls: RefCell::new(None),
178            previous_focus: RefCell::new(None),
179            popup_host_changed_handler: HandlerList::new(),
180            opening: HandlerList::new(),
181            closing: HandlerList::new(),
182        }
183    }
184
185    /// Initializes a new instance of the [`ContextMenu`] class.
186    pub fn new() -> Ref<Self> {
187        instantiate(Self::construct())
188    }
189
190    /// Initializes a new instance of the [`ContextMenu`] class that uses
191    /// `interaction_handler`.
192    pub fn with_interaction_handler(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Ref<Self> {
193        instantiate(Self::construct_with(interaction_handler))
194    }
195
196    /// The horizontal offset of the menu in relation to its target; see
197    /// [`Popup::horizontal_offset`].
198    pub fn horizontal_offset(&self) -> f64 {
199        self.get_value(Self::horizontal_offset_property())
200    }
201
202    pub fn set_horizontal_offset(&self, value: f64) {
203        self.set_value(Self::horizontal_offset_property(), value)
204    }
205
206    /// The vertical offset of the menu in relation to its target; see
207    /// [`Popup::vertical_offset`].
208    pub fn vertical_offset(&self) -> f64 {
209        self.get_value(Self::vertical_offset_property())
210    }
211
212    pub fn set_vertical_offset(&self, value: f64) {
213        self.set_value(Self::vertical_offset_property(), value)
214    }
215
216    /// See [`Popup::placement_anchor`].
217    pub fn placement_anchor(&self) -> PopupAnchor {
218        self.get_value(Self::placement_anchor_property())
219    }
220
221    pub fn set_placement_anchor(&self, value: PopupAnchor) {
222        self.set_value(Self::placement_anchor_property(), value)
223    }
224
225    /// See [`Popup::placement_constraint_adjustment`].
226    pub fn placement_constraint_adjustment(&self) -> PopupPositionerConstraintAdjustment {
227        self.get_value(Self::placement_constraint_adjustment_property())
228    }
229
230    pub fn set_placement_constraint_adjustment(&self, value: PopupPositionerConstraintAdjustment) {
231        self.set_value(Self::placement_constraint_adjustment_property(), value)
232    }
233
234    /// See [`Popup::placement_gravity`].
235    pub fn placement_gravity(&self) -> PopupGravity {
236        self.get_value(Self::placement_gravity_property())
237    }
238
239    pub fn set_placement_gravity(&self, value: PopupGravity) {
240        self.set_value(Self::placement_gravity_property(), value)
241    }
242
243    /// See [`Popup::placement`].
244    pub fn placement(&self) -> PlacementMode {
245        self.get_value(Self::placement_property())
246    }
247
248    pub fn set_placement(&self, value: PlacementMode) {
249        self.set_value(Self::placement_property(), value)
250    }
251
252    /// See [`Popup::window_manager_add_shadow_hint`].
253    pub fn window_manager_add_shadow_hint(&self) -> bool {
254        self.get_value(Self::window_manager_add_shadow_hint_property())
255    }
256
257    pub fn set_window_manager_add_shadow_hint(&self, value: bool) {
258        self.set_value(Self::window_manager_add_shadow_hint_property(), value)
259    }
260
261    /// See [`Popup::placement_rect`].
262    pub fn placement_rect(&self) -> Option<Rect> {
263        self.get_value(Self::placement_rect_property())
264    }
265
266    pub fn set_placement_rect(&self, value: Option<Rect>) {
267        self.set_value(Self::placement_rect_property(), value)
268    }
269
270    /// See [`Popup::placement_target`].
271    pub fn placement_target(&self) -> Option<Ref<Control>> {
272        ElementRef::resolve(&self.get_value(Self::placement_target_property()))
273    }
274
275    pub fn set_placement_target(&self, value: impl Into<Nullable<Control>>) {
276        self.set_value(Self::placement_target_property(), ElementRef::from_nullable(value.into().0))
277    }
278
279    /// See [`Popup::custom_popup_placement_callback`].
280    pub fn custom_popup_placement_callback(&self) -> Option<CustomPopupPlacementCallback> {
281        self.get_value(Self::custom_popup_placement_callback_property()).map(|value| value.0)
282    }
283
284    pub fn set_custom_popup_placement_callback(&self, value: Option<CustomPopupPlacementCallback>) {
285        self.set_value(
286            Self::custom_popup_placement_callback_property(),
287            value.map(CustomPopupPlacementCallbackValue),
288        )
289    }
290
291    fn subscribe<F: ?Sized + 'static>(
292        &self,
293        select: fn(&ContextMenu) -> &HandlerList<F>,
294        handler: Rc<F>,
295    ) -> Rc<dyn IDisposable> {
296        let token = select(self).add(handler);
297        let weak = self.to_ref().downgrade();
298        Disposable::create(move || {
299            if let Some(this) = weak.upgrade() {
300                select(&this).remove(token);
301            }
302        })
303    }
304
305    /// Occurs when the value of the `IsOpen` property is changing from
306    /// false to true. Disposing the returned handle unsubscribes.
307    pub fn opening(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
308        self.subscribe::<dyn Fn(&CancelEventArgs)>(|menu| &menu.opening, Rc::new(handler))
309    }
310
311    /// Occurs when the value of the `IsOpen` property is changing from
312    /// true to false. Disposing the returned handle unsubscribes.
313    pub fn closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
314        self.subscribe::<dyn Fn(&CancelEventArgs)>(|menu| &menu.closing, Rc::new(handler))
315    }
316
317    /// The controls the menu is attached to that are still alive, or
318    /// `None` when the list was never created.
319    fn attached_controls(&self) -> Option<Vec<Ref<Control>>> {
320        self.attached_controls.borrow().as_ref().map(|controls| controls.iter().filter_map(WeakRef::upgrade).collect())
321    }
322
323    /// Removes the first occurrence of `control` from the attached
324    /// controls.
325    fn remove_attached_control(&self, control: &Ref<Control>) {
326        if let Some(controls) = self.attached_controls.borrow_mut().as_mut() {
327            // Controls that are gone are dropped from the list on the way.
328            controls.retain(|c| c.upgrade().is_some());
329            if let Some(index) = controls.iter().position(|c| c.upgrade().as_ref() == Some(control)) {
330                controls.remove(index);
331            }
332        }
333    }
334
335    /// Called when the `ContextMenu` property changes on a control.
336    fn context_menu_changed(e: &FerroPropertyChangedEventArgs<'_>) {
337        let control = e.sender().downcast_ref::<Control>().expect("the sender is a control");
338        let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<ContextMenu>>>();
339
340        if let Some(old_menu) = old_value {
341            if let Some((context_requested, context_canceled, attached, detached)) =
342                control.context_menu_handlers().take()
343            {
344                control.remove_handler(InputElement::context_requested_event(), context_requested);
345                control.remove_handler(InputElement::context_canceled_event(), context_canceled);
346                attached.dispose();
347                detached.dispose();
348            }
349            old_menu.remove_attached_control(&control.to_ref());
350            let popup = old_menu.popup.borrow().clone();
351            if let Some(popup) = popup {
352                popup.set_parent(None);
353            }
354        }
355
356        if new_value.is_some() {
357            let context_requested =
358                control.add_handler(InputElement::context_requested_event(), Self::control_context_requested);
359            let context_canceled =
360                control.add_handler(InputElement::context_canceled_event(), Self::control_context_canceled);
361            let weak = control.to_ref().downgrade();
362            let attached = control.attached_to_visual_tree({
363                let weak = weak.clone();
364                move |e| {
365                    if let Some(control) = weak.upgrade() {
366                        Self::control_on_attached_to_visual_tree(&control, e);
367                    }
368                }
369            });
370            let detached = control.detached_from_visual_tree(move |e| {
371                if let Some(control) = weak.upgrade() {
372                    Self::control_detached_from_visual_tree(&control, e);
373                }
374            });
375            control.context_menu_handlers().set(Some((context_requested, context_canceled, attached, detached)));
376        }
377
378        if control.is_attached_to_visual_tree() {
379            Self::attach_control_to_context_menu(control);
380        }
381    }
382
383    /// Opens a context menu on the specified control.
384    ///
385    /// # Panics
386    ///
387    /// Panics when `control` is `None` and the menu is attached to no
388    /// control, and when `control` is not one of the controls the menu is
389    /// attached to.
390    pub fn open_at(&self, control: Option<&Ref<Control>>) {
391        let attached_controls = self.attached_controls();
392
393        if control.is_none() && attached_controls.as_ref().is_none_or(|controls| controls.is_empty()) {
394            panic!("Value cannot be null. (Parameter 'control')");
395        }
396
397        if let (Some(control), Some(attached_controls)) = (control, &attached_controls) {
398            if !attached_controls.contains(control) {
399                panic!(
400                    "Cannot show ContentMenu on a different control to the one it is attached to. (Parameter 'control')"
401                );
402            }
403        }
404
405        let control = match control {
406            Some(control) => control.clone(),
407            None => attached_controls.expect("checked above")[0].clone(),
408        };
409        let placement_target = self.placement_target().unwrap_or_else(|| control.clone());
410        self.open_core(&control, &placement_target, self.placement());
411    }
412
413    /// The host of the popup the menu is shown in, while it is open.
414    pub fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
415        let popup = self.popup.borrow().clone();
416        popup.and_then(|popup| popup.host())
417    }
418
419    /// Raised when the popup host changes (the popup host provider
420    /// contract). Disposing the returned handle unsubscribes.
421    pub fn popup_host_changed(
422        &self,
423        handler: impl Fn(Option<&Rc<dyn IPopupHost>>) + 'static,
424    ) -> Rc<dyn IDisposable> {
425        self.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|menu| &menu.popup_host_changed_handler, Rc::new(handler))
426    }
427
428    /// The menu as a popup host provider (the diagnostics contract it
429    /// implements in the reference).
430    pub fn to_popup_host_provider(&self) -> Rc<dyn IPopupHostProvider> {
431        Rc::new(ContextMenuHostProvider(self.to_ref()))
432    }
433
434    fn open_core(&self, control: &Ref<Control>, placement_target: &Ref<Control>, placement: PlacementMode) {
435        if self.is_open() {
436            return;
437        }
438
439        let existing = self.popup.borrow().clone();
440        let popup = match existing {
441            Some(popup) => popup,
442            None => {
443                let popup = Popup::new();
444                popup.set_is_light_dismiss_enabled(true);
445                popup.set_overlay_dismiss_event_pass_through(true);
446                popup.set_takes_focus_from_native_control(Popup::get_takes_focus_from_native_control(self));
447
448                // The popup belongs to the menu and lives as long as it:
449                // the handlers are never removed.
450                let weak = self.to_ref().downgrade();
451                let _ = popup.opened({
452                    let weak = weak.clone();
453                    move || {
454                        if let Some(this) = weak.upgrade() {
455                            this.popup_opened();
456                        }
457                    }
458                });
459                let _ = popup.closed({
460                    let weak = weak.clone();
461                    move || {
462                        if let Some(this) = weak.upgrade() {
463                            this.popup_closed();
464                        }
465                    }
466                });
467                let _ = popup.closing({
468                    let weak = weak.clone();
469                    move |e| {
470                        if let Some(this) = weak.upgrade() {
471                            this.popup_closing(e);
472                        }
473                    }
474                });
475                popup.add_handler(InputElement::key_up_event(), move |_, e: &KeyEventArgs| {
476                    if let Some(this) = weak.upgrade() {
477                        this.popup_key_up(e);
478                    }
479                });
480
481                *self.popup.borrow_mut() = Some(popup.clone());
482                popup
483            }
484        };
485
486        popup.set_popup_parent(Some(control));
487
488        popup.set_placement(placement);
489
490        // Position of the line below is really important.
491        // All styles are being applied only when control has logical parent.
492        // Line below will add ContextMenu as child to the Popup and this will trigger styles and they would be applied.
493        // If you will move line below somewhere else it may cause that ContextMenu will behave differently from what you are expecting.
494        popup.set_child(self.to_ref());
495        popup.set_placement_target(placement_target);
496        popup.set_horizontal_offset(self.horizontal_offset());
497        popup.set_vertical_offset(self.vertical_offset());
498        popup.set_placement_anchor(self.placement_anchor());
499        popup.set_placement_constraint_adjustment(self.placement_constraint_adjustment());
500        popup.set_placement_gravity(self.placement_gravity());
501        popup.set_placement_rect(self.placement_rect());
502        popup.set_custom_popup_placement_callback(self.custom_popup_placement_callback());
503        popup.set_window_manager_add_shadow_hint(self.window_manager_add_shadow_hint());
504        self.set_is_open(true);
505        popup.set_is_open(true);
506
507        self.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::opened_event(), self.to_ref()));
508    }
509
510    fn popup_opened(&self) {
511        let previous_focus = FocusManager::get_focus_manager(self).and_then(|manager| manager.get_focused_element());
512        *self.previous_focus.borrow_mut() = previous_focus.map(|element| element.downgrade());
513        self.focus();
514
515        let host = self.popup_host();
516        for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
517            handler(host.as_ref());
518        }
519    }
520
521    fn popup_closing(&self, e: &CancelEventArgs) {
522        e.set_cancel(self.cancel_closing());
523    }
524
525    fn popup_closed(&self) {
526        let children = StyledElement::logical_children(self).to_vec();
527        for i in children {
528            if let Some(menu_item) = i.cast::<MenuItem>() {
529                menu_item.set_is_sub_menu_open(false);
530            }
531        }
532
533        self.set_selected_index(-1);
534        self.set_is_open(false);
535
536        let popup = self.popup.borrow().clone();
537
538        if self.attached_controls().is_none_or(|controls| controls.is_empty()) {
539            if let Some(popup) = &popup {
540                popup.set_popup_parent(None);
541            }
542        }
543
544        self.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::closed_event(), self.to_ref()));
545
546        for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
547            handler(None);
548        }
549
550        // Not in the reference, where the popup keeps the menu as its
551        // child: the menu owns the popup, so the popup holds the menu only
552        // while it is open (a menu and its popup must not keep each other
553        // alive). A handler above may have opened the menu again.
554        if let Some(popup) = popup {
555            if !popup.is_open() && !self.is_open() {
556                popup.set_child(None::<Ref<Control>>);
557            }
558        }
559    }
560
561    fn popup_key_up(&self, e: &KeyEventArgs) {
562        if self.is_open() {
563            let keymap = Application::current()
564                .and_then(|application| application.platform_settings())
565                .map(|settings| settings.hotkey_configuration());
566
567            if keymap.is_some_and(|keymap| keymap.open_context_menu.iter().any(|k| k.matches(Some(e))))
568                && !self.cancel_closing()
569            {
570                self.close();
571                e.set_handled(true);
572            }
573        }
574    }
575
576    fn control_context_canceled(sender: &Interactive, e: &RoutedEventArgs) {
577        if e.handled() {
578            return;
579        }
580        let Some(control) = sender.downcast_ref::<Control>() else { return };
581        let Some(context_menu) = control.context_menu() else { return };
582
583        if context_menu.is_open() {
584            context_menu.close();
585        }
586    }
587
588    fn control_context_requested(sender: &Interactive, e: &ContextRequestedEventArgs) {
589        let Some(control) = sender.downcast_ref::<Control>() else { return };
590        let Some(context_menu) = control.context_menu() else { return };
591
592        if !e.handled() && !context_menu.cancel_opening() {
593            let requested_by_pointer = e.try_get_position(None).is_some();
594            let control = control.to_ref();
595            let placement_target =
596                e.source().and_then(|source| source.cast::<Control>()).unwrap_or_else(|| control.clone());
597            context_menu.open_core(
598                &control,
599                &placement_target,
600                if requested_by_pointer { context_menu.placement() } else { PlacementMode::Bottom },
601            );
602            e.set_handled(true);
603
604            if e.is_holding() {
605                control.perform_feedback(FeedbackAction::hold());
606            }
607        }
608    }
609
610    fn control_on_attached_to_visual_tree(sender: &Control, _e: &VisualTreeAttachmentEventArgs) {
611        Self::attach_control_to_context_menu(sender);
612    }
613
614    fn attach_control_to_context_menu(control: &Control) {
615        if let Some(context_menu) = control.context_menu() {
616            context_menu
617                .attached_controls
618                .borrow_mut()
619                .get_or_insert_with(Vec::new)
620                .push(control.to_ref().downgrade());
621        }
622    }
623
624    fn control_detached_from_visual_tree(control: &Control, _e: &VisualTreeAttachmentEventArgs) {
625        if let Some(context_menu) = control.context_menu() {
626            let control = control.to_ref();
627            let popup = context_menu.popup.borrow().clone();
628            if let Some(popup) = popup {
629                if popup.parent() == Some(control.clone().upcast::<StyledElement>()) {
630                    popup.set_parent(None);
631                }
632            }
633
634            context_menu.close();
635            context_menu.remove_attached_control(&control);
636        }
637    }
638
639    fn cancel_closing(&self) -> bool {
640        let event_args = CancelEventArgs::new();
641        for (_, handler) in self.closing.snapshot().iter() {
642            handler(&event_args);
643        }
644        event_args.cancel()
645    }
646
647    fn cancel_opening(&self) -> bool {
648        let event_args = CancelEventArgs::new();
649        for (_, handler) in self.opening.snapshot().iter() {
650            handler(&event_args);
651        }
652        event_args.cancel()
653    }
654}
655
656/// The popup host provider contract of a context menu.
657struct ContextMenuHostProvider(Ref<ContextMenu>);
658
659impl IPopupHostProvider for ContextMenuHostProvider {
660    fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
661        self.0.popup_host()
662    }
663
664    fn popup_host_changed(&self, handler: Rc<dyn Fn(Option<&Rc<dyn IPopupHost>>)>) -> Rc<dyn IDisposable> {
665        self.0.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|menu| &menu.popup_host_changed_handler, handler)
666    }
667}