1use super::{FlyoutBase, FlyoutBaseImpl, FlyoutShowMode};
2use crate::diagnostics::IPopupHostProvider;
3use crate::platform::{FeedbackAction, PlatformFeedbackExtensions};
4use crate::primitives::popup_positioning::{
5 CustomPopupPlacementCallback, PopupAnchor, PopupGravity, PopupPositionerConstraintAdjustment,
6};
7use crate::primitives::{CustomPopupPlacementCallbackValue, IPopupHost, Popup};
8use crate::{Application, Control, PlacementMode};
9use ferroui_base::controls::Classes;
10use ferroui_base::input::raw::{IRawInputEventArgs, RawPointerEventArgs, RawPointerEventType};
11use ferroui_base::input::{
12 ContextRequestedEventArgs, InputElement, InputManager, KeyEventArgs, KeyboardNavigationHandler,
13 NavigationDirection,
14};
15use ferroui_base::interactivity::{Interactive, RoutedEventArgs, RoutedEventHandlerToken};
16use ferroui_base::layout::{LayoutHelper, Layoutable};
17use ferroui_base::logging::{LogEventLevel, Logger};
18use ferroui_base::reactive::{AnonymousObserver, Disposable, IDisposable};
19use ferroui_base::utilities::{CancelEventArgs, HandlerList};
20use ferroui_base::{
21 ferro_class, ferro_property, FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs,
22 Nullable, PixelRect, Rect, Ref, Size, StyledProperty, Thickness, VisualTreeAttachmentEventArgs, WeakRef,
23};
24use std::cell::{Cell, OnceCell, RefCell};
25use std::rc::Rc;
26
27type TargetHandlers = (Rc<dyn IDisposable>, RoutedEventHandlerToken);
30
31#[repr(C)]
35pub struct PopupFlyoutBase {
36 base: FlyoutBase,
37 popup_lazy: OnceCell<Ref<Popup>>,
38 enlarged_popup_rect: Cell<Option<Rect>>,
39 enlarge_popup_rect_screen_pixel_rect: Cell<Option<PixelRect>>,
40 transient_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
41 popup_host_changed_handler: HandlerList<dyn Fn(Option<&Rc<dyn IPopupHost>>)>,
42 is_open: Cell<bool>,
43 ignore_is_open_changed: Cell<bool>,
44 last_placement_target: RefCell<Option<WeakRef<Control>>>,
47 target_handlers: RefCell<Option<TargetHandlers>>,
48 closing: HandlerList<dyn Fn(&CancelEventArgs)>,
49 opening: HandlerList<dyn Fn(&CancelEventArgs)>,
50}
51
52ferro_class! {
53 PopupFlyoutBase: FlyoutBase, virtuals PopupFlyoutBaseImpl: FlyoutBaseImpl {
54 fn hide_core(this, can_cancel: bool) -> bool;
57 fn show_at_core(this, placement_target: &Control, show_at_pointer: bool) -> bool;
60 fn on_opening(this, args: &CancelEventArgs);
62 fn on_closing(this, args: &CancelEventArgs);
64 fn create_presenter(this) -> Ref<Control>;
66 }
67}
68
69ferroui_base::ferro_impl_classes!(PopupFlyoutBase: FerroObjectImpl);
70
71impl FlyoutBaseImpl for PopupFlyoutBase {
72 fn show_at(this: &Self, placement_target: &Control) {
75 this.show_at_core(placement_target, false);
76 }
77
78 fn hide(this: &Self) {
81 this.hide_core(true);
82 }
83}
84
85impl PopupFlyoutBaseImpl for PopupFlyoutBase {
86 fn hide_core(this: &Self, can_cancel: bool) -> bool {
87 if !this.is_open.get() {
88 return false;
89 }
90
91 if can_cancel && this.cancel_closing() {
92 return false;
93 }
94
95 this.is_open.set(false);
96 {
97 let _ignore = this.begin_ignoring_is_open();
98 this.set_current_value(FlyoutBase::is_open_property(), false);
99 }
100 let popup = this.popup();
101 popup.set_is_open(false);
102
103 popup.set_placement_target(None);
104 popup.set_popup_parent(None);
105
106 let transient_disposable = this.transient_disposable.take();
108 if let Some(transient_disposable) = transient_disposable {
109 transient_disposable.dispose();
110 }
111 this.enlarged_popup_rect.set(None);
112 this.enlarge_popup_rect_screen_pixel_rect.set(None);
113
114 let target_handlers = this.target_handlers.take();
115 if let (Some(target), Some((detached, key_up))) = (this.target(), target_handlers) {
116 detached.dispose();
117 target.remove_handler(InputElement::key_up_event(), key_up);
118 }
119
120 this.on_closed();
121
122 this.set_target(None);
123
124 true
125 }
126
127 fn show_at_core(this: &Self, placement_target: &Control, show_at_pointer: bool) -> bool {
128 let placement_target = placement_target.to_ref();
129
130 *this.last_placement_target.borrow_mut() = Some(placement_target.downgrade());
131
132 if this.is_open.get() {
133 if this.target().as_ref() == Some(&placement_target) {
134 return false;
135 } else {
136 let _ = this.hide_core(false);
138 }
139 }
140
141 let popup = this.popup();
142 this.set_target(&placement_target);
143 popup.set_placement_target(&placement_target);
144 popup.set_popup_parent(Some(&placement_target));
145
146 if popup.child().is_none() {
147 popup.set_child(this.create_presenter());
148 }
149
150 popup.set_overlay_dismiss_event_pass_through(this.overlay_dismiss_event_pass_through());
151 popup.set_overlay_input_pass_through_element(this.overlay_input_pass_through_element());
152
153 if this.cancel_opening() {
154 return false;
155 }
156
157 this.position_popup(show_at_pointer);
158 this.is_open.set(true);
159 {
160 let _ignore = this.begin_ignoring_is_open();
161 this.set_current_value(FlyoutBase::is_open_property(), true);
162 }
163 popup.set_is_open(true);
164 this.on_opened();
165
166 let weak = this.to_ref().downgrade();
167 let detached = placement_target.detached_from_visual_tree({
168 let weak = weak.clone();
169 move |e| {
170 if let Some(this) = weak.upgrade() {
171 this.placement_target_detached_from_visual_tree(e);
172 }
173 }
174 });
175 let key_up = placement_target.add_handler(InputElement::key_up_event(), move |_, e: &KeyEventArgs| {
176 if let Some(this) = weak.upgrade() {
177 this.on_placement_target_or_popup_key_up(e);
178 }
179 });
180 *this.target_handlers.borrow_mut() = Some((detached, key_up));
181
182 match this.show_mode() {
183 FlyoutShowMode::Standard => {
184 if let Some(child) = popup.child() {
186 if child.focusable() {
187 child.focus();
188 } else {
189 let next_focus =
190 KeyboardNavigationHandler::get_next(&child.upcast(), NavigationDirection::Next);
191 if let Some(next_focus) = next_focus {
192 next_focus.focus();
193 }
194 }
195 }
196 }
197 FlyoutShowMode::TransientWithDismissOnPointerMoveAway => {
198 if let Some(input_manager) = InputManager::instance() {
199 let weak = this.to_ref().downgrade();
200 let subscription = input_manager.process().subscribe(Rc::new(AnonymousObserver::new(
201 move |e: Rc<dyn IRawInputEventArgs>| {
202 if let Some(this) = weak.upgrade() {
203 this.handle_transient_dismiss(&*e);
204 }
205 },
206 )));
207 *this.transient_disposable.borrow_mut() = Some(subscription);
208 }
209 }
210 FlyoutShowMode::Transient => {}
211 }
212
213 true
214 }
215
216 fn on_opening(this: &Self, args: &CancelEventArgs) {
217 for (_, handler) in this.opening.snapshot().iter() {
218 handler(args);
219 }
220 }
221
222 fn on_closing(this: &Self, args: &CancelEventArgs) {
223 for (_, handler) in this.closing.snapshot().iter() {
224 handler(args);
225 }
226 }
227
228 fn create_presenter(_this: &Self) -> Ref<Control> {
229 panic!("PopupFlyoutBase is abstract.")
230 }
231}
232
233struct IgnoreIsOpenScope<'a> {
235 owner: &'a PopupFlyoutBase,
236}
237
238impl Drop for IgnoreIsOpenScope<'_> {
239 fn drop(&mut self) {
240 self.owner.ignore_is_open_changed.set(false);
241 }
242}
243
244ferroui_base::ferro_properties! { impl PopupFlyoutBase {
245 ferro_property!(
246 pub fn placement_property() -> StyledProperty<PlacementMode> {
248 Popup::placement_property().add_owner::<PopupFlyoutBase>()
249 }
250 );
251
252 ferro_property!(
253 pub fn horizontal_offset_property() -> StyledProperty<f64> {
255 Popup::horizontal_offset_property().add_owner::<PopupFlyoutBase>()
256 }
257 );
258
259 ferro_property!(
260 pub fn vertical_offset_property() -> StyledProperty<f64> {
262 Popup::vertical_offset_property().add_owner::<PopupFlyoutBase>()
263 }
264 );
265
266 ferro_property!(
267 pub fn placement_anchor_property() -> StyledProperty<PopupAnchor> {
269 Popup::placement_anchor_property().add_owner::<PopupFlyoutBase>()
270 }
271 );
272
273 ferro_property!(
274 pub fn placement_gravity_property() -> StyledProperty<PopupGravity> {
276 Popup::placement_gravity_property().add_owner::<PopupFlyoutBase>()
277 }
278 );
279
280 ferro_property!(
281 pub fn custom_popup_placement_callback_property() -> StyledProperty<Option<CustomPopupPlacementCallbackValue>> {
283 Popup::custom_popup_placement_callback_property().add_owner::<PopupFlyoutBase>()
284 }
285 );
286
287 ferro_property!(
288 pub fn show_mode_property() -> StyledProperty<FlyoutShowMode> {
290 FerroProperty::register::<PopupFlyoutBase, _>("ShowMode", FlyoutShowMode::Standard)
291 }
292 );
293
294 ferro_property!(
295 pub fn overlay_dismiss_event_pass_through_property() -> StyledProperty<bool> {
297 Popup::overlay_dismiss_event_pass_through_property().add_owner::<PopupFlyoutBase>()
298 }
299 );
300
301 ferro_property!(
302 pub fn overlay_input_pass_through_element_property() -> StyledProperty<Option<ferroui_base::ElementRef<InputElement>>> {
304 Popup::overlay_input_pass_through_element_property().add_owner::<PopupFlyoutBase>()
305 }
306 );
307
308 ferro_property!(
309 pub fn placement_constraint_adjustment_property() -> StyledProperty<PopupPositionerConstraintAdjustment> {
311 Popup::placement_constraint_adjustment_property().add_owner::<PopupFlyoutBase>()
312 }
313 );
314} }
315
316impl PopupFlyoutBase {
317 fn static_constructor() {
318 FlyoutBase::is_open_property().changed().add_class_handler::<PopupFlyoutBase>(|x, e| x.is_open_changed(e));
319 Control::context_flyout_property()
320 .changed()
321 .add_class_handler::<Control>(Self::on_context_flyout_property_changed);
322 }
323
324 pub fn construct() -> Self {
326 Self {
327 base: FlyoutBase::construct(),
328 popup_lazy: OnceCell::new(),
329 enlarged_popup_rect: Cell::new(None),
330 enlarge_popup_rect_screen_pixel_rect: Cell::new(None),
331 transient_disposable: RefCell::new(None),
332 popup_host_changed_handler: HandlerList::new(),
333 is_open: Cell::new(false),
334 ignore_is_open_changed: Cell::new(false),
335 last_placement_target: RefCell::new(None),
336 target_handlers: RefCell::new(None),
337 closing: HandlerList::new(),
338 opening: HandlerList::new(),
339 }
340 }
341
342 pub fn popup(&self) -> Ref<Popup> {
345 self.popup_lazy.get_or_init(|| self.create_popup()).clone()
346 }
347
348 pub fn placement(&self) -> PlacementMode {
350 self.get_value(Self::placement_property())
351 }
352
353 pub fn set_placement(&self, value: PlacementMode) {
354 self.set_value(Self::placement_property(), value)
355 }
356
357 pub fn placement_gravity(&self) -> PopupGravity {
360 self.get_value(Self::placement_gravity_property())
361 }
362
363 pub fn set_placement_gravity(&self, value: PopupGravity) {
364 self.set_value(Self::placement_gravity_property(), value)
365 }
366
367 pub fn placement_anchor(&self) -> PopupAnchor {
370 self.get_value(Self::placement_anchor_property())
371 }
372
373 pub fn set_placement_anchor(&self, value: PopupAnchor) {
374 self.set_value(Self::placement_anchor_property(), value)
375 }
376
377 pub fn horizontal_offset(&self) -> f64 {
379 self.get_value(Self::horizontal_offset_property())
380 }
381
382 pub fn set_horizontal_offset(&self, value: f64) {
383 self.set_value(Self::horizontal_offset_property(), value)
384 }
385
386 pub fn vertical_offset(&self) -> f64 {
388 self.get_value(Self::vertical_offset_property())
389 }
390
391 pub fn set_vertical_offset(&self, value: f64) {
392 self.set_value(Self::vertical_offset_property(), value)
393 }
394
395 pub fn custom_popup_placement_callback(&self) -> Option<CustomPopupPlacementCallback> {
398 self.get_value(Self::custom_popup_placement_callback_property()).map(|value| value.0)
399 }
400
401 pub fn set_custom_popup_placement_callback(&self, value: Option<CustomPopupPlacementCallback>) {
402 self.set_value(
403 Self::custom_popup_placement_callback_property(),
404 value.map(CustomPopupPlacementCallbackValue),
405 )
406 }
407
408 pub fn show_mode(&self) -> FlyoutShowMode {
410 self.get_value(Self::show_mode_property())
411 }
412
413 pub fn set_show_mode(&self, value: FlyoutShowMode) {
414 self.set_value(Self::show_mode_property(), value)
415 }
416
417 pub fn overlay_dismiss_event_pass_through(&self) -> bool {
425 self.get_value(Self::overlay_dismiss_event_pass_through_property())
426 }
427
428 pub fn set_overlay_dismiss_event_pass_through(&self, value: bool) {
429 self.set_value(Self::overlay_dismiss_event_pass_through_property(), value)
430 }
431
432 pub fn overlay_input_pass_through_element(&self) -> Option<Ref<InputElement>> {
435 ferroui_base::ElementRef::resolve(&self.get_value(Self::overlay_input_pass_through_element_property()))
436 }
437
438 pub fn set_overlay_input_pass_through_element(&self, value: impl Into<Nullable<InputElement>>) {
439 self.set_value(
440 Self::overlay_input_pass_through_element_property(),
441 ferroui_base::ElementRef::from_nullable(value.into().0),
442 )
443 }
444
445 pub fn placement_constraint_adjustment(&self) -> PopupPositionerConstraintAdjustment {
448 self.get_value(Self::placement_constraint_adjustment_property())
449 }
450
451 pub fn set_placement_constraint_adjustment(&self, value: PopupPositionerConstraintAdjustment) {
452 self.set_value(Self::placement_constraint_adjustment_property(), value)
453 }
454
455 pub fn to_popup_host_provider(&self) -> Rc<dyn IPopupHostProvider> {
458 Rc::new(PopupFlyoutHostProvider(self.to_ref()))
459 }
460
461 fn subscribe<F: ?Sized + 'static>(
462 &self,
463 select: fn(&PopupFlyoutBase) -> &HandlerList<F>,
464 handler: Rc<F>,
465 ) -> Rc<dyn IDisposable> {
466 let token = select(self).add(handler);
467 let weak = self.to_ref().downgrade();
468 Disposable::create(move || {
469 if let Some(this) = weak.upgrade() {
470 select(&this).remove(token);
471 }
472 })
473 }
474
475 pub fn closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
478 self.subscribe::<dyn Fn(&CancelEventArgs)>(|flyout| &flyout.closing, Rc::new(handler))
479 }
480
481 pub fn opening(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
484 self.subscribe::<dyn Fn(&CancelEventArgs)>(|flyout| &flyout.opening, Rc::new(handler))
485 }
486
487 pub(crate) fn set_default_placement_target(&self, target: Option<&Control>) {
491 *self.last_placement_target.borrow_mut() = target.map(|target| target.to_ref().downgrade());
492 }
493
494 pub fn show_at_with(&self, placement_target: &Control, show_at_pointer: bool) {
497 self.show_at_core(placement_target, show_at_pointer);
498 }
499
500 fn placement_target_detached_from_visual_tree(&self, _e: &VisualTreeAttachmentEventArgs) {
501 let _ = self.hide_core(false);
502 *self.last_placement_target.borrow_mut() = None;
503 }
504
505 fn handle_transient_dismiss(&self, args: &dyn IRawInputEventArgs) {
506 let Some(p_args) = args.downcast_ref::<RawPointerEventArgs>() else { return };
507 if p_args.type_() != RawPointerEventType::Move {
508 return;
509 }
510
511 let host = self.popup().host();
518 let popup_root = host.as_ref().and_then(|host| host.as_popup_root());
519 let overlay_popup_host = host.as_ref().and_then(|host| host.as_overlay_popup_host());
520
521 if self.enlarged_popup_rect.get().is_none() && self.enlarge_popup_rect_screen_pixel_rect.get().is_none() {
522 if let Some(root) = popup_root {
524 let tmp = root.bounds().inflate(100.0);
526 self.enlarge_popup_rect_screen_pixel_rect.set(Some(PixelRect::from_points(
527 root.point_to_screen(tmp.top_left()),
528 root.point_to_screen(tmp.bottom_right()),
529 )));
530 } else if let Some(host) = overlay_popup_host {
531 self.enlarged_popup_rect.set(Some(host.bounds().inflate(100.0)));
533 }
534
535 return;
536 }
537
538 if popup_root.is_some() {
539 let event_root = p_args.root().root_element();
544 let pt = event_root.point_to_screen(p_args.position());
545 if self.enlarge_popup_rect_screen_pixel_rect.get().is_some_and(|rect| !rect.contains(pt)) {
546 self.hide_core(false);
547 }
548 } else if overlay_popup_host.is_some() {
549 if self.enlarged_popup_rect.get().is_some_and(|rect| !rect.contains(p_args.position())) {
552 self.hide_core(false);
553 }
554 }
555 }
556
557 fn create_popup(&self) -> Ref<Popup> {
558 let popup = Popup::new();
559 popup.set_window_manager_add_shadow_hint(false);
560 popup.set_is_light_dismiss_enabled(true);
561
562 let weak = self.to_ref().downgrade();
565 let _ = popup.opened({
566 let weak = weak.clone();
567 move || {
568 if let Some(this) = weak.upgrade() {
569 this.on_popup_opened();
570 }
571 }
572 });
573 let _ = popup.closed({
574 let weak = weak.clone();
575 move || {
576 if let Some(this) = weak.upgrade() {
577 this.on_popup_closed();
578 }
579 }
580 });
581 let _ = popup.closing({
582 let weak = weak.clone();
583 move |e| {
584 if let Some(this) = weak.upgrade() {
585 this.on_popup_closing(e);
586 }
587 }
588 });
589 popup.add_handler(InputElement::key_up_event(), move |_, e: &KeyEventArgs| {
590 if let Some(this) = weak.upgrade() {
591 this.on_placement_target_or_popup_key_up(e);
592 }
593 });
594 popup
595 }
596
597 fn on_popup_opened(&self) {
598 self.is_open.set(true);
599 {
600 let _ignore = self.begin_ignoring_is_open();
601 self.set_current_value(FlyoutBase::is_open_property(), true);
602 }
603
604 let host = self.popup().host();
605 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
606 handler(host.as_ref());
607 }
608 }
609
610 fn on_popup_closing(&self, e: &CancelEventArgs) {
611 if self.is_open.get() {
612 e.set_cancel(self.cancel_closing());
613 }
614 }
615
616 fn on_popup_closed(&self) {
617 self.hide_core(false);
618
619 for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
620 handler(None);
621 }
622 }
623
624 fn on_placement_target_or_popup_key_up(&self, e: &KeyEventArgs) {
626 if e.handled() || !self.is_open.get() {
627 return;
628 }
629
630 let this: Ref<FlyoutBase> = self.to_ref().upcast();
631 if self.target().and_then(|target| target.context_flyout()) != Some(this) {
632 return;
633 }
634
635 let keymap = Application::current()
636 .and_then(|application| application.platform_settings())
637 .map(|settings| settings.hotkey_configuration());
638
639 if keymap.is_some_and(|keymap| keymap.open_context_menu.iter().any(|k| k.matches(Some(e)))) {
640 e.set_handled(self.hide_core(true));
641 }
642 }
643
644 fn is_open_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
645 if self.ignore_is_open_changed.get() {
646 return;
647 }
648
649 if e.get_new_value::<bool>() {
650 let last_placement_target = self.last_placement_target.borrow().as_ref().and_then(WeakRef::upgrade);
651 if let Some(last_placement_target) = last_placement_target {
652 if self.show_at_core(&last_placement_target, false) {
653 return;
654 }
655 }
656
657 let _ignore = self.begin_ignoring_is_open();
659 self.set_current_value(FlyoutBase::is_open_property(), false);
660 } else if !self.hide_core(true) {
661 let _ignore = self.begin_ignoring_is_open();
663 self.set_current_value(FlyoutBase::is_open_property(), true);
664 }
665 }
666
667 fn begin_ignoring_is_open(&self) -> IgnoreIsOpenScope<'_> {
668 self.ignore_is_open_changed.set(true);
669 IgnoreIsOpenScope { owner: self }
670 }
671
672 fn position_popup(&self, show_at_pointer: bool) {
673 let popup = self.popup();
674
675 let child = popup.child().expect("the popup has a child");
678 if child.desired_size() == Size::default() {
679 let child: &Layoutable = &child;
681 LayoutHelper::measure_child(Some(child), Size::INFINITY, Thickness::default());
682 }
683
684 popup.set_vertical_offset(self.vertical_offset());
685 popup.set_horizontal_offset(self.horizontal_offset());
686 popup.set_placement_anchor(self.placement_anchor());
687 popup.set_placement_gravity(self.placement_gravity());
688 popup.set_custom_popup_placement_callback(self.custom_popup_placement_callback());
689 if show_at_pointer {
690 popup.set_placement(PlacementMode::Pointer);
691 } else {
692 popup.set_placement(self.placement());
693 popup.set_placement_constraint_adjustment(self.placement_constraint_adjustment());
694 }
695 }
696
697 fn on_context_flyout_property_changed(c: &Control, args: &FerroPropertyChangedEventArgs<'_>) {
698 let (old_value, new_value) = args.get_old_and_new_value::<Option<Ref<FlyoutBase>>>();
699
700 if old_value.is_some() {
701 if let Some((context_requested, context_canceled)) = c.context_flyout_handlers().take() {
702 c.remove_handler(InputElement::context_requested_event(), context_requested);
703 c.remove_handler(InputElement::context_canceled_event(), context_canceled);
704 }
705 }
706 if new_value.is_some() {
707 let context_requested =
708 c.add_handler(InputElement::context_requested_event(), Self::on_control_context_requested);
709 let context_canceled =
710 c.add_handler(InputElement::context_canceled_event(), Self::on_control_context_canceled);
711 c.context_flyout_handlers().set(Some((context_requested, context_canceled)));
712 }
713 }
714
715 fn on_control_context_canceled(sender: &Interactive, e: &RoutedEventArgs) {
716 if e.handled() {
717 return;
718 }
719 let Some(control) = sender.downcast_ref::<Control>() else { return };
720 let Some(flyout) = control.context_flyout() else { return };
721
722 if flyout.is_open() {
723 flyout.hide();
724 }
725 }
726
727 fn on_control_context_requested(sender: &Interactive, e: &ContextRequestedEventArgs) {
728 if e.handled() {
729 return;
730 }
731 let Some(control) = sender.downcast_ref::<Control>() else { return };
732 let Some(flyout) = control.context_flyout() else { return };
733
734 if control.context_menu().is_some() {
735 if let Some(logger) = Logger::try_get(LogEventLevel::Verbose, "FlyoutBase") {
736 let source: &dyn std::any::Any = control;
737 logger.log(Some(source), "ContextMenu and ContextFlyout are both set, defaulting to ContextMenu");
738 }
739 return;
740 }
741
742 if let Some(popup_flyout) = flyout.cast::<PopupFlyoutBase>() {
743 let triggered_by_pointer_input = e.try_get_position(None).is_some();
746 e.set_handled(popup_flyout.show_at_core(control, triggered_by_pointer_input));
747
748 if e.handled() && e.is_holding() {
749 control.perform_feedback(FeedbackAction::hold());
750 }
751 } else {
752 flyout.show_at(control);
753 e.set_handled(true);
754
755 if e.is_holding() {
756 control.perform_feedback(FeedbackAction::hold());
757 }
758 }
759 }
760
761 fn cancel_closing(&self) -> bool {
762 let event_args = CancelEventArgs::new();
763 self.on_closing(&event_args);
764 event_args.cancel()
765 }
766
767 fn cancel_opening(&self) -> bool {
768 let event_args = CancelEventArgs::new();
769 self.on_opening(&event_args);
770 event_args.cancel()
771 }
772
773 pub(crate) fn set_presenter_classes(presenter: Option<&Control>, classes: &Classes) {
776 let Some(presenter) = presenter else { return };
777
778 let presenter_classes = presenter.classes();
780 for i in (0..presenter_classes.count()).rev() {
781 let name = presenter_classes.get(i);
782 if !classes.contains(&name) && !name.contains(':') {
783 presenter_classes.remove_at(i);
784 }
785 }
786
787 presenter_classes.add_range(classes.snapshot().iter());
789 }
790}
791
792struct PopupFlyoutHostProvider(Ref<PopupFlyoutBase>);
794
795impl IPopupHostProvider for PopupFlyoutHostProvider {
796 fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
797 self.0.popup().host()
798 }
799
800 fn popup_host_changed(&self, handler: Rc<dyn Fn(Option<&Rc<dyn IPopupHost>>)>) -> Rc<dyn IDisposable> {
801 self.0.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|flyout| &flyout.popup_host_changed_handler, handler)
802 }
803}