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, VisualImplExt, VisualTreeAttachmentEventArgs, WeakRef,
18};
19use std::cell::RefCell;
20use std::rc::Rc;
21
22#[repr(C)]
24pub struct ContextMenu {
25 base: MenuBase,
26 popup: RefCell<Option<Ref<Popup>>>,
27 attached_controls: RefCell<Option<Vec<WeakRef<Control>>>>,
31 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 element_backdrop: crate::media::ElementSystemBackdrop,
39}
40
41ferro_class!(ContextMenu: MenuBase);
42ferro_class_info!(ContextMenu { new: ContextMenu::new });
43
44ferro_impl_classes!(
45 ContextMenu: StyledElementImpl,
46 LayoutableImpl,
47 InteractiveImpl,
48 InputElementImpl,
49 ControlImpl,
50 TemplatedControlImpl,
51 ItemsControlImpl,
52 SelectingItemsControlImpl
53);
54
55impl FerroObjectImpl for ContextMenu {
56 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
57 Self::parent_on_property_changed(this, change);
58
59 if change.property() == Self::window_manager_add_shadow_hint_property().as_property() {
60 let popup = this.popup.borrow().clone();
61 if let Some(popup) = popup {
62 popup.set_window_manager_add_shadow_hint(change.get_new_value::<bool>());
63 }
64 }
65 if change.property() == Self::system_backdrop_property().as_property() && this.is_attached_to_visual_tree() {
66 this.element_backdrop.connect(this, this.system_backdrop());
67 }
68 }
69}
70
71impl VisualImpl for ContextMenu {
72 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
73 Self::parent_on_attached_to_visual_tree(this, e);
74 this.element_backdrop.connect(this, this.system_backdrop());
75 }
76
77 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
78 Self::parent_on_detached_from_visual_tree(this, e);
79 this.element_backdrop.disconnect();
80 }
81}
82
83impl MenuBaseImpl for ContextMenu {
84 fn open(this: &Self) {
86 this.open_at(None);
87 }
88
89 fn close(this: &Self) {
91 if !this.is_open() {
92 return;
93 }
94
95 let popup = this.popup.borrow().clone();
96 if let Some(popup) = popup {
97 if popup.is_visible() {
98 popup.set_is_open(false);
99 }
100 }
101 }
102}
103
104impl ISetterValue for ContextMenu {
105 fn initialize(&self, setter: &Setter) {
106 if setter.property() != Some(Control::context_menu_property().as_property()) {
109 panic!("Cannot use a control as a Setter value. Wrap the control in a <Template>.");
110 }
111 }
112}
113
114ferro_properties! {
115 impl ContextMenu {
116 pub fn horizontal_offset_property() -> StyledProperty<f64> {
118 Popup::horizontal_offset_property().add_owner::<ContextMenu>()
119 }
120
121 pub fn vertical_offset_property() -> StyledProperty<f64> {
123 Popup::vertical_offset_property().add_owner::<ContextMenu>()
124 }
125
126 pub fn placement_anchor_property() -> StyledProperty<PopupAnchor> {
128 Popup::placement_anchor_property().add_owner::<ContextMenu>()
129 }
130
131 pub fn placement_constraint_adjustment_property() -> StyledProperty<PopupPositionerConstraintAdjustment> {
133 Popup::placement_constraint_adjustment_property().add_owner::<ContextMenu>()
134 }
135
136 pub fn placement_gravity_property() -> StyledProperty<PopupGravity> {
138 Popup::placement_gravity_property().add_owner::<ContextMenu>()
139 }
140
141 pub fn placement_property() -> StyledProperty<PlacementMode> {
143 Popup::placement_property().add_owner::<ContextMenu>()
144 }
145
146 pub fn placement_rect_property() -> StyledProperty<Option<Rect>> {
148 Popup::placement_rect_property().add_owner::<ContextMenu>()
149 }
150
151 pub fn window_manager_add_shadow_hint_property() -> StyledProperty<bool> {
153 Popup::window_manager_add_shadow_hint_property().add_owner::<ContextMenu>()
154 }
155
156 pub fn placement_target_property() -> StyledProperty<Option<ElementRef<Control>>> {
161 Popup::placement_target_property().add_owner::<ContextMenu>()
162 }
163
164 pub fn custom_popup_placement_callback_property() -> StyledProperty<Option<CustomPopupPlacementCallbackValue>> {
166 Popup::custom_popup_placement_callback_property().add_owner::<ContextMenu>()
167 }
168
169 pub fn system_backdrop_property() -> StyledProperty<Option<Ref<crate::media::SystemBackdrop>>> {
173 crate::flyouts::MenuFlyoutPresenter::system_backdrop_property().add_owner::<ContextMenu>()
174 }
175 }
176}
177
178impl ContextMenu {
179 fn static_constructor() {
181 Self::placement_property().override_default_value::<ContextMenu>(PlacementMode::Pointer);
182 let _ = Control::context_menu_property().changed().subscribe(Self::context_menu_changed);
183 crate::automation::AutomationProperties::accessibility_view_property()
184 .override_default_value::<ContextMenu>(crate::automation::AccessibilityView::Control);
185 crate::automation::AutomationProperties::control_type_override_property()
186 .override_default_value::<ContextMenu>(Some(crate::automation::peers::AutomationControlType::Menu));
187 }
188
189 pub fn construct() -> Self {
191 Self::construct_with(DefaultMenuInteractionHandler::new(true))
192 }
193
194 pub fn construct_with(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Self {
197 Self {
198 base: MenuBase::construct_with(interaction_handler),
199 popup: RefCell::new(None),
200 attached_controls: RefCell::new(None),
201 previous_focus: RefCell::new(None),
202 popup_host_changed_handler: HandlerList::new(),
203 opening: HandlerList::new(),
204 closing: HandlerList::new(),
205 element_backdrop: Default::default(),
206 }
207 }
208
209 pub fn new() -> Ref<Self> {
211 instantiate(Self::construct())
212 }
213
214 pub fn with_interaction_handler(interaction_handler: Rc<dyn IMenuInteractionHandler>) -> Ref<Self> {
217 instantiate(Self::construct_with(interaction_handler))
218 }
219
220 pub fn system_backdrop(&self) -> Option<Ref<crate::media::SystemBackdrop>> {
222 self.get_value(Self::system_backdrop_property())
223 }
224
225 pub fn set_system_backdrop(&self, value: Option<Ref<crate::media::SystemBackdrop>>) {
226 self.set_value(Self::system_backdrop_property(), value)
227 }
228
229 pub fn horizontal_offset(&self) -> f64 {
232 self.get_value(Self::horizontal_offset_property())
233 }
234
235 pub fn set_horizontal_offset(&self, value: f64) {
236 self.set_value(Self::horizontal_offset_property(), value)
237 }
238
239 pub fn vertical_offset(&self) -> f64 {
242 self.get_value(Self::vertical_offset_property())
243 }
244
245 pub fn set_vertical_offset(&self, value: f64) {
246 self.set_value(Self::vertical_offset_property(), value)
247 }
248
249 pub fn placement_anchor(&self) -> PopupAnchor {
251 self.get_value(Self::placement_anchor_property())
252 }
253
254 pub fn set_placement_anchor(&self, value: PopupAnchor) {
255 self.set_value(Self::placement_anchor_property(), value)
256 }
257
258 pub fn placement_constraint_adjustment(&self) -> PopupPositionerConstraintAdjustment {
260 self.get_value(Self::placement_constraint_adjustment_property())
261 }
262
263 pub fn set_placement_constraint_adjustment(&self, value: PopupPositionerConstraintAdjustment) {
264 self.set_value(Self::placement_constraint_adjustment_property(), value)
265 }
266
267 pub fn placement_gravity(&self) -> PopupGravity {
269 self.get_value(Self::placement_gravity_property())
270 }
271
272 pub fn set_placement_gravity(&self, value: PopupGravity) {
273 self.set_value(Self::placement_gravity_property(), value)
274 }
275
276 pub fn placement(&self) -> PlacementMode {
278 self.get_value(Self::placement_property())
279 }
280
281 pub fn set_placement(&self, value: PlacementMode) {
282 self.set_value(Self::placement_property(), value)
283 }
284
285 pub fn window_manager_add_shadow_hint(&self) -> bool {
287 self.get_value(Self::window_manager_add_shadow_hint_property())
288 }
289
290 pub fn set_window_manager_add_shadow_hint(&self, value: bool) {
291 self.set_value(Self::window_manager_add_shadow_hint_property(), value)
292 }
293
294 pub fn placement_rect(&self) -> Option<Rect> {
296 self.get_value(Self::placement_rect_property())
297 }
298
299 pub fn set_placement_rect(&self, value: Option<Rect>) {
300 self.set_value(Self::placement_rect_property(), value)
301 }
302
303 pub fn placement_target(&self) -> Option<Ref<Control>> {
305 ElementRef::resolve(&self.get_value(Self::placement_target_property()))
306 }
307
308 pub fn set_placement_target(&self, value: impl Into<Nullable<Control>>) {
309 self.set_value(Self::placement_target_property(), ElementRef::from_nullable(value.into().0))
310 }
311
312 pub fn custom_popup_placement_callback(&self) -> Option<CustomPopupPlacementCallback> {
314 self.get_value(Self::custom_popup_placement_callback_property()).map(|value| value.0)
315 }
316
317 pub fn set_custom_popup_placement_callback(&self, value: Option<CustomPopupPlacementCallback>) {
318 self.set_value(
319 Self::custom_popup_placement_callback_property(),
320 value.map(CustomPopupPlacementCallbackValue),
321 )
322 }
323
324 fn subscribe<F: ?Sized + 'static>(
325 &self,
326 select: fn(&ContextMenu) -> &HandlerList<F>,
327 handler: Rc<F>,
328 ) -> Rc<dyn IDisposable> {
329 let token = select(self).add(handler);
330 let weak = self.to_ref().downgrade();
331 Disposable::create(move || {
332 if let Some(this) = weak.upgrade() {
333 select(&this).remove(token);
334 }
335 })
336 }
337
338 pub fn opening(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
341 self.subscribe::<dyn Fn(&CancelEventArgs)>(|menu| &menu.opening, Rc::new(handler))
342 }
343
344 pub fn closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
347 self.subscribe::<dyn Fn(&CancelEventArgs)>(|menu| &menu.closing, Rc::new(handler))
348 }
349
350 fn attached_controls(&self) -> Option<Vec<Ref<Control>>> {
353 self.attached_controls.borrow().as_ref().map(|controls| controls.iter().filter_map(WeakRef::upgrade).collect())
354 }
355
356 fn remove_attached_control(&self, control: &Ref<Control>) {
359 if let Some(controls) = self.attached_controls.borrow_mut().as_mut() {
360 controls.retain(|c| c.upgrade().is_some());
362 if let Some(index) = controls.iter().position(|c| c.upgrade().as_ref() == Some(control)) {
363 controls.remove(index);
364 }
365 }
366 }
367
368 fn context_menu_changed(e: &FerroPropertyChangedEventArgs<'_>) {
370 let control = e.sender().downcast_ref::<Control>().expect("the sender is a control");
371 let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<ContextMenu>>>();
372
373 if let Some(old_menu) = old_value {
374 if let Some((context_requested, context_canceled, attached, detached)) =
375 control.context_menu_handlers().take()
376 {
377 control.remove_handler(InputElement::context_requested_event(), context_requested);
378 control.remove_handler(InputElement::context_canceled_event(), context_canceled);
379 attached.dispose();
380 detached.dispose();
381 }
382 old_menu.remove_attached_control(&control.to_ref());
383 let popup = old_menu.popup.borrow().clone();
384 if let Some(popup) = popup {
385 popup.set_parent(None);
386 }
387 }
388
389 if new_value.is_some() {
390 let context_requested =
391 control.add_handler(InputElement::context_requested_event(), Self::control_context_requested);
392 let context_canceled =
393 control.add_handler(InputElement::context_canceled_event(), Self::control_context_canceled);
394 let weak = control.to_ref().downgrade();
395 let attached = control.attached_to_visual_tree({
396 let weak = weak.clone();
397 move |e| {
398 if let Some(control) = weak.upgrade() {
399 Self::control_on_attached_to_visual_tree(&control, e);
400 }
401 }
402 });
403 let detached = control.detached_from_visual_tree(move |e| {
404 if let Some(control) = weak.upgrade() {
405 Self::control_detached_from_visual_tree(&control, e);
406 }
407 });
408 control.context_menu_handlers().set(Some((context_requested, context_canceled, attached, detached)));
409 }
410
411 if control.is_attached_to_visual_tree() {
412 Self::attach_control_to_context_menu(control);
413 }
414 }
415
416 pub fn open_at(&self, control: Option<&Ref<Control>>) {
424 let attached_controls = self.attached_controls();
425
426 if control.is_none() && attached_controls.as_ref().is_none_or(|controls| controls.is_empty()) {
427 panic!("Value cannot be null. (Parameter 'control')");
428 }
429
430 if let (Some(control), Some(attached_controls)) = (control, &attached_controls) {
431 if !attached_controls.contains(control) {
432 panic!(
433 "Cannot show ContentMenu on a different control to the one it is attached to. (Parameter 'control')"
434 );
435 }
436 }
437
438 let control = match control {
439 Some(control) => control.clone(),
440 None => attached_controls.expect("checked above")[0].clone(),
441 };
442 let placement_target = self.placement_target().unwrap_or_else(|| control.clone());
443 self.open_core(&control, &placement_target, self.placement(), None);
444 }
445
446 pub fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
448 let popup = self.popup.borrow().clone();
449 popup.and_then(|popup| popup.host())
450 }
451
452 pub fn popup_host_changed(
455 &self,
456 handler: impl Fn(Option<&Rc<dyn IPopupHost>>) + 'static,
457 ) -> Rc<dyn IDisposable> {
458 self.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|menu| &menu.popup_host_changed_handler, Rc::new(handler))
459 }
460
461 pub fn to_popup_host_provider(&self) -> Rc<dyn IPopupHostProvider> {
464 Rc::new(ContextMenuHostProvider(self.to_ref()))
465 }
466
467 fn open_core(
468 &self,
469 control: &Ref<Control>,
470 placement_target: &Ref<Control>,
471 placement: PlacementMode,
472 placement_rect: Option<Rect>,
473 ) {
474 if self.is_open() {
475 return;
476 }
477
478 let existing = self.popup.borrow().clone();
479 let popup = match existing {
480 Some(popup) => popup,
481 None => {
482 let popup = Popup::new();
483 popup.set_is_light_dismiss_enabled(true);
484 popup.set_overlay_dismiss_event_pass_through(true);
485 popup.set_takes_focus_from_native_control(Popup::get_takes_focus_from_native_control(self));
486
487 let weak = self.to_ref().downgrade();
490 let _ = popup.opened({
491 let weak = weak.clone();
492 move || {
493 if let Some(this) = weak.upgrade() {
494 this.popup_opened();
495 }
496 }
497 });
498 let _ = popup.closed({
499 let weak = weak.clone();
500 move || {
501 if let Some(this) = weak.upgrade() {
502 this.popup_closed();
503 }
504 }
505 });
506 let _ = popup.closing({
507 let weak = weak.clone();
508 move |e| {
509 if let Some(this) = weak.upgrade() {
510 this.popup_closing(e);
511 }
512 }
513 });
514 popup.add_handler(InputElement::key_up_event(), move |_, e: &KeyEventArgs| {
515 if let Some(this) = weak.upgrade() {
516 this.popup_key_up(e);
517 }
518 });
519
520 *self.popup.borrow_mut() = Some(popup.clone());
521 popup
522 }
523 };
524
525 popup.set_popup_parent(Some(control));
526
527 popup.set_placement(placement);
528
529 popup.set_child(self.to_ref());
534 popup.set_placement_target(placement_target);
535 popup.set_horizontal_offset(self.horizontal_offset());
536 popup.set_vertical_offset(self.vertical_offset());
537 popup.set_placement_anchor(self.placement_anchor());
538 popup.set_placement_constraint_adjustment(self.placement_constraint_adjustment());
539 popup.set_placement_gravity(self.placement_gravity());
540 popup.set_placement_rect(placement_rect.or_else(|| self.placement_rect()));
541 popup.set_custom_popup_placement_callback(self.custom_popup_placement_callback());
542 popup.set_window_manager_add_shadow_hint(self.window_manager_add_shadow_hint());
543 self.set_is_open(true);
544 popup.set_is_open(true);
545
546 self.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::opened_event(), self.to_ref()));
547 }
548
549 fn popup_opened(&self) {
550 let previous_focus = FocusManager::get_focus_manager(self).and_then(|manager| manager.get_focused_element());
551 *self.previous_focus.borrow_mut() = previous_focus.map(|element| element.downgrade());
552 self.focus();
553
554 let host = self.popup_host();
555 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
556 handler(host.as_ref());
557 }
558 }
559
560 fn popup_closing(&self, e: &CancelEventArgs) {
561 e.set_cancel(self.cancel_closing());
562 }
563
564 fn popup_closed(&self) {
565 let children = StyledElement::logical_children(self).to_vec();
566 for i in children {
567 if let Some(menu_item) = i.cast::<MenuItem>() {
568 menu_item.set_is_sub_menu_open(false);
569 }
570 }
571
572 self.set_selected_index(-1);
573 self.set_is_open(false);
574
575 let popup = self.popup.borrow().clone();
576
577 if self.attached_controls().is_none_or(|controls| controls.is_empty()) {
578 if let Some(popup) = &popup {
579 popup.set_popup_parent(None);
580 }
581 }
582
583 self.raise_event(&RoutedEventArgs::with_event_and_source(MenuBase::closed_event(), self.to_ref()));
584
585 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
586 handler(None);
587 }
588
589 if let Some(popup) = popup {
594 if !popup.is_open() && !self.is_open() {
595 popup.set_child(None::<Ref<Control>>);
596 }
597 }
598 }
599
600 fn popup_key_up(&self, e: &KeyEventArgs) {
601 if self.is_open() {
602 let keymap = Application::current()
603 .and_then(|application| application.platform_settings())
604 .map(|settings| settings.hotkey_configuration());
605
606 if keymap.is_some_and(|keymap| keymap.open_context_menu.iter().any(|k| k.matches(Some(e))))
607 && !self.cancel_closing()
608 {
609 self.close();
610 e.set_handled(true);
611 }
612 }
613 }
614
615 fn control_context_canceled(sender: &Interactive, e: &RoutedEventArgs) {
616 if e.handled() {
617 return;
618 }
619 let Some(control) = sender.downcast_ref::<Control>() else { return };
620 let Some(context_menu) = control.context_menu() else { return };
621
622 if context_menu.is_open() {
623 context_menu.close();
624 }
625 }
626
627 pub(crate) fn control_context_requested(sender: &Interactive, e: &ContextRequestedEventArgs) {
628 if e.handled() {
629 return;
630 }
631 let Some(control) = sender.downcast_ref::<Control>() else { return };
632 let Some(context_menu) = control.context_menu() else { return };
633 let root = control.visual_root();
634 let request_generation = control.context_request_generation();
635 let is_current_request = || {
636 root == control.visual_root()
637 && control.context_request_generation() == request_generation
638 && control.context_menu() == Some(context_menu.clone())
639 };
640
641 if !context_menu.cancel_opening() {
642 if e.handled() || !is_current_request() {
643 e.set_handled(true);
644 return;
645 }
646 let requested_by_pointer = e.try_get_position(None).is_some();
647 let control = control.to_ref();
648 let placement_target =
649 e.source().and_then(|source| source.cast::<Control>()).unwrap_or_else(|| control.clone());
650 let placement_root = placement_target.visual_root();
651 let placement_rect = e.try_get_placement_rect(&placement_target);
652 if e.handled()
656 || !is_current_request()
657 || placement_root != placement_target.visual_root()
658 {
659 e.set_handled(true);
660 return;
661 }
662 context_menu.open_core(
663 &control,
664 &placement_target,
665 if placement_rect.is_some() {
666 PlacementMode::BottomEdgeAlignedLeft
668 } else if requested_by_pointer {
669 context_menu.placement()
670 } else {
671 PlacementMode::Bottom
672 },
673 placement_rect,
674 );
675 e.set_handled(true);
676
677 if e.is_holding() {
678 control.perform_feedback(FeedbackAction::hold());
679 }
680 }
681 }
682
683 fn control_on_attached_to_visual_tree(sender: &Control, _e: &VisualTreeAttachmentEventArgs) {
684 Self::attach_control_to_context_menu(sender);
685 }
686
687 fn attach_control_to_context_menu(control: &Control) {
688 if let Some(context_menu) = control.context_menu() {
689 context_menu
690 .attached_controls
691 .borrow_mut()
692 .get_or_insert_with(Vec::new)
693 .push(control.to_ref().downgrade());
694 }
695 }
696
697 fn control_detached_from_visual_tree(control: &Control, _e: &VisualTreeAttachmentEventArgs) {
698 if let Some(context_menu) = control.context_menu() {
699 let control = control.to_ref();
700 let popup = context_menu.popup.borrow().clone();
701 if let Some(popup) = popup {
702 if popup.parent() == Some(control.clone().upcast::<StyledElement>()) {
703 popup.set_parent(None);
704 }
705 }
706
707 context_menu.close();
708 context_menu.remove_attached_control(&control);
709 }
710 }
711
712 fn cancel_closing(&self) -> bool {
713 let event_args = CancelEventArgs::new();
714 for (_, handler) in self.closing.snapshot().iter() {
715 handler(&event_args);
716 }
717 event_args.cancel()
718 }
719
720 fn cancel_opening(&self) -> bool {
721 let event_args = CancelEventArgs::new();
722 for (_, handler) in self.opening.snapshot().iter() {
723 handler(&event_args);
724 }
725 event_args.cancel()
726 }
727}
728
729struct ContextMenuHostProvider(Ref<ContextMenu>);
731
732impl IPopupHostProvider for ContextMenuHostProvider {
733 fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
734 self.0.popup_host()
735 }
736
737 fn popup_host_changed(&self, handler: Rc<dyn Fn(Option<&Rc<dyn IPopupHost>>)>) -> Rc<dyn IDisposable> {
738 self.0.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|menu| &menu.popup_host_changed_handler, handler)
739 }
740}