Skip to main content

freya_winit/
extensions.rs

1use freya_core::{
2    elements::rect::Rect,
3    prelude::{
4        Event,
5        EventHandlersExt,
6        EventsCombos,
7        Platform,
8        PointerEventData,
9        PressEventType,
10        UserEvent,
11    },
12    user_event::SingleThreadErasedEvent,
13};
14use winit::window::{
15    Window,
16    WindowId,
17};
18
19use crate::{
20    config::WindowConfig,
21    renderer::{
22        NativeWindowErasedEventAction,
23        RendererContext,
24    },
25};
26
27/// Extension trait that adds winit-specific window management capabilities to [`Platform`].
28pub trait WinitPlatformExt {
29    /// Dynamically launch a new window at runtime with the given configuration.
30    ///
31    /// This is meant to create windows on the fly after the application has started,
32    /// as opposed to the initial windows registered via [`crate::config::LaunchConfig`].
33    ///
34    /// Returns the [`WindowId`] of the newly created window once it has been created.
35    ///
36    /// # Example
37    ///
38    /// ```rust,no_run
39    /// use freya::prelude::*;
40    ///
41    /// async fn open_new_window() {
42    ///     let window_id = Platform::get()
43    ///         .launch_window(WindowConfig::new(my_app).with_title("New Window"))
44    ///         .await;
45    /// }
46    /// # fn my_app() -> impl IntoElement { rect() }
47    /// ```
48    fn launch_window(&self, window_config: WindowConfig) -> impl Future<Output = WindowId>;
49
50    /// Close an existing window by its [`WindowId`].
51    ///
52    /// # Example
53    ///
54    /// ```rust,no_run
55    /// use freya::{
56    ///     prelude::*,
57    ///     winit::window::WindowId,
58    /// };
59    ///
60    /// fn close_window(window_id: WindowId) {
61    ///     Platform::get().close_window(window_id);
62    /// }
63    /// ```
64    fn close_window(&self, window_id: WindowId);
65
66    /// Focus a window by its [`WindowId`].
67    ///
68    /// If `window_id` is `None`, the current window will be focused.
69    ///
70    /// # Example
71    ///
72    /// ```rust,no_run
73    /// use freya::{
74    ///     prelude::*,
75    ///     winit::window::WindowId,
76    /// };
77    ///
78    /// fn focus_specific_window(window_id: WindowId) {
79    ///     Platform::get().focus_window(Some(window_id));
80    /// }
81    ///
82    /// fn focus_current_window() {
83    ///     Platform::get().focus_window(None);
84    /// }
85    /// ```
86    fn focus_window(&self, window_id: Option<WindowId>);
87
88    /// Execute a callback with mutable access to a [`Window`].
89    ///
90    /// If `window_id` is `None`, the callback will be executed on the current window.
91    /// This allows direct manipulation of the underlying winit [`Window`] for advanced use cases.
92    ///
93    /// To create new windows dynamically, see [`WinitPlatformExt::launch_window()`].
94    ///
95    /// # Example
96    ///
97    /// ```rust,no_run
98    /// use freya::{
99    ///     prelude::*,
100    ///     winit::window::WindowId,
101    /// };
102    ///
103    /// fn set_window_title(window_id: Option<WindowId>, title: &'static str) {
104    ///     Platform::get().with_window(window_id, move |window| {
105    ///         window.set_title(title);
106    ///     });
107    /// }
108    ///
109    /// fn minimize_current_window() {
110    ///     Platform::get().with_window(None, |window| {
111    ///         window.set_minimized(true);
112    ///     });
113    /// }
114    /// ```
115    fn with_window(
116        &self,
117        window_id: Option<WindowId>,
118        callback: impl FnOnce(&mut Window) + 'static,
119    );
120
121    /// Queue a callback to be run on the renderer thread with access to a [`RendererContext`].
122    ///
123    /// The call dispatches an event to the winit event loop and returns right away; the
124    /// callback runs later, when the event loop picks it up. The [`WindowId`] passed to the
125    /// callback is the id of the window this [`Platform`] instance was bound to. The return
126    /// value is delivered through the returned oneshot
127    /// [`Receiver`](futures_channel::oneshot::Receiver), which can be `.await`ed or dropped.
128    ///
129    /// The callback runs outside any component scope, so you can't call [`Platform::get`] or
130    /// consume context from inside it; use the [`RendererContext`] argument instead.
131    fn post_callback<F, T: 'static>(&self, f: F) -> futures_channel::oneshot::Receiver<T>
132    where
133        F: FnOnce(WindowId, &mut RendererContext) -> T + 'static;
134}
135
136pub trait WindowDragExt {
137    fn window_drag(self) -> Self;
138}
139
140impl WindowDragExt for Rect {
141    fn window_drag(self) -> Self {
142        self.on_pointer_down(move |e: Event<PointerEventData>| {
143            match EventsCombos::pressed(e.global_location()) {
144                PressEventType::Single => {
145                    Platform::get().with_window(None, |window| {
146                        let _ = window.drag_window();
147                    });
148                }
149                PressEventType::Double => {
150                    Platform::get().with_window(None, |window| {
151                        if window.is_maximized() {
152                            window.set_maximized(false);
153                        } else {
154                            window.set_maximized(true);
155                        }
156                    });
157                }
158                _ => {}
159            }
160        })
161    }
162}
163
164impl WinitPlatformExt for Platform {
165    async fn launch_window(&self, window_config: WindowConfig) -> WindowId {
166        let (tx, rx) = futures_channel::oneshot::channel();
167        self.send(UserEvent::Erased(SingleThreadErasedEvent(Box::new(
168            NativeWindowErasedEventAction::LaunchWindow {
169                window_config,
170                ack: tx,
171            },
172        ))));
173        rx.await.expect("Failed to create Window")
174    }
175
176    fn close_window(&self, window_id: WindowId) {
177        self.send(UserEvent::Erased(SingleThreadErasedEvent(Box::new(
178            NativeWindowErasedEventAction::CloseWindow(window_id),
179        ))));
180    }
181
182    fn focus_window(&self, window_id: Option<WindowId>) {
183        self.with_window(window_id, |w| w.focus_window());
184    }
185
186    fn with_window(
187        &self,
188        window_id: Option<WindowId>,
189        callback: impl FnOnce(&mut Window) + 'static,
190    ) {
191        self.send(UserEvent::Erased(SingleThreadErasedEvent(Box::new(
192            NativeWindowErasedEventAction::RendererCallback(Box::new(move |id, c| {
193                callback(&mut c.windows.get_mut(&window_id.unwrap_or(id)).unwrap().window);
194            })),
195        ))));
196    }
197
198    fn post_callback<F, T: 'static>(&self, f: F) -> futures_channel::oneshot::Receiver<T>
199    where
200        F: FnOnce(WindowId, &mut RendererContext) -> T + 'static,
201    {
202        let (tx, rx) = futures_channel::oneshot::channel::<T>();
203        let cb = Box::new(move |id, ctx: &mut RendererContext| {
204            let res = (f)(id, ctx);
205            let _ = tx.send(res);
206        });
207        self.send(UserEvent::Erased(SingleThreadErasedEvent(Box::new(
208            NativeWindowErasedEventAction::RendererCallback(cb),
209        ))));
210        rx
211    }
212}