Skip to main content

ferroui_native/
popup_impl.rs

1use crate::helpers::ComResultExt;
2use crate::interop::*;
3use crate::top_level_impl::{
4    top_level_of, impl_top_level_contract, MacOSTopLevelHandle, TopLevelEvents, TopLevelImpl, TopLevelParent,
5};
6use crate::window_impl::WindowImpl;
7use crate::window_impl_base::{impl_window_base_contract, WindowBaseImpl, WindowBaseParent, WindowEventsParent};
8use ferroui_base::{PixelPoint, Size};
9use ferroui_controls::platform::{IPopupImpl, ITopLevelImpl, IWindowBaseImpl, PlatformThemeVariant};
10use ferroui_controls::primitives::popup_positioning::{
11    IPopupPositioner, ManagedPopupPositioner, ManagedPopupPositionerPopupImplHelper,
12};
13use ferroui_controls::WindowResizeReason;
14use ferroui_microcom::ComPtr;
15use std::cell::RefCell;
16use std::rc::{Rc, Weak};
17
18/// A macOS popup window.
19pub struct PopupImpl {
20    weak_self: Weak<PopupImpl>,
21    base: WindowBaseImpl,
22    parent: Rc<dyn ITopLevelImpl>,
23    /// The native popup; `None` until it is created and once it is
24    /// disposed.
25    native: RefCell<Option<ComPtr<IFrnPopup>>>,
26    popup_positioner: RefCell<Option<Rc<dyn IPopupPositioner>>>,
27}
28
29/// Walks up from a popup's parent to the top-level that is not a popup.
30fn root_parent(mut parent: Rc<dyn ITopLevelImpl>) -> Rc<dyn ITopLevelImpl> {
31    loop {
32        let next = match parent.as_any().downcast_ref::<PopupImpl>() {
33            Some(popup) => popup.parent.clone(),
34            None => return parent,
35        };
36        parent = next;
37    }
38}
39
40impl PopupImpl {
41    pub(crate) fn new(factory: ComPtr<IFerroNativeFactory>, parent: Rc<dyn ITopLevelImpl>) -> Rc<PopupImpl> {
42        let this = Rc::new_cyclic(|weak_self| PopupImpl {
43            weak_self: weak_self.clone(),
44            base: WindowBaseImpl::new(factory.clone()),
45            parent: parent.clone(),
46            native: RefCell::new(None),
47            popup_positioner: RefCell::new(None),
48        });
49
50        let e = IFrnWindowEvents::from_impl(TopLevelEvents(this.clone()));
51        let native = factory.create_popup(Some(&e)).check().expect("the native popup");
52        *this.native.borrow_mut() = Some(native.clone());
53        this.base.init(MacOSTopLevelHandle::from_window_base(ComPtr::<IFrnWindowBase>::from_ref(&native)));
54
55        let weak = Rc::downgrade(&this);
56        let positioner_helper = ManagedPopupPositionerPopupImplHelper::new(
57            parent.clone(),
58            Rc::new(move |position, size, scaling| {
59                if let Some(this) = weak.upgrade() {
60                    this.move_resize(position, size, scaling);
61                }
62            }),
63        );
64        let positioner: Rc<dyn IPopupPositioner> = Rc::new(ManagedPopupPositioner::new(Rc::new(positioner_helper)));
65        *this.popup_positioner.borrow_mut() = Some(positioner);
66
67        let parent = root_parent(parent);
68
69        if let Some(window) = parent.as_any().downcast_ref::<WindowImpl>() {
70            let parent_native = window.window_base().native();
71            native.set_parent(parent_native.as_deref()).check();
72        }
73
74        // Use the parent's input context to process events
75        if let Some(parent_top_level) = top_level_of(&*parent) {
76            this.top_level().set_input_method(parent_top_level.input_method());
77        }
78
79        this
80    }
81
82    pub(crate) fn top_level(&self) -> &Rc<TopLevelImpl> {
83        self.base.top_level()
84    }
85
86    fn move_resize(&self, position: PixelPoint, size: Size, _scaling: f64) {
87        self.base.set_position(position);
88        self.base.resize(size, WindowResizeReason::Layout);
89    }
90
91    /// The native popup; `None` once the popup is disposed.
92    pub fn native(&self) -> Option<ComPtr<IFrnPopup>> {
93        self.native.borrow().clone()
94    }
95}
96
97impl TopLevelParent for PopupImpl {
98    fn top_level(&self) -> &Rc<TopLevelImpl> {
99        self.base.top_level()
100    }
101
102    fn dispose_top_level(&self) {
103        if let Some(native) = self.base.native() {
104            native.set_parent(None).check();
105        }
106
107        self.base.dispose();
108        let native = self.native.borrow_mut().take();
109        drop(native);
110    }
111
112    fn create_popup_core(&self) -> Option<Rc<dyn IPopupImpl>> {
113        let this: Rc<dyn ITopLevelImpl> = self.weak_self.upgrade()?;
114        Some(PopupImpl::new(self.top_level().factory().clone(), this))
115    }
116
117    fn set_frame_theme_variant_core(&self, theme_variant: Option<PlatformThemeVariant>) {
118        self.base.set_frame_theme_variant(theme_variant);
119    }
120}
121
122impl WindowBaseParent for PopupImpl {
123    fn window_base(&self) -> &WindowBaseImpl {
124        &self.base
125    }
126}
127
128impl WindowEventsParent for PopupImpl {
129    fn on_closing(&self) -> bool {
130        true
131    }
132
133    fn on_window_state_changed(&self, _state: FrnWindowState) {}
134
135    fn on_got_input_when_disabled(&self) {
136        // NOP on Popup
137    }
138}
139
140impl_top_level_contract!(PopupImpl {
141    fn as_window_base_impl(&self) -> Option<&dyn IWindowBaseImpl> {
142        Some(self)
143    }
144
145    fn as_popup_impl(&self) -> Option<&dyn IPopupImpl> {
146        Some(self)
147    }
148});
149
150impl_window_base_contract!(PopupImpl);
151
152impl IPopupImpl for PopupImpl {
153    fn popup_positioner(&self) -> Option<Rc<dyn IPopupPositioner>> {
154        self.popup_positioner.borrow().clone()
155    }
156
157    fn set_window_manager_add_shadow_hint(&self, _enabled: bool) {}
158
159    fn take_focus(&self) {
160        let parent = root_parent(self.parent.clone());
161
162        if let Some(window) = parent.as_any().downcast_ref::<WindowImpl>() {
163            if let Some(native) = window.native() {
164                native.take_focus_from_children().check();
165            }
166        }
167    }
168
169    fn set_hit_test_visible(&self, is_hit_test_visible: bool) {
170        if let Some(native) = self.native() {
171            native.set_hit_test_visible(is_hit_test_visible).check();
172        }
173    }
174}