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}