zng_ext_window/
app.rs

1use zng_app::{
2    AppBuilder, AppControlFlow, HeadlessApp,
3    view_process::raw_events::{RAW_WINDOW_FOCUS_EVENT, RawWindowFocusArgs},
4    window::WindowId,
5};
6
7use crate::{CloseWindowResult, WINDOWS, WindowInstanceState, WindowRoot, WindowVars};
8
9/// Extension trait, adds [`run_window`] to [`AppBuilder`].
10///
11/// [`run_window`]: AppRunWindowExt::run_window
12pub trait AppRunWindowExt {
13    /// Runs the application event loop and requests a new window.
14    ///
15    /// The window opens after the future returns it. The [`WINDOW`] context for the new window is already available in the `new_window` future.
16    ///
17    /// This method only returns when the app has exited.
18    ///
19    /// # Examples
20    ///
21    /// ```no_run
22    /// # use zng_app::window::WINDOW;
23    /// # use zng_app::APP;
24    /// # use zng_ext_window::AppRunWindowExt as _;
25    /// # trait AppDefaults { fn defaults(&self) -> zng_app::AppBuilder { APP.minimal() } }
26    /// # impl AppDefaults for APP { }
27    /// # macro_rules! Window { ($($tt:tt)*) => { unimplemented!() } }
28    /// APP.defaults().run_window("main", async {
29    ///     println!("starting app with window {:?}", WINDOW.id());
30    ///     Window! {
31    ///         title = "Window 1";
32    ///         child = Text!("Window 1");
33    ///     }
34    /// })
35    /// ```
36    ///
37    /// Which is a shortcut for:
38    ///
39    /// ```no_run
40    /// # use zng_app::window::WINDOW;
41    /// # use zng_ext_window::WINDOWS;
42    /// # use zng_app::APP;
43    /// # use zng_ext_window::AppRunWindowExt as _;
44    /// # trait AppDefaults { fn defaults(&self) -> zng_app::AppBuilder { APP.minimal() } }
45    /// # impl AppDefaults for APP { }
46    /// # macro_rules! Window { ($($tt:tt)*) => { unimplemented!() } }
47    /// APP.defaults().run(async {
48    ///     WINDOWS.open("main", async {
49    ///         println!("starting app with window {:?}", WINDOW.id());
50    ///         Window! {
51    ///             title = "Window 1";
52    ///             child = Text!("Window 1");
53    ///         }
54    ///     });
55    /// })
56    /// ```
57    ///
58    /// [`WINDOW`]: zng_app::window::WINDOW
59    fn run_window<F>(self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>)
60    where
61        F: Future<Output = WindowRoot> + Send + 'static;
62}
63impl AppRunWindowExt for AppBuilder {
64    fn run_window<F>(self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>)
65    where
66        F: Future<Output = WindowRoot> + Send + 'static,
67    {
68        let window_id = window_id.into();
69        let new_window = new_window.into_future();
70        self.run(async move {
71            WINDOWS.open(window_id, new_window);
72        })
73    }
74}
75
76/// Window extension methods for [`HeadlessApp`].
77///
78/// [`open_window`]: HeadlessAppWindowExt::open_window
79/// [`HeadlessApp`]: zng_app::HeadlessApp
80pub trait HeadlessAppWindowExt {
81    /// Open a new headless window and returns the new window ID.
82    ///
83    /// The `new_window` runs inside the [`WINDOW`] context of the new window.
84    ///
85    /// Returns the [`WindowVars`] of the new window after the window is open and loaded.
86    ///
87    /// [`WINDOW`]: zng_app::window::WINDOW
88    /// [`WindowId`]: zng_app::window::WindowId
89    fn open_window<F>(&mut self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>) -> WindowVars
90    where
91        F: Future<Output = WindowRoot> + Send + 'static;
92
93    /// Cause the headless window to think it is focused in the screen.
94    fn focus_window(&mut self, window_id: impl Into<WindowId>);
95    /// Cause the headless window to think focus moved away from it.
96    fn blur_window(&mut self, window_id: impl Into<WindowId>);
97
98    /// Copy the current frame pixels of the window.
99    ///
100    /// The var will update until the image is loaded or error.
101    #[cfg(feature = "image")]
102    fn window_frame_image(
103        &mut self,
104        window_id: impl Into<WindowId>,
105        mask: Option<zng_view_api::image::ImageMaskMode>,
106    ) -> zng_ext_image::ImageVar;
107
108    /// Sends a close request.
109    ///
110    /// Returns if the window was found and closed.
111    fn close_window(&mut self, window_id: impl Into<WindowId>) -> bool;
112
113    /// Open a new headless window and update the app until the window closes.
114    fn run_window<F>(&mut self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>)
115    where
116        F: Send + Future<Output = WindowRoot> + 'static;
117
118    /// Open a new headless window and update the app until the window closes or 60 seconds elapse.
119    #[cfg(any(test, doc, feature = "test_util"))]
120    fn doc_test_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
121    where
122        F: Future<Output = WindowRoot> + 'static + Send;
123}
124impl HeadlessAppWindowExt for HeadlessApp {
125    fn open_window<F>(&mut self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>) -> WindowVars
126    where
127        F: Future<Output = WindowRoot> + Send + 'static,
128    {
129        let window_id = window_id.into();
130        let response = WINDOWS.open(window_id, new_window);
131        self.run_task(async move {
132            let vars = response.wait_rsp().await;
133            if let Some(mode) = WINDOWS.mode(window_id) {
134                if mode.has_renderer() {
135                    vars.instance_state()
136                        .wait_match(|s| matches!(s, WindowInstanceState::Loaded { has_view: true } | WindowInstanceState::Closed))
137                        .await;
138                } else {
139                    vars.instance_state()
140                        .wait_match(|s| matches!(s, WindowInstanceState::Loaded { has_view: false } | WindowInstanceState::Closed))
141                        .await;
142                }
143            }
144            vars
145        })
146        .unwrap()
147    }
148
149    fn focus_window(&mut self, window_id: impl Into<WindowId>) {
150        let args = RawWindowFocusArgs::now(None, Some(window_id.into()));
151        RAW_WINDOW_FOCUS_EVENT.notify(args);
152        let _ = self.update(false);
153    }
154
155    fn blur_window(&mut self, window_id: impl Into<WindowId>) {
156        let args = RawWindowFocusArgs::now(Some(window_id.into()), None);
157        RAW_WINDOW_FOCUS_EVENT.notify(args);
158        let _ = self.update(false);
159    }
160
161    #[cfg(feature = "image")]
162    fn window_frame_image(
163        &mut self,
164        window_id: impl Into<WindowId>,
165        mask: Option<zng_view_api::image::ImageMaskMode>,
166    ) -> zng_ext_image::ImageVar {
167        WINDOWS.frame_image(window_id, mask)
168    }
169
170    fn close_window(&mut self, window_id: impl Into<WindowId>) -> bool {
171        let r = WINDOWS.close(window_id);
172        let r = self.run_task(async move { r.wait_rsp().await });
173        r.is_none() || matches!(r.unwrap(), CloseWindowResult::Closed)
174    }
175
176    fn run_window<F>(&mut self, window_id: impl Into<WindowId>, new_window: impl IntoFuture<IntoFuture = F>)
177    where
178        F: Future<Output = WindowRoot> + Send + 'static,
179    {
180        let state = self.open_window(window_id, new_window).instance_state();
181        while !matches!(state.get(), WindowInstanceState::Closed) {
182            if let AppControlFlow::Exit = self.update(true) {
183                return;
184            }
185        }
186    }
187
188    #[cfg(any(test, doc, feature = "test_util"))]
189    fn doc_test_window<F>(&mut self, new_window: impl IntoFuture<IntoFuture = F>)
190    where
191        F: Future<Output = WindowRoot> + Send + 'static,
192    {
193        use zng_layout::unit::TimeUnits;
194
195        let timer = zng_app::timer::TIMERS.deadline(60.secs());
196
197        zng_task::spawn(async {
198            zng_task::deadline(65.secs()).await;
199            eprintln!("doc_test_window reached 65s fallback deadline");
200            zng_env::exit(-1);
201        });
202        let state = self.open_window(WindowId::new_unique(), new_window).instance_state();
203
204        while !matches!(state.get(), WindowInstanceState::Closed) {
205            if let AppControlFlow::Exit = self.update(true) {
206                return;
207            }
208            if timer.get().has_elapsed() {
209                panic!("doc_test_window reached 60s deadline");
210            }
211        }
212    }
213}