zng_ext_window/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! App window and monitors manager.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11// suppress nag about very simple boxed closure signatures.
12#![expect(clippy::type_complexity)]
13
14#[macro_use]
15extern crate bitflags;
16
17mod control;
18pub use control::{NestedWindowNode, NestedWindowWidgetInfoExt, OpenNestedHandlerArgs};
19
20mod ime;
21pub use ime::*;
22
23mod types;
24pub use types::*;
25
26mod monitor;
27pub use monitor::*;
28
29mod vars;
30pub use vars::*;
31
32mod service;
33pub use service::*;
34
35use zng_app::{
36    AppControlFlow, AppExtended, AppExtension, HeadlessApp,
37    update::{EventUpdate, InfoUpdates, LayoutUpdates, RenderUpdates, WidgetUpdates},
38    view_process::raw_events::{RAW_WINDOW_FOCUS_EVENT, RawWindowFocusArgs},
39    window::WindowId,
40};
41use zng_ext_image::{IMAGES_WINDOW, ImageVar};
42use zng_view_api::image::ImageMaskMode;
43
44pub mod cmd;
45
46/// Application extension that manages windows.
47///
48/// # Events
49///
50/// Events this extension provides:
51///
52/// * [`WINDOW_OPEN_EVENT`]
53/// * [`WINDOW_CHANGED_EVENT`]
54/// * [`WINDOW_FOCUS_CHANGED_EVENT`]
55/// * [`WINDOW_CLOSE_REQUESTED_EVENT`]
56/// * [`WINDOW_CLOSE_EVENT`]
57/// * [`MONITORS_CHANGED_EVENT`]
58///
59/// # Services
60///
61/// Services this extension provides:
62///
63/// * [`WINDOWS`]
64/// * [`MONITORS`]
65///
66/// The [`WINDOWS`] service is also setup as the implementer for [`IMAGES`] rendering.
67///
68/// [`IMAGES`]: zng_ext_image::IMAGES
69#[derive(Default)]
70pub struct WindowManager {}
71impl AppExtension for WindowManager {
72    fn init(&mut self) {
73        IMAGES_WINDOW.hook_render_windows_service(Box::new(WINDOWS));
74    }
75
76    fn event_preview(&mut self, update: &mut EventUpdate) {
77        MonitorsService::on_pre_event(update);
78        WINDOWS::on_pre_event(update);
79    }
80
81    fn event_ui(&mut self, update: &mut EventUpdate) {
82        WINDOWS::on_ui_event(update);
83    }
84
85    fn event(&mut self, update: &mut EventUpdate) {
86        WINDOWS::on_event(update);
87    }
88
89    fn update_ui(&mut self, update_widgets: &mut WidgetUpdates) {
90        WINDOWS::on_ui_update(update_widgets);
91    }
92
93    fn update(&mut self) {
94        WINDOWS::on_update();
95    }
96
97    fn info(&mut self, info_widgets: &mut InfoUpdates) {
98        WINDOWS::on_info(info_widgets);
99    }
100
101    fn layout(&mut self, layout_widgets: &mut LayoutUpdates) {
102        WINDOWS::on_layout(layout_widgets);
103    }
104
105    fn render(&mut self, render_widgets: &mut RenderUpdates, render_update_widgets: &mut RenderUpdates) {
106        WINDOWS::on_render(render_widgets, render_update_widgets);
107    }
108}
109
110/// Extension trait, adds [`run_window`] to [`AppExtended`].
111///
112/// [`run_window`]: AppRunWindowExt::run_window
113/// [`AppExtended`]: zng_app::AppExtended
114pub trait AppRunWindowExt {
115    /// Runs the application event loop and requests a new window.
116    ///
117    /// The window opens after the future returns it. The [`WINDOW`] context for the new window is already available in the `new_window` future.
118    ///
119    /// This method only returns when the app has exited.
120    ///
121    /// # Examples
122    ///
123    /// ```no_run
124    /// # use zng_app::window::WINDOW;
125    /// # use zng_app::APP;
126    /// # use zng_ext_window::AppRunWindowExt as _;
127    /// # trait AppDefaults { fn defaults(&self) -> zng_app::AppExtended<impl zng_app::AppExtension> { APP.minimal() } }
128    /// # impl AppDefaults for APP { }
129    /// # macro_rules! Window { ($($tt:tt)*) => { unimplemented!() } }
130    /// APP.defaults().run_window(async {
131    ///     println!("starting app with window {:?}", WINDOW.id());
132    ///     Window! {
133    ///         title = "Window 1";
134    ///         child = Text!("Window 1");
135    ///     }
136    /// })
137    /// ```
138    ///
139    /// Which is a shortcut for:
140    ///
141    /// ```no_run
142    /// # use zng_app::window::WINDOW;
143    /// # use zng_ext_window::WINDOWS;
144    /// # use zng_app::APP;
145    /// # use zng_ext_window::AppRunWindowExt as _;
146    /// # trait AppDefaults { fn defaults(&self) -> zng_app::AppExtended<impl zng_app::AppExtension> { APP.minimal() } }
147    /// # impl AppDefaults for APP { }
148    /// # macro_rules! Window { ($($tt:tt)*) => { unimplemented!() } }
149    /// APP.defaults().run(async {
150    ///     WINDOWS.open(async {
151    ///         println!("starting app with window {:?}", WINDOW.id());
152    ///         Window! {
153    ///             title = "Window 1";
154    ///             child = Text!("Window 1");
155    ///         }
156    ///     });
157    /// })
158    /// ```
159    ///
160    /// [`WINDOW`]: zng_app::window::WINDOW
161    fn run_window<F>(self, new_window: impl IntoFuture<IntoFuture = F>)
162    where
163        F: Future<Output = WindowRoot> + Send + 'static;
164}
165impl<E: AppExtension> AppRunWindowExt for AppExtended<E> {
166    fn run_window<F>(self, new_window: impl IntoFuture<IntoFuture = F>)
167    where
168        F: Future<Output = WindowRoot> + Send + 'static,
169    {
170        let new_window = new_window.into_future();
171        self.run(async move {
172            WINDOWS.open(new_window);
173        })
174    }
175}
176
177/// Window extension methods for [`HeadlessApp`].
178///
179/// [`open_window`]: HeadlessAppWindowExt::open_window
180/// [`HeadlessApp`]: zng_app::HeadlessApp
181pub trait HeadlessAppWindowExt {
182    /// Open a new headless window and returns the new window ID.
183    ///
184    /// The `new_window` runs inside the [`WINDOW`] context of the new window.
185    ///
186    /// Returns the [`WindowId`] of the new window after the window is open and loaded and has generated one frame
187    /// or if the window already closed before the first frame.
188    ///
189    /// [`WINDOW`]: zng_app::window::WINDOW
190    /// [`WindowId`]: zng_app::window::WindowId
191    fn open_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>) -> WindowId
192    where
193        F: Future<Output = WindowRoot> + Send + 'static;
194
195    /// Cause the headless window to think it is focused in the screen.
196    fn focus_window(&mut self, window_id: WindowId);
197    /// Cause the headless window to think focus moved away from it.
198    fn blur_window(&mut self, window_id: WindowId);
199
200    /// Copy the current frame pixels of the window.
201    ///
202    /// The var will update until the image is loaded or error.
203    fn window_frame_image(&mut self, window_id: WindowId, mask: Option<ImageMaskMode>) -> ImageVar;
204
205    /// Sends a close request.
206    ///
207    /// Returns if the window was found and closed.
208    fn close_window(&mut self, window_id: WindowId) -> bool;
209
210    /// Open a new headless window and update the app until the window closes.
211    fn run_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
212    where
213        F: Send + Future<Output = WindowRoot> + 'static;
214
215    /// Open a new headless window and update the app until the window closes or 60 seconds elapse.
216    #[cfg(any(test, doc, feature = "test_util"))]
217    fn doc_test_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
218    where
219        F: Future<Output = WindowRoot> + 'static + Send;
220}
221impl HeadlessAppWindowExt for HeadlessApp {
222    fn open_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>) -> WindowId
223    where
224        F: Future<Output = WindowRoot> + Send + 'static,
225    {
226        zng_app::APP.extensions().require::<WindowManager>();
227
228        let response = WINDOWS.open(new_window);
229        self.run_task(async move {
230            let window_id = response.wait_rsp().await;
231            if !WINDOWS.is_loaded(window_id) {
232                let close_rcv = WINDOW_CLOSE_EVENT.receiver();
233                let frame_rcv = FRAME_IMAGE_READY_EVENT.receiver();
234                zng_task::any!(
235                    async {
236                        while let Ok(args) = close_rcv.recv_async().await {
237                            if args.windows.contains(&window_id) {
238                                break;
239                            }
240                        }
241                    },
242                    async {
243                        while let Ok(args) = frame_rcv.recv_async().await {
244                            if args.window_id == window_id {
245                                break;
246                            }
247                        }
248                    }
249                )
250                .await;
251            }
252            window_id
253        })
254        .unwrap()
255    }
256
257    fn focus_window(&mut self, window_id: WindowId) {
258        let args = RawWindowFocusArgs::now(None, Some(window_id));
259        RAW_WINDOW_FOCUS_EVENT.notify(args);
260        let _ = self.update(false);
261    }
262
263    fn blur_window(&mut self, window_id: WindowId) {
264        let args = RawWindowFocusArgs::now(Some(window_id), None);
265        RAW_WINDOW_FOCUS_EVENT.notify(args);
266        let _ = self.update(false);
267    }
268
269    fn window_frame_image(&mut self, window_id: WindowId, mask: Option<ImageMaskMode>) -> ImageVar {
270        WINDOWS.frame_image(window_id, mask)
271    }
272
273    fn close_window(&mut self, window_id: WindowId) -> bool {
274        use zng_app::view_process::raw_events::*;
275
276        let args = RawWindowCloseRequestedArgs::now(window_id);
277        RAW_WINDOW_CLOSE_REQUESTED_EVENT.notify(args);
278
279        let mut requested = false;
280        let mut closed = false;
281
282        let _ = self.update_observe_event(
283            |update| {
284                if let Some(args) = WINDOW_CLOSE_REQUESTED_EVENT.on(update) {
285                    requested |= args.windows.contains(&window_id);
286                } else if let Some(args) = WINDOW_CLOSE_EVENT.on(update) {
287                    closed |= args.windows.contains(&window_id);
288                }
289            },
290            false,
291        );
292
293        assert_eq!(requested, closed);
294
295        closed
296    }
297
298    fn run_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
299    where
300        F: Future<Output = WindowRoot> + Send + 'static,
301    {
302        let window_id = self.open_window(new_window);
303        while WINDOWS.is_open(window_id) {
304            if let AppControlFlow::Exit = self.update(true) {
305                return;
306            }
307        }
308    }
309
310    #[cfg(any(test, doc, feature = "test_util"))]
311    fn doc_test_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
312    where
313        F: Future<Output = WindowRoot> + Send + 'static,
314    {
315        use zng_layout::unit::TimeUnits;
316        use zng_var::Var;
317        let timer = zng_app::timer::TIMERS.deadline(60.secs());
318
319        zng_task::spawn(async {
320            zng_task::deadline(65.secs()).await;
321            eprintln!("doc_test_window reached 65s fallback deadline");
322            zng_env::exit(-1);
323        });
324        let window_id = self.open_window(new_window);
325
326        while WINDOWS.is_open(window_id) {
327            if let AppControlFlow::Exit = self.update(true) {
328                return;
329            }
330            if timer.get().has_elapsed() {
331                panic!("doc_test_window reached 60s deadline");
332            }
333        }
334    }
335}