Skip to main content

ferroui_controls/primitives/
popup.rs

1use super::popup_positioning::{
2    CustomPopupPlacementCallback, PopupAnchor, PopupGravity, PopupPositionRequest, PopupPositionerConstraintAdjustment,
3};
4use super::{
5    IPopupHost, LightDismissOverlayLayer, OverlayPopupHost, PopupRoot, TemplateAppliedEventArgs, TemplatedControl,
6};
7use crate::diagnostics::IPopupHostProvider;
8use crate::presenters::ContentPresenter;
9use ferroui_base::input::text_input::TransformTrackingHelper;
10use ferroui_base::data::core::ValueTypes;
11use ferroui_base::utilities::CancelEventArgs;
12use ferroui_base::ElementRef;
13use crate::{Control, ControlImpl, PixelPointEventArgs, PlacementMode, TopLevel, Window, WindowBase};
14use ferroui_base::data::BindingPriority;
15use ferroui_base::input::raw::{IRawInputEventArgs, RawPointerEventArgs, RawPointerEventType};
16use ferroui_base::input::{InputElement, InputElementImpl, InputManager, PointerPressedEventArgs};
17use ferroui_base::interactivity::InteractiveImpl;
18use ferroui_base::layout::{Layoutable, LayoutableImpl};
19use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
20use ferroui_base::media::{ScaleTransform, Transform};
21use ferroui_base::reactive::{AnonymousObserver, CompositeDisposable, Disposable, IDisposable, ObservableExt};
22use ferroui_base::styling::ThemeVariant;
23use ferroui_base::threading::{Dispatcher, DispatcherPriority};
24use ferroui_base::utilities::HandlerList;
25use ferroui_base::{
26    ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, DirectProperty,
27    FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs,
28    IFerroDependencyResolver, Matrix, Nullable, PixelPoint, Point, Rect, Ref, Size, StyledElement,
29    StyledElementImpl, StyledElementImplExt, StyledProperty, Visual, VisualImpl, VisualImplExt,
30    VisualTreeAttachmentEventArgs, WeakRef,
31};
32use std::cell::{Cell, RefCell};
33use std::rc::Rc;
34
35/// The value of the `CustomPopupPlacementCallback` property: a callback,
36/// compared by identity.
37#[derive(Clone)]
38pub struct CustomPopupPlacementCallbackValue(pub CustomPopupPlacementCallback);
39
40impl PartialEq for CustomPopupPlacementCallbackValue {
41    fn eq(&self, other: &Self) -> bool {
42        Rc::ptr_eq(&self.0, &other.0)
43    }
44}
45
46/// Displays a popup window.
47#[repr(C)]
48pub struct Popup {
49    base: Control,
50    dependency_resolver: RefCell<Option<Rc<dyn IFerroDependencyResolver>>>,
51    is_open_requested: Cell<bool>,
52    ignore_is_open_changed: Cell<bool>,
53    is_using_overlay_layer: Cell<bool>,
54    open_state: RefCell<Option<Rc<PopupOpenState>>>,
55    popup_host_changed_handler: HandlerList<dyn Fn(Option<&Rc<dyn IPopupHost>>)>,
56    opened_popups: RefCell<Vec<Ref<Popup>>>,
57    closed: HandlerList<dyn Fn()>,
58    opened: HandlerList<dyn Fn()>,
59    closing: HandlerList<dyn Fn(&CancelEventArgs)>,
60    /// Deviation (DEVIATIONS.md, Popup): the subscriptions to the render
61    /// transforms of the placement target and of its ancestors while the
62    /// popup is open (`subscribe_placement_target_transforms`).
63    placement_target_transform_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
64    queued_for_placement_target_move: Cell<bool>,
65    /// The rectangle of the placement target in its root when the popup
66    /// was last positioned for a change of a render transform.
67    last_placement_target_rect: Cell<Option<Rect>>,
68}
69
70ferro_class!(Popup: Control);
71ferroui_base::ferro_class_info!(Popup { new: Popup::new });
72ferro_impl_classes!(Popup: InteractiveImpl, InputElementImpl);
73
74impl ControlImpl for Popup {
75    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
76        crate::automation::peers::PopupAutomationPeer::new(this).upcast()
77    }
78}
79
80impl FerroObjectImpl for Popup {
81    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
82        Self::parent_on_property_changed(this, change);
83        let Some(open_state) = this.open_state() else { return };
84
85        let property = change.property();
86        if property == Layoutable::width_property().as_property()
87            || property == Layoutable::min_width_property().as_property()
88            || property == Layoutable::max_width_property().as_property()
89            || property == Layoutable::height_property().as_property()
90            || property == Layoutable::min_height_property().as_property()
91            || property == Layoutable::max_height_property().as_property()
92        {
93            this.update_host_sizing(&open_state.popup_host, open_state.top_level(), &open_state.placement_target());
94        } else if property == Self::placement_target_property().as_property()
95            || property == Self::placement_property().as_property()
96            || property == Self::horizontal_offset_property().as_property()
97            || property == Self::vertical_offset_property().as_property()
98            || property == Self::placement_anchor_property().as_property()
99            || property == Self::placement_constraint_adjustment_property().as_property()
100            || property == Self::placement_rect_property().as_property()
101        {
102            if property == Self::placement_target_property().as_property() {
103                let new_target = ElementRef::resolve(&change.get_new_value::<Option<ElementRef<Control>>>())
104                    .or_else(|| this.find_logical_ancestor_of_type::<Control>(false));
105
106                let Some(new_target) = new_target else {
107                    this.close();
108                    return;
109                };
110                if TopLevel::get_top_level(Some(&new_target)) != open_state.top_level() {
111                    this.close();
112                    return;
113                }
114
115                open_state.set_placement_target(new_target);
116                if this.placement() != PlacementMode::Pointer {
117                    this.subscribe_placement_target_transforms();
118                }
119            }
120
121            this.update_host_position(&open_state.popup_host, &open_state.placement_target());
122        } else if property == Self::topmost_property().as_property() {
123            open_state.popup_host.set_topmost(change.get_new_value::<bool>());
124        } else if property == InputElement::is_hit_test_visible_property().as_property() {
125            open_state.popup_host.set_is_hit_test_visible(change.get_new_value::<bool>());
126        }
127    }
128}
129
130impl StyledElementImpl for Popup {
131    fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
132        Self::parent_on_detached_from_logical_tree(this, e);
133        this.close();
134    }
135}
136
137impl VisualImpl for Popup {
138    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
139        Self::parent_on_attached_to_visual_tree(this, e);
140        if this.is_open_requested.get() {
141            this.open();
142        }
143    }
144}
145
146impl LayoutableImpl for Popup {
147    /// Measures the control: a size of 0,0 as the popup itself takes up no
148    /// space.
149    fn measure_core(_this: &Self, _available_size: Size) -> Size {
150        Size::default()
151    }
152}
153
154/// Resets the flag that makes the popup ignore changes of `IsOpen`.
155struct IgnoreIsOpenScope<'a> {
156    owner: &'a Popup,
157}
158
159impl Drop for IgnoreIsOpenScope<'_> {
160    fn drop(&mut self) {
161        self.owner.ignore_is_open_changed.set(false);
162    }
163}
164
165ferroui_base::ferro_properties! { impl Popup {
166    ferro_property!(
167        /// Defines the `WindowManagerAddShadowHint` property.
168        pub fn window_manager_add_shadow_hint_property() -> StyledProperty<bool> {
169            FerroProperty::register::<Popup, _>("WindowManagerAddShadowHint", false)
170        }
171    );
172
173    ferro_property!(
174        /// Defines the `Child` property.
175        pub fn child_property() -> StyledProperty<Option<Ref<Control>>> {
176            FerroProperty::register::<Popup, _>("Child", None)
177        }
178    );
179
180    ferro_property!(
181        /// Defines the `InheritsTransform` property.
182        pub fn inherits_transform_property() -> StyledProperty<bool> {
183            FerroProperty::register::<Popup, _>("InheritsTransform", false)
184        }
185    );
186
187    ferro_property!(
188        /// Defines the `IsOpen` property.
189        pub fn is_open_property() -> StyledProperty<bool> {
190            FerroProperty::register::<Popup, _>("IsOpen", false)
191        }
192    );
193
194    ferro_property!(
195        /// Defines the `PlacementAnchor` property.
196        pub fn placement_anchor_property() -> StyledProperty<PopupAnchor> {
197            FerroProperty::register::<Popup, _>("PlacementAnchor", PopupAnchor::NONE)
198        }
199    );
200
201    ferro_property!(
202        /// Defines the `PlacementConstraintAdjustment` property.
203        pub fn placement_constraint_adjustment_property() -> StyledProperty<PopupPositionerConstraintAdjustment> {
204            FerroProperty::register::<Popup, _>(
205                "PlacementConstraintAdjustment",
206                PopupPositionerConstraintAdjustment::FLIP_X
207                    | PopupPositionerConstraintAdjustment::FLIP_Y
208                    | PopupPositionerConstraintAdjustment::SLIDE_X
209                    | PopupPositionerConstraintAdjustment::SLIDE_Y
210                    | PopupPositionerConstraintAdjustment::RESIZE_X
211                    | PopupPositionerConstraintAdjustment::RESIZE_Y,
212            )
213        }
214    );
215
216    ferro_property!(
217        /// Defines the `PlacementGravity` property.
218        pub fn placement_gravity_property() -> StyledProperty<PopupGravity> {
219            FerroProperty::register::<Popup, _>("PlacementGravity", PopupGravity::NONE)
220        }
221    );
222
223    ferro_property!(
224        /// Defines the `Placement` property.
225        pub fn placement_property() -> StyledProperty<PlacementMode> {
226            FerroProperty::register::<Popup, _>("Placement", PlacementMode::Bottom)
227        }
228    );
229
230    ferro_property!(
231        /// Defines the `PlacementRect` property.
232        pub fn placement_rect_property() -> StyledProperty<Option<Rect>> {
233            FerroProperty::register::<Popup, _>("PlacementRect", None)
234        }
235    );
236
237    ferro_property!(
238        /// Defines the `PlacementTarget` property.
239        ///
240        /// The popup does not own its placement target (it is usually an
241        /// ancestor of the popup), so the value is an element reference.
242        pub fn placement_target_property() -> StyledProperty<Option<ElementRef<Control>>> {
243            ValueTypes::register_element_ref::<Control>();
244            FerroProperty::register::<Popup, _>("PlacementTarget", None)
245        }
246    );
247
248    ferro_property!(
249        /// Defines the `CustomPopupPlacementCallback` property.
250        pub fn custom_popup_placement_callback_property() -> StyledProperty<Option<CustomPopupPlacementCallbackValue>> {
251            FerroProperty::register::<Popup, _>("CustomPopupPlacementCallback", None)
252        }
253    );
254
255    ferro_property!(
256        /// Defines the `OverlayDismissEventPassThrough` property.
257        pub fn overlay_dismiss_event_pass_through_property() -> StyledProperty<bool> {
258            FerroProperty::register::<Popup, _>("OverlayDismissEventPassThrough", false)
259        }
260    );
261
262    ferro_property!(
263        /// Defines the `OverlayInputPassThroughElement` property.
264        pub fn overlay_input_pass_through_element_property() -> StyledProperty<Option<ElementRef<InputElement>>> {
265            ValueTypes::register_element_ref::<InputElement>();
266            FerroProperty::register::<Popup, _>("OverlayInputPassThroughElement", None)
267        }
268    );
269
270    ferro_property!(
271        /// Defines the `HorizontalOffset` property.
272        pub fn horizontal_offset_property() -> StyledProperty<f64> {
273            FerroProperty::register::<Popup, _>("HorizontalOffset", 0.0)
274        }
275    );
276
277    ferro_property!(
278        /// Defines the `IsLightDismissEnabled` property.
279        pub fn is_light_dismiss_enabled_property() -> StyledProperty<bool> {
280            FerroProperty::register::<Popup, _>("IsLightDismissEnabled", false)
281        }
282    );
283
284    ferro_property!(
285        /// Defines the `VerticalOffset` property.
286        pub fn vertical_offset_property() -> StyledProperty<f64> {
287            FerroProperty::register::<Popup, _>("VerticalOffset", 0.0)
288        }
289    );
290
291    ferro_property!(
292        /// Defines the `Topmost` property.
293        pub fn topmost_property() -> StyledProperty<bool> {
294            FerroProperty::register::<Popup, _>("Topmost", false)
295        }
296    );
297
298    ferro_property!(
299        /// Defines the `TakesFocusFromNativeControl` property.
300        pub fn takes_focus_from_native_control_property() -> AttachedProperty<bool> {
301            FerroProperty::register_attached::<Popup, Control, _>("TakesFocusFromNativeControl", true)
302        }
303    );
304
305    ferro_property!(
306        /// Defines the `ShouldUseOverlayLayer` property.
307        pub fn should_use_overlay_layer_property() -> StyledProperty<bool> {
308            FerroProperty::register::<Popup, _>("ShouldUseOverlayLayer", false)
309        }
310    );
311
312    ferro_property!(
313        /// Defines the `IsUsingOverlayLayer` property.
314        pub fn is_using_overlay_layer_property() -> DirectProperty<Popup, bool> {
315            FerroProperty::register_direct::<Popup, _>("IsUsingOverlayLayer", |o| o.is_using_overlay_layer(), None, false)
316        }
317    );
318} }
319
320impl Popup {
321    fn static_constructor() {
322        Self::child_property().changed().add_class_handler::<Popup>(|x, e| x.child_changed(e));
323        Self::is_open_property().changed().add_class_handler::<Popup>(|x, e| x.is_open_changed(e));
324    }
325
326    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
327    pub fn construct() -> Self {
328        Self {
329            base: Control::construct(),
330            dependency_resolver: RefCell::new(None),
331            is_open_requested: Cell::new(false),
332            ignore_is_open_changed: Cell::new(false),
333            is_using_overlay_layer: Cell::new(false),
334            open_state: RefCell::new(None),
335            popup_host_changed_handler: HandlerList::new(),
336            opened_popups: RefCell::new(Vec::new()),
337            closed: HandlerList::new(),
338            opened: HandlerList::new(),
339            closing: HandlerList::new(),
340            placement_target_transform_subscriptions: RefCell::new(Vec::new()),
341            queued_for_placement_target_move: Cell::new(false),
342            last_placement_target_rect: Cell::new(None),
343        }
344    }
345
346    pub fn new() -> Ref<Self> {
347        instantiate(Self::construct())
348    }
349
350    fn subscribe<F: ?Sized + 'static>(&self, select: fn(&Popup) -> &HandlerList<F>, handler: Rc<F>) -> Rc<dyn IDisposable> {
351        let token = select(self).add(handler);
352        let weak = self.to_ref().downgrade();
353        Disposable::create(move || {
354            if let Some(this) = weak.upgrade() {
355                select(&this).remove(token);
356            }
357        })
358    }
359
360    /// Raised when the popup closes. Disposing the returned handle
361    /// unsubscribes.
362    pub fn closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
363        self.subscribe::<dyn Fn()>(|popup| &popup.closed, Rc::new(handler))
364    }
365
366    /// Raised when the popup opens. Disposing the returned handle
367    /// unsubscribes.
368    pub fn opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
369        self.subscribe::<dyn Fn()>(|popup| &popup.opened, Rc::new(handler))
370    }
371
372    /// Raised before the popup closes; the close can be cancelled (internal
373    /// upstream). Disposing the returned handle unsubscribes.
374    pub fn closing(&self, handler: impl Fn(&CancelEventArgs) + 'static) -> Rc<dyn IDisposable> {
375        self.subscribe::<dyn Fn(&CancelEventArgs)>(|popup| &popup.closing, Rc::new(handler))
376    }
377
378    /// Raised when the popup host changes (the popup host provider
379    /// contract). Disposing the returned handle unsubscribes.
380    pub fn popup_host_changed(
381        &self,
382        handler: impl Fn(Option<&Rc<dyn IPopupHost>>) + 'static,
383    ) -> Rc<dyn IDisposable> {
384        self.subscribe::<dyn Fn(Option<&Rc<dyn IPopupHost>>)>(|popup| &popup.popup_host_changed_handler, Rc::new(handler))
385    }
386
387    fn open_state(&self) -> Option<Rc<PopupOpenState>> {
388        self.open_state.borrow().clone()
389    }
390
391    /// The host of the open popup (internal upstream).
392    pub fn host(&self) -> Option<Rc<dyn IPopupHost>> {
393        self.open_state().map(|open_state| open_state.popup_host.clone())
394    }
395
396    /// The popups that are currently open directly inside this popup, in
397    /// the order they were opened.
398    pub fn opened_popups(&self) -> Vec<Ref<Popup>> {
399        self.opened_popups.borrow().clone()
400    }
401
402    /// A hint to the window manager that a shadow should be added to the
403    /// popup.
404    pub fn window_manager_add_shadow_hint(&self) -> bool {
405        self.get_value(Self::window_manager_add_shadow_hint_property())
406    }
407
408    pub fn set_window_manager_add_shadow_hint(&self, value: bool) {
409        self.set_value(Self::window_manager_add_shadow_hint_property(), value)
410    }
411
412    /// The control to display in the popup.
413    pub fn child(&self) -> Option<Ref<Control>> {
414        self.get_value(Self::child_property())
415    }
416
417    pub fn set_child(&self, value: impl Into<Nullable<Control>>) {
418        self.set_value(Self::child_property(), value.into().0)
419    }
420
421    /// A dependency resolver for the [`PopupRoot`].
422    ///
423    /// This property allows a client to customize the behaviour of the
424    /// popup by injecting a specialized dependency resolver into the
425    /// [`PopupRoot`]'s constructor.
426    pub fn dependency_resolver(&self) -> Option<Rc<dyn IFerroDependencyResolver>> {
427        self.dependency_resolver.borrow().clone()
428    }
429
430    pub fn set_dependency_resolver(&self, value: Option<Rc<dyn IFerroDependencyResolver>>) {
431        *self.dependency_resolver.borrow_mut() = value;
432    }
433
434    /// Determines whether the popup inherits the render transform from its
435    /// placement target. Defaults to false.
436    pub fn inherits_transform(&self) -> bool {
437        self.get_value(Self::inherits_transform_property())
438    }
439
440    pub fn set_inherits_transform(&self, value: bool) {
441        self.set_value(Self::inherits_transform_property(), value)
442    }
443
444    /// Determines how the popup can be dismissed.
445    ///
446    /// Light dismiss is when the user taps on any area other than the
447    /// popup.
448    pub fn is_light_dismiss_enabled(&self) -> bool {
449        self.get_value(Self::is_light_dismiss_enabled_property())
450    }
451
452    pub fn set_is_light_dismiss_enabled(&self, value: bool) {
453        self.set_value(Self::is_light_dismiss_enabled_property(), value)
454    }
455
456    /// Whether the popup is currently open.
457    pub fn is_open(&self) -> bool {
458        self.get_value(Self::is_open_property())
459    }
460
461    pub fn set_is_open(&self, value: bool) {
462        self.set_value(Self::is_open_property(), value)
463    }
464
465    /// The anchor point on the placement rect when the placement is
466    /// [`PlacementMode::AnchorAndGravity`].
467    pub fn placement_anchor(&self) -> PopupAnchor {
468        self.get_value(Self::placement_anchor_property())
469    }
470
471    pub fn set_placement_anchor(&self, value: PopupAnchor) {
472        self.set_value(Self::placement_anchor_property(), value)
473    }
474
475    /// Describes how the popup position will be adjusted if the unadjusted
476    /// position would result in the popup being partly constrained.
477    pub fn placement_constraint_adjustment(&self) -> PopupPositionerConstraintAdjustment {
478        self.get_value(Self::placement_constraint_adjustment_property())
479    }
480
481    pub fn set_placement_constraint_adjustment(&self, value: PopupPositionerConstraintAdjustment) {
482        self.set_value(Self::placement_constraint_adjustment_property(), value)
483    }
484
485    /// Defines in what direction the popup should open when the placement
486    /// is [`PlacementMode::AnchorAndGravity`].
487    pub fn placement_gravity(&self) -> PopupGravity {
488        self.get_value(Self::placement_gravity_property())
489    }
490
491    pub fn set_placement_gravity(&self, value: PopupGravity) {
492        self.set_value(Self::placement_gravity_property(), value)
493    }
494
495    /// The desired placement of the popup in relation to the placement
496    /// target.
497    pub fn placement(&self) -> PlacementMode {
498        self.get_value(Self::placement_property())
499    }
500
501    pub fn set_placement(&self, value: PlacementMode) {
502        self.set_value(Self::placement_property(), value)
503    }
504
505    /// The anchor rectangle within the parent that the popup will be placed
506    /// relative to when the placement is
507    /// [`PlacementMode::AnchorAndGravity`].
508    ///
509    /// The placement rect defines a rectangle relative to the placement
510    /// target around which the popup will be opened, with the placement
511    /// anchor determining which edge of the placement target is used.
512    ///
513    /// If unset, the anchor rectangle will be the bounds of the placement
514    /// target.
515    pub fn placement_rect(&self) -> Option<Rect> {
516        self.get_value(Self::placement_rect_property())
517    }
518
519    pub fn set_placement_rect(&self, value: Option<Rect>) {
520        self.set_value(Self::placement_rect_property(), value)
521    }
522
523    /// The control that is used to determine the popup's position.
524    pub fn placement_target(&self) -> Option<Ref<Control>> {
525        ElementRef::resolve(&self.get_value(Self::placement_target_property()))
526    }
527
528    pub fn set_placement_target(&self, value: impl Into<Nullable<Control>>) {
529        self.set_value(Self::placement_target_property(), ElementRef::from_nullable(value.into().0))
530    }
531
532    /// The callback that positions the popup when the placement is
533    /// [`PlacementMode::Custom`].
534    pub fn custom_popup_placement_callback(&self) -> Option<CustomPopupPlacementCallback> {
535        self.get_value(Self::custom_popup_placement_callback_property()).map(|value| value.0)
536    }
537
538    pub fn set_custom_popup_placement_callback(&self, value: Option<CustomPopupPlacementCallback>) {
539        self.set_value(
540            Self::custom_popup_placement_callback_property(),
541            value.map(CustomPopupPlacementCallbackValue),
542        )
543    }
544
545    /// Whether the event that closes the popup is passed through to the
546    /// parent window.
547    ///
548    /// When light dismiss is enabled, clicks outside the popup cause the
549    /// popup to close. When this is false, these clicks will be handled by
550    /// the popup and not be registered by the parent window. When true, the
551    /// events will be passed through to the parent window.
552    pub fn overlay_dismiss_event_pass_through(&self) -> bool {
553        self.get_value(Self::overlay_dismiss_event_pass_through_property())
554    }
555
556    pub fn set_overlay_dismiss_event_pass_through(&self, value: bool) {
557        self.set_value(Self::overlay_dismiss_event_pass_through_property(), value)
558    }
559
560    /// An element that should receive pointer input events even when
561    /// underneath the popup's overlay.
562    pub fn overlay_input_pass_through_element(&self) -> Option<Ref<InputElement>> {
563        ElementRef::resolve(&self.get_value(Self::overlay_input_pass_through_element_property()))
564    }
565
566    pub fn set_overlay_input_pass_through_element(&self, value: impl Into<Nullable<InputElement>>) {
567        self.set_value(Self::overlay_input_pass_through_element_property(), ElementRef::from_nullable(value.into().0))
568    }
569
570    /// The horizontal offset of the popup in relation to the placement
571    /// target.
572    pub fn horizontal_offset(&self) -> f64 {
573        self.get_value(Self::horizontal_offset_property())
574    }
575
576    pub fn set_horizontal_offset(&self, value: f64) {
577        self.set_value(Self::horizontal_offset_property(), value)
578    }
579
580    /// The vertical offset of the popup in relation to the placement
581    /// target.
582    pub fn vertical_offset(&self) -> f64 {
583        self.get_value(Self::vertical_offset_property())
584    }
585
586    pub fn set_vertical_offset(&self, value: f64) {
587        self.set_value(Self::vertical_offset_property(), value)
588    }
589
590    /// Whether this popup appears on top of all other windows.
591    pub fn topmost(&self) -> bool {
592        self.get_value(Self::topmost_property())
593    }
594
595    pub fn set_topmost(&self, value: bool) {
596        self.set_value(Self::topmost_property(), value)
597    }
598
599    /// Whether the popup, on show, transfers focus from any focused native
600    /// control to the toolkit. The default is `true`.
601    ///
602    /// This property only applies to advanced native control embedding
603    /// scenarios. By default, if a popup is shown when a native control is
604    /// focused, focus is transferred back to the toolkit in order for the
605    /// popup to receive input. If this property is set to `false`, then the
606    /// shown popup will not receive input until it receives an interaction
607    /// which explicitly focuses the popup, such as a mouse click.
608    pub fn takes_focus_from_native_control(&self) -> bool {
609        self.get_value(Self::takes_focus_from_native_control_property())
610    }
611
612    pub fn set_takes_focus_from_native_control(&self, value: bool) {
613        self.set_value(Self::takes_focus_from_native_control_property(), value)
614    }
615
616    /// Whether the popup should be shown in the overlay layer of the parent
617    /// window.
618    ///
619    /// When this is false the implementation depends on the platform. Use
620    /// [`is_using_overlay_layer`](Self::is_using_overlay_layer) to get the
621    /// actual popup behavior. This is an equivalent of the overlay popups
622    /// platform option, but settable independently per each popup.
623    pub fn should_use_overlay_layer(&self) -> bool {
624        self.get_value(Self::should_use_overlay_layer_property())
625    }
626
627    pub fn set_should_use_overlay_layer(&self, value: bool) {
628        self.set_value(Self::should_use_overlay_layer_property(), value)
629    }
630
631    /// Whether the popup is shown in the overlay layer of the parent
632    /// window.
633    pub fn is_using_overlay_layer(&self) -> bool {
634        self.is_using_overlay_layer.get()
635    }
636
637    fn set_is_using_overlay_layer(&self, value: bool) {
638        self.set_and_raise_cell(Self::is_using_overlay_layer_property(), &self.is_using_overlay_layer, value);
639    }
640
641    /// Opens the popup.
642    pub fn open(&self) {
643        // Popup is currently open
644        if self.open_state.borrow().is_some() {
645            return;
646        }
647
648        let Some(placement_target) =
649            self.placement_target().or_else(|| self.find_logical_ancestor_of_type::<Control>(false))
650        else {
651            self.is_open_requested.set(true);
652            return;
653        };
654
655        let Some(top_level) = TopLevel::get_top_level(Some(&placement_target)) else {
656            self.is_open_requested.set(true);
657            return;
658        };
659
660        self.is_open_requested.set(false);
661
662        let this = self.to_ref();
663        let weak = this.downgrade();
664        let popup_host = OverlayPopupHost::create_popup_host(
665            &placement_target,
666            self.dependency_resolver(),
667            self.should_use_overlay_layer(),
668        );
669        let handler_cleanup = Rc::new(CompositeDisposable::with_capacity(7));
670
671        self.update_host_sizing(&popup_host, Some(top_level.clone()), &placement_target);
672        popup_host.set_topmost(self.topmost());
673        popup_host.set_is_hit_test_visible(self.is_hit_test_visible());
674        popup_host.set_child(self.child());
675        popup_host.as_control().set_parent(this.clone());
676
677        if self.inherits_transform() {
678            let weak = weak.clone();
679            handler_cleanup.add(TransformTrackingHelper::track(&placement_target.clone().upcast(), true, move |v, m| {
680                if let Some(this) = weak.upgrade() {
681                    this.placement_target_transform_changed(v, m);
682                }
683            }));
684        } else {
685            popup_host.set_transform(None);
686        }
687
688        if let Some(top_level_popup) = popup_host.as_popup_root() {
689            handler_cleanup.add(top_level_popup.bind_typed_value(
690                ThemeVariant::actual_theme_variant_property(),
691                self.get_binding_observable(ThemeVariant::actual_theme_variant_property()),
692                BindingPriority::LocalValue,
693            ));
694        }
695
696        self.update_host_position(&popup_host, &placement_target);
697
698        let template_applied_subscription = popup_host.template_applied(Rc::new({
699            let weak = weak.clone();
700            move |e| {
701                if let Some(this) = weak.upgrade() {
702                    this.root_template_applied(e);
703                }
704            }
705        }));
706        handler_cleanup.add(template_applied_subscription.clone());
707
708        handler_cleanup.add(placement_target.detached_from_visual_tree({
709            let weak = weak.clone();
710            move |e| {
711                if let Some(this) = weak.upgrade() {
712                    this.target_detached(e);
713                }
714            }
715        }));
716
717        self.subscribe_to_top_level(&top_level, &placement_target, &handler_cleanup);
718
719        // Deviation (DEVIATIONS.md, Popup): see
720        // `subscribe_placement_target_transforms`.
721        if self.placement() != PlacementMode::Pointer {
722            let weak = weak.clone();
723            handler_cleanup.add(Disposable::create(move || {
724                if let Some(this) = weak.upgrade() {
725                    this.unsubscribe_placement_target_transforms();
726                }
727            }));
728        }
729
730        if let Some(input_manager) = InputManager::instance() {
731            let weak = weak.clone();
732            handler_cleanup.add(input_manager.process().subscribe(Rc::new(AnonymousObserver::new(
733                move |e: Rc<dyn IRawInputEventArgs>| {
734                    if let Some(this) = weak.upgrade() {
735                        this.listen_for_non_client_click(&*e);
736                    }
737                },
738            ))));
739        }
740
741        let cleanup_popup = Disposable::create({
742            let popup_host = popup_host.clone();
743            let handler_cleanup = handler_cleanup.clone();
744            move || {
745                handler_cleanup.dispose();
746
747                popup_host.set_child(None);
748                popup_host.hide();
749
750                popup_host.as_control().set_parent(None);
751                popup_host.dispose();
752            }
753        });
754
755        if self.is_light_dismiss_enabled() {
756            let dismiss_layer = LightDismissOverlayLayer::get_light_dismiss_overlay_layer(&placement_target);
757
758            if let Some(dismiss_layer) = dismiss_layer {
759                handler_cleanup.add(dismiss_layer.register(self.overlay_input_pass_through_element().as_ref()));
760
761                let token = dismiss_layer.add_handler(InputElement::pointer_pressed_event(), {
762                    let weak = weak.clone();
763                    move |_, e: &PointerPressedEventArgs| {
764                        if let Some(this) = weak.upgrade() {
765                            this.pointer_pressed_dismiss_overlay(e);
766                        }
767                    }
768                });
769                let weak_layer = dismiss_layer.downgrade();
770                handler_cleanup.add(Disposable::create(move || {
771                    if let Some(dismiss_layer) = weak_layer.upgrade() {
772                        dismiss_layer.remove_handler(InputElement::pointer_pressed_event(), token);
773                    }
774                }));
775            }
776        }
777
778        let parent_popup = find_parent_popup(&placement_target);
779        let open_state = Rc::new(PopupOpenState::new(
780            placement_target,
781            &top_level,
782            popup_host.clone(),
783            cleanup_popup,
784            parent_popup.as_ref(),
785            template_applied_subscription,
786        ));
787        *self.open_state.borrow_mut() = Some(open_state);
788
789        if self.placement() != PlacementMode::Pointer {
790            self.subscribe_placement_target_transforms();
791        }
792
793        if let Some(parent_popup) = parent_popup {
794            parent_popup.add_opened_popup(&this);
795        } else {
796            top_level.add_opened_popup(&this);
797        }
798
799        window_manager_add_shadow_hint_changed(&popup_host, self.window_manager_add_shadow_hint());
800
801        popup_host.show();
802        self.set_is_using_overlay_layer(popup_host.as_overlay_popup_host().is_some());
803
804        if self.takes_focus_from_native_control() {
805            popup_host.take_focus();
806        }
807
808        {
809            let _ignore = self.begin_ignoring_is_open();
810            self.set_current_value(Self::is_open_property(), true);
811        }
812
813        for (_, handler) in self.opened.snapshot().iter() {
814            handler();
815        }
816
817        let host = self.host();
818        for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
819            handler(host.as_ref());
820        }
821    }
822
823    /// Subscribes to the notifications of the top-level of the placement
824    /// target that close or reposition the popup.
825    fn subscribe_to_top_level(
826        &self,
827        top_level: &Ref<TopLevel>,
828        placement_target: &Ref<Control>,
829        handler_cleanup: &CompositeDisposable,
830    ) {
831        let weak = self.to_ref().downgrade();
832
833        if let Some(window) = top_level.cast::<Window>() {
834            let window: &WindowBase = &window;
835            if window.platform_impl().is_some() {
836                handler_cleanup.add(window.deactivated({
837                    let weak = weak.clone();
838                    move || {
839                        if let Some(this) = weak.upgrade() {
840                            this.window_deactivated();
841                        }
842                    }
843                }));
844
845                handler_cleanup.add(top_level.platform_lost_focus({
846                    let weak = weak.clone();
847                    move || {
848                        if let Some(this) = weak.upgrade() {
849                            this.window_lost_focus();
850                        }
851                    }
852                }));
853
854                // Recalculate popup position on parent moved/resized, but not if placement was on pointer
855                if self.placement() != PlacementMode::Pointer {
856                    handler_cleanup.add(top_level.platform_position_changed({
857                        let weak = weak.clone();
858                        move |pp| {
859                            if let Some(this) = weak.upgrade() {
860                                this.window_position_changed(pp);
861                            }
862                        }
863                    }));
864
865                    // If the placement target is moved, update the popup position
866                    handler_cleanup.add(placement_target.layout_updated({
867                        let weak = weak.clone();
868                        move || {
869                            if let Some(this) = weak.upgrade() {
870                                this.placement_target_layout_updated();
871                            }
872                        }
873                    }));
874                }
875
876                return;
877            }
878        }
879
880        if let Some(parent_popup_root) = top_level.cast::<PopupRoot>() {
881            handler_cleanup.add(parent_popup_root.position_changed({
882                let weak = weak.clone();
883                move |e| {
884                    if let Some(this) = weak.upgrade() {
885                        this.parent_popup_position_changed(e);
886                    }
887                }
888            }));
889
890            if let Some(popup) = parent_popup_root.parent().and_then(|parent| parent.cast::<Popup>()) {
891                handler_cleanup.add(popup.closed({
892                    let weak = weak.clone();
893                    move || {
894                        if let Some(this) = weak.upgrade() {
895                            this.parent_closed();
896                        }
897                    }
898                }));
899            }
900        } else if let Some(platform_impl) = top_level.platform_impl() {
901            if platform_impl.as_window_base_impl().is_some() {
902                handler_cleanup.add(top_level.platform_lost_focus({
903                    let weak = weak.clone();
904                    move || {
905                        if let Some(this) = weak.upgrade() {
906                            this.top_level_lost_platform_focus();
907                        }
908                    }
909                }));
910
911                handler_cleanup.add(top_level.platform_deactivated({
912                    let weak = weak.clone();
913                    move || {
914                        if let Some(this) = weak.upgrade() {
915                            this.window_base_deactivated();
916                        }
917                    }
918                }));
919            } else {
920                handler_cleanup.add(top_level.platform_lost_focus({
921                    let weak = weak.clone();
922                    move || {
923                        if let Some(this) = weak.upgrade() {
924                            this.top_level_lost_platform_focus();
925                        }
926                    }
927                }));
928            }
929        }
930    }
931
932    /// Closes the popup.
933    pub fn close(&self) {
934        self.close_core()
935    }
936
937    /// Gets the value of the `TakesFocusFromNativeControl` attached
938    /// property on the specified control.
939    pub fn get_takes_focus_from_native_control(control: &Control) -> bool {
940        control.get_value(Self::takes_focus_from_native_control_property())
941    }
942
943    /// Sets the value of the `TakesFocusFromNativeControl` attached
944    /// property on the specified control.
945    pub fn set_takes_focus_from_native_control_on(control: &Control, value: bool) {
946        control.set_value(Self::takes_focus_from_native_control_property(), value)
947    }
948
949    /// Helper method to set popup's styling and templated parent (internal
950    /// upstream).
951    pub fn set_popup_parent(&self, new_parent: Option<&Ref<Control>>) {
952        let new_parent_element: Option<Ref<StyledElement>> = new_parent.map(|parent| parent.clone().upcast());
953        if self.parent().is_some() && self.parent() != new_parent_element {
954            self.set_parent(None);
955        }
956
957        if self.parent().is_none() || self.placement_target().as_ref() != new_parent {
958            self.set_parent(Nullable(new_parent_element));
959            self.set_templated_parent(Nullable(new_parent.and_then(|parent| parent.templated_parent())));
960        }
961    }
962
963    fn update_host_position(&self, popup_host: &Rc<dyn IPopupHost>, placement_target: &Ref<Control>) {
964        popup_host.configure_position(PopupPositionRequest::new_with(
965            placement_target.clone().upcast(),
966            self.placement(),
967            Point::new(self.horizontal_offset(), self.vertical_offset()),
968            self.placement_anchor(),
969            self.placement_gravity(),
970            self.placement_constraint_adjustment(),
971            Some(self.placement_rect().unwrap_or_else(|| Rect::from_size(placement_target.bounds().size()))),
972            self.custom_popup_placement_callback(),
973        ));
974    }
975
976    fn update_host_sizing(
977        &self,
978        popup_host: &Rc<dyn IPopupHost>,
979        top_level: Option<Ref<TopLevel>>,
980        placement_target: &Ref<Control>,
981    ) {
982        let mut scale_x = 1.0;
983        let mut scale_y = 1.0;
984
985        let matrix = if self.inherits_transform() {
986            top_level.and_then(|top_level| placement_target.transform_to_visual(&top_level))
987        } else {
988            None
989        };
990
991        if let Some(m) = matrix {
992            scale_x = (m.m11 * m.m11 + m.m12 * m.m12).sqrt();
993            scale_y = (m.m11 * m.m11 + m.m12 * m.m12).sqrt();
994
995            // Ideally we'd only assign a ScaleTransform here when the scale != 1, but there's
996            // an issue with LayoutTransformControl in that it sets its LayoutTransform property
997            // with LocalValue priority in ArrangeOverride in certain cases when LayoutTransform
998            // is null, which breaks TemplateBindings to this property.
999            let transform: Ref<Transform> = ScaleTransform::with_scale(scale_x, scale_y).upcast();
1000            popup_host.set_transform(Some(transform));
1001        } else {
1002            popup_host.set_transform(None);
1003        }
1004
1005        popup_host.set_width(self.width() * scale_x);
1006        popup_host.set_min_width(self.min_width() * scale_x);
1007        popup_host.set_max_width(self.max_width() * scale_x);
1008        popup_host.set_height(self.height() * scale_y);
1009        popup_host.set_min_height(self.min_height() * scale_y);
1010        popup_host.set_max_height(self.max_height() * scale_y);
1011    }
1012
1013    /// Subscribes to the changes of the render transforms of the placement
1014    /// target and of its visual ancestors, in place of the previous
1015    /// subscriptions, and remembers where the target is in its root.
1016    ///
1017    /// Deviation (DEVIATIONS.md, Popup): upstream positions an open popup
1018    /// again only when its window or parent popup moves and when the
1019    /// bounds of the placement target change (`Popup.HandlePositionChange`,
1020    /// `PlacementTargetLayoutUpdated`). The anchor rectangle is the place of
1021    /// the target in the root, which the render transforms are part of, so
1022    /// a popup opened while its target was moved by an animated render
1023    /// transform (a page that slides in) stayed where the target was when
1024    /// it opened. The popup is positioned again, once per frame, when a
1025    /// render transform of the chain is set, replaced or removed, or a
1026    /// mutable one changes in place, and the place of the target in the
1027    /// root differs from the last one (`docs/porting/decisions/
1028    /// popup-opened-while-its-target-moves.md`).
1029    fn subscribe_placement_target_transforms(&self) {
1030        self.unsubscribe_placement_target_transforms();
1031        let Some(open_state) = self.open_state() else { return };
1032        let placement_target = open_state.placement_target();
1033        self.last_placement_target_rect.set(placement_target_rect_in_root(&placement_target));
1034
1035        let mut subscriptions = Vec::new();
1036        let mut visual = Some(placement_target.upcast::<Visual>());
1037        while let Some(current) = visual {
1038            let weak = self.to_ref().downgrade();
1039            subscriptions.push(current.property_changed(move |e| {
1040                if let Some(this) = weak.upgrade() {
1041                    this.placement_target_ancestor_property_changed(e);
1042                }
1043            }));
1044            if let Some(mutable_transform) =
1045                current.render_transform().as_ref().and_then(|render_transform| render_transform.as_mutable_transform())
1046            {
1047                let weak = self.to_ref().downgrade();
1048                subscriptions.push(mutable_transform.changed(Rc::new(move || {
1049                    if let Some(this) = weak.upgrade() {
1050                        this.enqueue_for_placement_target_move();
1051                    }
1052                })));
1053            }
1054
1055            visual = current.get_visual_parent();
1056        }
1057        *self.placement_target_transform_subscriptions.borrow_mut() = subscriptions;
1058    }
1059
1060    fn unsubscribe_placement_target_transforms(&self) {
1061        let subscriptions = std::mem::take(&mut *self.placement_target_transform_subscriptions.borrow_mut());
1062        for subscription in subscriptions {
1063            subscription.dispose();
1064        }
1065    }
1066
1067    fn placement_target_ancestor_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1068        if !e.is_effective_value_change() {
1069            return;
1070        }
1071        if e.property() == Visual::render_transform_property().as_property() {
1072            // A mutable transform of the chain may have been replaced.
1073            let last = self.last_placement_target_rect.get();
1074            self.subscribe_placement_target_transforms();
1075            self.last_placement_target_rect.set(last);
1076            self.enqueue_for_placement_target_move();
1077        } else if e.property() == Visual::render_transform_origin_property().as_property() {
1078            self.enqueue_for_placement_target_move();
1079        }
1080    }
1081
1082    fn enqueue_for_placement_target_move(&self) {
1083        if self.queued_for_placement_target_move.get() {
1084            return;
1085        }
1086        self.queued_for_placement_target_move.set(true);
1087        let this = self.to_ref();
1088        Dispatcher::ui_thread().post_local(move || this.placement_target_moved(), DispatcherPriority::AFTER_RENDER);
1089    }
1090
1091    fn placement_target_moved(&self) {
1092        self.queued_for_placement_target_move.set(false);
1093        let Some(open_state) = self.open_state() else { return };
1094        let placement_target = open_state.placement_target();
1095        let rect = placement_target_rect_in_root(&placement_target);
1096        if rect == self.last_placement_target_rect.get() {
1097            return;
1098        }
1099        self.last_placement_target_rect.set(rect);
1100        self.update_host_position(&open_state.popup_host, &placement_target);
1101    }
1102
1103    fn handle_position_change(&self) {
1104        if let Some(open_state) = self.open_state() {
1105            self.update_host_position(&open_state.popup_host, &open_state.placement_target());
1106        }
1107    }
1108
1109    /// Called when the `IsOpen` property changes.
1110    fn is_open_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1111        if !self.ignore_is_open_changed.get() {
1112            if e.get_new_value::<bool>() {
1113                self.open();
1114            } else {
1115                self.close();
1116            }
1117        }
1118    }
1119
1120    /// Called when the `Child` property changes.
1121    fn child_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1122        let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<Control>>>();
1123
1124        self.logical_children().clear();
1125
1126        if let Some(old_value) = old_value {
1127            old_value.set_parent(None);
1128        }
1129
1130        if let Some(new_value) = new_value {
1131            new_value.set_parent(self.to_ref());
1132            self.logical_children().add(new_value.upcast());
1133        }
1134    }
1135
1136    fn close_core(&self) {
1137        let closing_args = CancelEventArgs::new();
1138        for (_, handler) in self.closing.snapshot().iter() {
1139            handler(&closing_args);
1140        }
1141        if closing_args.cancel() {
1142            return;
1143        }
1144
1145        self.is_open_requested.set(false);
1146        let Some(open_state) = self.open_state() else {
1147            let _ignore = self.begin_ignoring_is_open();
1148            self.set_current_value(Self::is_open_property(), false);
1149            return;
1150        };
1151
1152        let this = self.to_ref();
1153        if let Some(parent_popup) = open_state.parent_popup() {
1154            parent_popup.remove_opened_popup(&this);
1155        } else if let Some(top_level) = open_state.top_level() {
1156            top_level.remove_opened_popup(&this);
1157        }
1158
1159        open_state.dispose();
1160        *self.open_state.borrow_mut() = None;
1161
1162        for (_, handler) in self.popup_host_changed_handler.snapshot().iter() {
1163            handler(None);
1164        }
1165
1166        {
1167            let _ignore = self.begin_ignoring_is_open();
1168            self.set_current_value(Self::is_open_property(), false);
1169        }
1170
1171        for (_, handler) in self.closed.snapshot().iter() {
1172            handler();
1173        }
1174    }
1175
1176    fn listen_for_non_client_click(&self, e: &dyn IRawInputEventArgs) {
1177        let mouse = e.downcast_ref::<RawPointerEventArgs>();
1178
1179        if self.is_light_dismiss_enabled()
1180            && mouse.is_some_and(|mouse| mouse.type_() == RawPointerEventType::NonClientLeftButtonDown)
1181        {
1182            self.close_core();
1183        }
1184    }
1185
1186    fn pointer_pressed_dismiss_overlay(&self, e: &PointerPressedEventArgs) {
1187        if !self.is_light_dismiss_enabled() {
1188            return;
1189        }
1190        let Some(v) = e.source().and_then(|source| source.cast::<Visual>()) else { return };
1191
1192        if !self.is_child_or_this(&v) {
1193            if self.overlay_dismiss_event_pass_through() {
1194                pass_through_event(e);
1195            }
1196
1197            // Ensure the popup is closed if it was not closed by a pass-through event handler
1198            if self.is_open() {
1199                self.close_core();
1200            }
1201        }
1202    }
1203
1204    fn target_detached(&self, _e: &VisualTreeAttachmentEventArgs) {
1205        self.close();
1206    }
1207
1208    fn root_template_applied(&self, _e: &TemplateAppliedEventArgs) {
1209        let Some(open_state) = self.open_state() else { return };
1210
1211        let popup_host = open_state.popup_host.clone();
1212
1213        open_state.unsubscribe_template_applied();
1214
1215        open_state.set_presenter_subscription(None);
1216
1217        // If the Popup appears in a control template, then the child controls
1218        // that appear in the popup host need to have their TemplatedParent
1219        // properties set.
1220        if self.templated_parent().is_some() {
1221            if let Some(presenter) = popup_host.presenter() {
1222                presenter.apply_template();
1223
1224                let weak = self.to_ref().downgrade();
1225                let presenter_object: &FerroObject = &presenter;
1226                let presenter_subscription = FerroObjectExtensions::get_observable(
1227                    presenter_object,
1228                    ContentPresenter::child_property(),
1229                )
1230                .subscribe_fn(
1231                    move |control: Option<Ref<Control>>| {
1232                        if let Some(this) = weak.upgrade() {
1233                            this.set_templated_parent_and_apply_child_templates(control.as_ref());
1234                        }
1235                    },
1236                );
1237
1238                open_state.set_presenter_subscription(Some(presenter_subscription));
1239            }
1240        }
1241    }
1242
1243    fn set_templated_parent_and_apply_child_templates(&self, control: Option<&Ref<Control>>) {
1244        if let Some(control) = control {
1245            TemplatedControl::apply_templated_parent(control, self.templated_parent().as_ref());
1246        }
1247    }
1248
1249    fn is_child_or_this(&self, child: &Visual) -> bool {
1250        let Some(open_state) = self.open_state() else { return false };
1251
1252        let popup_host: Ref<Visual> = open_state.popup_host.as_control().upcast();
1253
1254        let mut root = child.visual_root();
1255
1256        while let Some(current) = root {
1257            let Some(element) = current.downcast_ref::<InputElement>() else { break };
1258            let Some(hosted_root) = element.as_hosted_visual_tree_root() else { break };
1259
1260            if current == popup_host {
1261                return true;
1262            }
1263
1264            root = hosted_root.host().and_then(|host| host.visual_root());
1265        }
1266
1267        false
1268    }
1269
1270    /// Whether `visual` is inside the open popup.
1271    pub fn is_inside_popup(&self, visual: &Visual) -> bool {
1272        let Some(open_state) = self.open_state() else { return false };
1273
1274        let popup_host = open_state.popup_host.as_control();
1275
1276        popup_host.is_visual_ancestor_of(visual)
1277    }
1278
1279    /// Whether the pointer is over the open popup.
1280    pub fn is_pointer_over_popup(&self) -> bool {
1281        self.open_state().is_some_and(|open_state| open_state.popup_host.as_control().is_pointer_over())
1282    }
1283
1284    fn window_deactivated(&self) {
1285        if self.is_light_dismiss_enabled() {
1286            self.close();
1287        }
1288    }
1289
1290    fn window_base_deactivated(&self) {
1291        if self.is_light_dismiss_enabled() {
1292            self.close();
1293        }
1294    }
1295
1296    fn parent_closed(&self) {
1297        if self.is_light_dismiss_enabled() {
1298            self.close();
1299        }
1300    }
1301
1302    fn top_level_lost_platform_focus(&self) {
1303        if self.is_light_dismiss_enabled() {
1304            self.close();
1305        }
1306    }
1307
1308    fn placement_target_transform_changed(&self, _v: &Ref<Visual>, _matrix: Option<Matrix>) {
1309        if let Some(open_state) = self.open_state() {
1310            self.update_host_sizing(&open_state.popup_host, open_state.top_level(), &open_state.placement_target());
1311        }
1312    }
1313
1314    fn window_lost_focus(&self) {
1315        if self.is_light_dismiss_enabled() {
1316            self.close();
1317        }
1318    }
1319
1320    fn window_position_changed(&self, _pp: PixelPoint) {
1321        self.handle_position_change();
1322    }
1323
1324    fn placement_target_layout_updated(&self) {
1325        let Some(open_state) = self.open_state() else { return };
1326
1327        // A LayoutUpdated event is raised for the whole visual tree:
1328        // the bounds of the PlacementTarget might not have effectively changed.
1329        let new_bounds = open_state.placement_target().bounds();
1330        if new_bounds == open_state.last_placement_target_bounds.get() {
1331            return;
1332        }
1333
1334        open_state.last_placement_target_bounds.set(new_bounds);
1335        self.update_host_position(&open_state.popup_host, &open_state.placement_target());
1336    }
1337
1338    fn parent_popup_position_changed(&self, _e: &PixelPointEventArgs) {
1339        self.handle_position_change();
1340    }
1341
1342    fn begin_ignoring_is_open(&self) -> IgnoreIsOpenScope<'_> {
1343        self.ignore_is_open_changed.set(true);
1344        IgnoreIsOpenScope { owner: self }
1345    }
1346
1347    /// The popup as a popup host provider (the diagnostics contract it
1348    /// implements upstream).
1349    pub fn to_popup_host_provider(&self) -> Rc<dyn IPopupHostProvider> {
1350        Rc::new(PopupHostProvider(self.to_ref()))
1351    }
1352
1353    /// Records a popup opened directly inside this popup (internal
1354    /// upstream).
1355    pub fn add_opened_popup(&self, popup: &Ref<Popup>) {
1356        self.opened_popups.borrow_mut().push(popup.clone());
1357    }
1358
1359    /// Forgets a popup opened directly inside this popup (internal
1360    /// upstream).
1361    pub fn remove_opened_popup(&self, popup: &Ref<Popup>) {
1362        let mut opened_popups = self.opened_popups.borrow_mut();
1363        if let Some(index) = opened_popups.iter().position(|p| p == popup) {
1364            opened_popups.remove(index);
1365        }
1366    }
1367}
1368
1369fn window_manager_add_shadow_hint_changed(host: &Rc<dyn IPopupHost>, hint: bool) {
1370    if let Some(pr) = host.as_popup_root() {
1371        pr.set_window_manager_add_shadow_hint(hint);
1372    }
1373}
1374
1375fn pass_through_event(e: &PointerPressedEventArgs) {
1376    let Some(layer) = e.source().and_then(|source| source.cast::<LightDismissOverlayLayer>()) else { return };
1377    let Some(root) = layer.visual_root().and_then(|root| root.cast::<InputElement>()) else { return };
1378
1379    let p = e.get_current_point(Some(&root));
1380    let layer_visual: Ref<Visual> = layer.upcast();
1381    let hit = root.input_hit_test_filtered(p.position, &|x: &Visual| !x.to_ref().ptr_eq(&layer_visual), true);
1382
1383    if let Some(hit) = hit {
1384        e.pointer().capture(Some(&hit));
1385        hit.raise_event(e);
1386        e.set_handled(true);
1387    }
1388}
1389
1390fn find_parent_popup(placement_target: &Visual) -> Option<Ref<Popup>> {
1391    for visual in placement_target.get_self_and_visual_ancestors() {
1392        if visual.is::<PopupRoot>() || visual.is::<OverlayPopupHost>() {
1393            return visual
1394                .cast::<StyledElement>()
1395                .and_then(|element| element.parent())
1396                .and_then(|parent| parent.cast::<Popup>());
1397        }
1398    }
1399
1400    None
1401}
1402
1403/// The rectangle of `placement_target` in its visual root, render
1404/// transforms included; `None` when it is not in a tree.
1405fn placement_target_rect_in_root(placement_target: &Ref<Control>) -> Option<Rect> {
1406    let visual: Ref<Visual> = placement_target.clone().upcast();
1407    let root = visual.visual_root()?;
1408    let matrix = visual.transform_to_visual(&root)?;
1409    Some(Rect::from_position_size(Point::default(), visual.bounds().size()).transform_to_aabb(matrix))
1410}
1411
1412/// The state of an open popup.
1413struct PopupOpenState {
1414    cleanup: Rc<dyn IDisposable>,
1415    presenter_cleanup: RefCell<Option<Rc<dyn IDisposable>>>,
1416    template_applied_subscription: Rc<dyn IDisposable>,
1417    placement_target: RefCell<Ref<Control>>,
1418    last_placement_target_bounds: Cell<Rect>,
1419    top_level: WeakRef<TopLevel>,
1420    parent_popup: Option<WeakRef<Popup>>,
1421    popup_host: Rc<dyn IPopupHost>,
1422}
1423
1424impl PopupOpenState {
1425    fn new(
1426        placement_target: Ref<Control>,
1427        top_level: &Ref<TopLevel>,
1428        popup_host: Rc<dyn IPopupHost>,
1429        cleanup: Rc<dyn IDisposable>,
1430        parent_popup: Option<&Ref<Popup>>,
1431        template_applied_subscription: Rc<dyn IDisposable>,
1432    ) -> Self {
1433        Self {
1434            cleanup,
1435            presenter_cleanup: RefCell::new(None),
1436            template_applied_subscription,
1437            last_placement_target_bounds: Cell::new(placement_target.bounds()),
1438            placement_target: RefCell::new(placement_target),
1439            top_level: top_level.downgrade(),
1440            parent_popup: parent_popup.map(Ref::downgrade),
1441            popup_host,
1442        }
1443    }
1444
1445    /// The top-level of the placement target; `None` once it is gone.
1446    fn top_level(&self) -> Option<Ref<TopLevel>> {
1447        self.top_level.upgrade()
1448    }
1449
1450    fn parent_popup(&self) -> Option<Ref<Popup>> {
1451        self.parent_popup.as_ref().and_then(WeakRef::upgrade)
1452    }
1453
1454    fn placement_target(&self) -> Ref<Control> {
1455        self.placement_target.borrow().clone()
1456    }
1457
1458    fn set_placement_target(&self, value: Ref<Control>) {
1459        self.last_placement_target_bounds.set(value.bounds());
1460        *self.placement_target.borrow_mut() = value;
1461    }
1462
1463    /// Stops listening for the template of the host being applied.
1464    fn unsubscribe_template_applied(&self) {
1465        self.template_applied_subscription.dispose();
1466    }
1467
1468    fn set_presenter_subscription(&self, presenter_cleanup: Option<Rc<dyn IDisposable>>) {
1469        let old = self.presenter_cleanup.replace(presenter_cleanup);
1470        if let Some(old) = old {
1471            old.dispose();
1472        }
1473    }
1474}
1475
1476impl IDisposable for PopupOpenState {
1477    fn dispose(&self) {
1478        let presenter_cleanup = self.presenter_cleanup.borrow().clone();
1479        if let Some(presenter_cleanup) = presenter_cleanup {
1480            presenter_cleanup.dispose();
1481        }
1482
1483        self.cleanup.dispose();
1484    }
1485}
1486
1487/// The popup host provider contract of a popup.
1488struct PopupHostProvider(Ref<Popup>);
1489
1490impl IPopupHostProvider for PopupHostProvider {
1491    fn popup_host(&self) -> Option<Rc<dyn IPopupHost>> {
1492        self.0.host()
1493    }
1494
1495    fn popup_host_changed(&self, handler: Rc<dyn Fn(Option<&Rc<dyn IPopupHost>>)>) -> Rc<dyn IDisposable> {
1496        self.0.popup_host_changed(move |host| handler(host))
1497    }
1498}