Skip to main content

ferroui_native/
window_impl_base.rs

1//! What windows and popups have in common.
2
3use crate::helpers::*;
4use crate::interop::*;
5use crate::top_level_impl::{callback_property, MacOSTopLevelHandle, TopLevelEvents, TopLevelImpl, TopLevelParent};
6use ferroui_base::platform::IPlatformSettings;
7use ferroui_base::{FerroLocator, LocatorExtensions, PixelPoint, Size};
8use ferroui_controls::platform::PlatformThemeVariant;
9use ferroui_controls::{WindowEdge, WindowResizeReason};
10use ferroui_microcom::ComPtr;
11use std::cell::RefCell;
12use std::rc::Rc;
13
14/// What the concrete window types provide to the shared window code.
15pub(crate) trait WindowBaseParent: TopLevelParent {
16    /// The shared window state.
17    fn window_base(&self) -> &WindowBaseImpl;
18
19    /// Shows the window.
20    fn show_core(&self, activate: bool, is_dialog: bool) {
21        self.window_base().show(activate, is_dialog);
22    }
23}
24
25/// The receiver of the window events of the native side for the concrete
26/// window types.
27pub(crate) trait WindowEventsParent: WindowBaseParent {
28    fn on_closing(&self) -> bool;
29    fn on_window_state_changed(&self, state: FrnWindowState);
30    fn on_got_input_when_disabled(&self);
31}
32
33/// The state and behaviour shared by windows and popups.
34pub struct WindowBaseImpl {
35    base: Rc<TopLevelImpl>,
36    deactivated: RefCell<Option<Rc<dyn Fn()>>>,
37    activated: RefCell<Option<Rc<dyn Fn()>>>,
38    position_changed: RefCell<Option<Rc<dyn Fn(PixelPoint)>>>,
39}
40
41impl WindowBaseImpl {
42    pub(crate) fn new(factory: ComPtr<IFerroNativeFactory>) -> WindowBaseImpl {
43        WindowBaseImpl {
44            base: TopLevelImpl::new(factory),
45            deactivated: RefCell::new(None),
46            activated: RefCell::new(None),
47            position_changed: RefCell::new(None),
48        }
49    }
50
51    pub(crate) fn top_level(&self) -> &Rc<TopLevelImpl> {
52        &self.base
53    }
54
55    /// The native window; `None` once it is disposed.
56    pub fn native(&self) -> Option<ComPtr<IFrnWindowBase>> {
57        self.base.native_window_base()
58    }
59
60    pub fn position(&self) -> PixelPoint {
61        match self.native() {
62            Some(native) => to_ferro_pixel_point(native.get_position().check()),
63            None => PixelPoint::default(),
64        }
65    }
66
67    pub fn set_position(&self, value: PixelPoint) {
68        if let Some(native) = self.native() {
69            native.set_position(pixel_point_to_frn_point(value)).check();
70        }
71    }
72
73    callback_property!(deactivated, set_deactivated, deactivated, dyn Fn());
74    callback_property!(activated, set_activated, activated, dyn Fn());
75    callback_property!(position_changed, set_position_changed, position_changed, dyn Fn(PixelPoint));
76
77    pub fn frame_size(&self) -> Option<Size> {
78        let native = self.native()?;
79        // The native side leaves the size at (-1, -1) when the window has
80        // no frame.
81        let s = native.get_frame_size_from(FrnSize { width: -1.0, height: -1.0 }).check();
82        if s.width < 0.0 && s.height < 0.0 {
83            None
84        } else {
85            Some(Size::new(s.width, s.height))
86        }
87    }
88
89    pub(crate) fn init(&self, handle: Rc<MacOSTopLevelHandle>) {
90        self.base.init(handle);
91
92        let mut default_width = 0;
93        let mut default_height = 0;
94
95        // The screen with the lowest scaling that contains the window.
96        let position = self.position();
97        let mut screens = self.base.screens().all_screens();
98        screens.sort_by(|a, b| a.scaling().partial_cmp(&b.scaling()).unwrap_or(std::cmp::Ordering::Equal));
99        let monitor = screens.into_iter().find(|m| m.bounds().contains(position));
100
101        if let Some(monitor) = monitor {
102            // Emulate Windows 7+ default window size behavior.
103            default_width = (monitor.working_area().width as f64 * 0.75) as i32;
104            default_height = (monitor.working_area().height as f64 * 0.7) as i32;
105        }
106
107        default_width = default_width.max(300);
108        default_height = default_height.max(200);
109
110        self.resize(Size::new(default_width as f64, default_height as f64), WindowResizeReason::Layout);
111    }
112
113    pub fn activate(&self) {
114        if let Some(native) = self.native() {
115            native.activate().check();
116        }
117    }
118
119    pub fn resize(&self, client_size: Size, reason: WindowResizeReason) {
120        if let Some(native) = self.native() {
121            native.resize(client_size.width, client_size.height, to_frn_resize_reason(reason)).check();
122        }
123    }
124
125    pub fn set_frame_theme_variant(&self, theme_variant: Option<PlatformThemeVariant>) {
126        // The theme variant enum of the top-level contract and the one of
127        // the settings contract are two types with the same values.
128        let theme_variant = match theme_variant {
129            Some(theme_variant) => theme_variant as i32,
130            None => FerroLocator::current()
131                .get_service::<dyn IPlatformSettings>()
132                .map_or(PlatformThemeVariant::Light as i32, |settings| {
133                    settings.get_color_values().theme_variant() as i32
134                }),
135        };
136        if let Some(native) = self.native() {
137            native.set_frame_theme_variant(FrnPlatformThemeVariant(theme_variant)).check();
138        }
139    }
140
141    pub(crate) fn dispose(&self) {
142        if let Some(native) = self.native() {
143            native.close().check();
144        }
145        self.base.dispose();
146    }
147
148    pub fn show(&self, activate: bool, is_dialog: bool) {
149        if let Some(native) = self.native() {
150            native.show(activate, is_dialog).check();
151        }
152    }
153
154    pub fn hide(&self) {
155        if let Some(native) = self.native() {
156            native.hide().check();
157        }
158    }
159
160    pub fn begin_move_drag(&self) {
161        if let Some(native) = self.native() {
162            native.begin_move_drag().check();
163        }
164    }
165
166    pub fn max_auto_size_hint(&self) -> Size {
167        // The largest screen by width + height; the first one wins a tie.
168        let mut best: Option<Size> = None;
169        for screen in self.base.screens().all_screens() {
170            let size = screen.bounds().size().to_size(1.0);
171            if best.is_none_or(|best| size.width + size.height > best.width + best.height) {
172                best = Some(size);
173            }
174        }
175        best.unwrap_or_default()
176    }
177
178    pub fn set_topmost(&self, value: bool) {
179        if let Some(native) = self.native() {
180            native.set_top_most(value).check();
181        }
182    }
183
184    // TODO (as in the reference implementation): not implemented on macOS.
185    pub fn begin_resize_drag(&self, _edge: WindowEdge) {}
186
187    pub fn set_min_max_size(&self, min_size: Size, max_size: Size) {
188        if let Some(native) = self.native() {
189            native.set_min_max_size(to_frn_size(min_size), to_frn_size(max_size)).check();
190        }
191    }
192}
193
194impl<P: WindowBaseParent> IFrnWindowBaseEventsImpl for TopLevelEvents<P> {
195    fn activated(&self) {
196        crate::callback_base::guard((), || {
197            let activated = self.0.window_base().activated();
198            if let Some(activated) = activated {
199                activated();
200            }
201        })
202    }
203
204    fn deactivated(&self) {
205        crate::callback_base::guard((), || {
206            let deactivated = self.0.window_base().deactivated();
207            if let Some(deactivated) = deactivated {
208                deactivated();
209            }
210        })
211    }
212
213    fn position_changed(&self, position: FrnPoint) {
214        crate::callback_base::guard((), || {
215            let position_changed = self.0.window_base().position_changed();
216            if let Some(position_changed) = position_changed {
217                position_changed(to_ferro_pixel_point(position));
218            }
219        })
220    }
221}
222
223impl<P: WindowEventsParent> IFrnWindowEventsImpl for TopLevelEvents<P> {
224    fn closing(&self) -> bool {
225        crate::callback_base::guard(true, || self.0.on_closing())
226    }
227
228    fn window_state_changed(&self, state: FrnWindowState) {
229        crate::callback_base::guard((), || self.0.on_window_state_changed(state))
230    }
231
232    fn got_input_when_disabled(&self) {
233        crate::callback_base::guard((), || self.0.on_got_input_when_disabled())
234    }
235}
236
237/// Implements `IWindowBaseImpl` for a type that is a [`WindowBaseParent`].
238macro_rules! impl_window_base_contract {
239    ($ty:ty) => {
240        impl ferroui_controls::platform::IWindowBaseImpl for $ty {
241            fn frame_size(&self) -> Option<ferroui_base::Size> {
242                $crate::window_impl_base::WindowBaseParent::window_base(self).frame_size()
243            }
244
245            fn show(&self, activate: bool, is_dialog: bool) {
246                $crate::window_impl_base::WindowBaseParent::show_core(self, activate, is_dialog)
247            }
248
249            fn hide(&self) {
250                $crate::window_impl_base::WindowBaseParent::window_base(self).hide()
251            }
252
253            fn position(&self) -> ferroui_base::PixelPoint {
254                $crate::window_impl_base::WindowBaseParent::window_base(self).position()
255            }
256
257            fn position_changed(&self) -> Option<std::rc::Rc<dyn Fn(ferroui_base::PixelPoint)>> {
258                $crate::window_impl_base::WindowBaseParent::window_base(self).position_changed()
259            }
260
261            fn set_position_changed(&self, value: Option<std::rc::Rc<dyn Fn(ferroui_base::PixelPoint)>>) {
262                $crate::window_impl_base::WindowBaseParent::window_base(self).set_position_changed(value)
263            }
264
265            fn activate(&self) {
266                $crate::window_impl_base::WindowBaseParent::window_base(self).activate()
267            }
268
269            fn deactivated(&self) -> Option<std::rc::Rc<dyn Fn()>> {
270                $crate::window_impl_base::WindowBaseParent::window_base(self).deactivated()
271            }
272
273            fn set_deactivated(&self, value: Option<std::rc::Rc<dyn Fn()>>) {
274                $crate::window_impl_base::WindowBaseParent::window_base(self).set_deactivated(value)
275            }
276
277            fn activated(&self) -> Option<std::rc::Rc<dyn Fn()>> {
278                $crate::window_impl_base::WindowBaseParent::window_base(self).activated()
279            }
280
281            fn set_activated(&self, value: Option<std::rc::Rc<dyn Fn()>>) {
282                $crate::window_impl_base::WindowBaseParent::window_base(self).set_activated(value)
283            }
284
285            fn max_auto_size_hint(&self) -> ferroui_base::Size {
286                $crate::window_impl_base::WindowBaseParent::window_base(self).max_auto_size_hint()
287            }
288
289            fn set_topmost(&self, value: bool) {
290                $crate::window_impl_base::WindowBaseParent::window_base(self).set_topmost(value)
291            }
292        }
293    };
294}
295pub(crate) use impl_window_base_contract;