zng_ext_window/
lib.rs

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