zng_ext_window/
windows.rs

1use std::{any::Any, mem, pin::Pin, sync::Arc};
2
3use parking_lot::Mutex;
4use zng_app::{
5    APP, Deadline, hn_once,
6    timer::TIMERS,
7    update::{InfoUpdates, LayoutUpdates, RenderUpdates, UPDATES, WidgetUpdates},
8    view_process::{
9        VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
10        raw_events::{RAW_WINDOW_FOCUS_EVENT, RawWindowFocusArgs},
11    },
12    widget::{
13        WIDGET, WidgetId,
14        info::{WidgetInfoTree, access::AccessEnabled},
15    },
16    window::{WINDOW, WINDOWS_APP, WindowId, WindowMode},
17};
18use zng_app_context::{RunOnDrop, app_local};
19use zng_task::{ParallelIteratorExt, rayon::prelude::*};
20use zng_txt::{ToTxt as _, Txt, formatx};
21use zng_unique_id::{IdEntry, IdMap, IdSet};
22use zng_var::{ResponderVar, ResponseVar, Var, const_var, response_done_var, response_var, var, var_default};
23use zng_view_api::{
24    DragDropId,
25    api_extension::{ApiExtensionId, ApiExtensionPayload},
26    drag_drop::{DragDropData, DragDropEffect, DragDropError},
27    window::{RenderMode, WindowCapability},
28};
29use zng_wgt::prelude::{InteractionPath, UiNode, WidgetInfo, WidgetInfoBuilder};
30
31use crate::{
32    CloseWindowResult, NestedWindowNode, ParallelWin, ViewExtensionError, WINDOW_CLOSE_EVENT, WINDOW_CLOSE_REQUESTED_EVENT,
33    WindowCloseArgs, WindowCloseRequestedArgs, WindowInstance, WindowInstanceState, WindowLoadingHandle, WindowNode, WindowRoot,
34    WindowVars,
35};
36
37app_local! {
38    pub(crate) static WINDOWS_SV: WindowsService = WindowsService::new();
39}
40pub(crate) struct WindowsService {
41    exit_on_last_close: Var<bool>,
42    pub(crate) default_render_mode: Var<RenderMode>,
43    parallel: Var<ParallelWin>,
44    // Mutex for Sync
45    pub(crate) root_extenders: Mutex<Vec<Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>>>,
46    pub(crate) open_nested_handlers: Mutex<Vec<Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>>>,
47
48    pub(crate) windows: IdMap<WindowId, WindowInstance>,
49    widget_update_buf: Vec<(WindowId, WindowNode, Option<WindowVars>)>,
50
51    pub(crate) focused: Var<Option<InteractionPath>>,
52    focused_set: bool,
53}
54impl WindowsService {
55    fn new() -> Self {
56        WINDOWS_APP.init_info_provider(Box::new(WINDOWS));
57        #[cfg(feature = "image")]
58        zng_ext_image::IMAGES_WINDOW.hook_render_windows_service(Box::new(WINDOWS));
59        crate::hooks::hook_events();
60        Self {
61            exit_on_last_close: var(true),
62            default_render_mode: var_default(),
63            parallel: var_default(),
64            root_extenders: Mutex::new(vec![]),
65            open_nested_handlers: Mutex::new(vec![]),
66
67            windows: IdMap::new(),
68            widget_update_buf: vec![],
69
70            focused: const_var(None),
71            focused_set: false,
72        }
73    }
74
75    /// Called to apply widget updates without locking the entire WINDOWS service, reuses a buffer
76    ///
77    /// Must call `finish_widget_update` to after the update
78    fn start_widget_update(&mut self, clone_vars: bool) -> Vec<(WindowId, WindowNode, Option<WindowVars>)> {
79        let mut buf = mem::take(&mut self.widget_update_buf);
80        if clone_vars {
81            buf.extend(
82                self.windows
83                    .iter_mut()
84                    .filter_map(|(k, v)| Some((*k, v.root.take()?, v.vars.clone()))),
85            );
86        } else {
87            buf.extend(self.windows.iter_mut().filter_map(|(k, v)| Some((*k, v.root.take()?, None))));
88        }
89        buf
90    }
91
92    fn finish_widget_update(&mut self, mut nodes: Vec<(WindowId, WindowNode, Option<WindowVars>)>) {
93        for (id, node, _) in nodes.drain(..) {
94            self.windows.get_mut(&id).unwrap().root = Some(node);
95        }
96        self.widget_update_buf = nodes;
97    }
98}
99
100/// Windows service.
101pub struct WINDOWS;
102impl WINDOWS {
103    /// Defines if app process exit should be requested when the last window closes. This is `true` by default.
104    ///
105    /// This setting does not consider headless windows and is fully ignored in headless apps.
106    ///
107    /// Note that if [`APP.exit`] is requested directly the windows service will cancel it, request
108    /// close for all headed and headless windows, and if all windows close request app exit again, independent
109    /// of this setting.
110    ///
111    /// [`APP.exit`]: zng_app::APP::exit
112    pub fn exit_on_last_close(&self) -> Var<bool> {
113        WINDOWS_SV.read().exit_on_last_close.clone()
114    }
115
116    /// Defines the render mode of windows opened by this service.
117    ///
118    /// Note that this setting only affects windows opened after it is changed, also the view-process may select
119    /// a different render mode if it cannot support the requested mode.
120    pub fn default_render_mode(&self) -> Var<RenderMode> {
121        WINDOWS_SV.read().default_render_mode.clone()
122    }
123
124    /// Defines what window operations can run in parallel, between windows.
125    ///
126    /// Note that this config is for parallel execution between windows, see the `parallel` property for parallel execution
127    /// within windows and widgets.
128    ///
129    /// See [`ParallelWin`] for the options.
130    pub fn parallel(&self) -> Var<ParallelWin> {
131        WINDOWS_SV.read().parallel.clone()
132    }
133
134    /// Variable that tracks the OS window manager configuration for the window chrome.
135    ///
136    /// The chrome (also known as window decorations) defines the title bar, window buttons and window border. Some
137    /// window managers don't provide a native chrome, you can use this config with [`WindowVars::chrome`]
138    /// in a [`register_root_extender`] to provide a custom fallback chrome, the main crate `zng` also provides a custom
139    /// chrome fallback.
140    ///
141    /// [`register_root_extender`]: WINDOWS_EXTENSIONS::register_root_extender
142    pub fn system_chrome(&self) -> Var<bool> {
143        VIEW_PROCESS_INITED_EVENT.var_map(
144            |a| Some(a.window.contains(WindowCapability::SYSTEM_CHROME)),
145            || VIEW_PROCESS.info().window.contains(WindowCapability::SYSTEM_CHROME),
146        )
147    }
148}
149impl WINDOWS {
150    /// Requests a new window.
151    ///
152    /// The `new_window` future runs inside the new [`WINDOW`] context.
153    ///
154    /// Returns a response var that will update once when the window starts building, the [`WindowVars::instance_state`] can be
155    /// use to continue monitoring the window.
156    ///
157    /// An update cycle is processed between the end of `new_window` and the window init, this means that you
158    /// can use the context [`WINDOW`] to set variables that will be read on init with the new value.
159    ///
160    /// Note that there are no *window handles*, the window is controlled in the service using the ID or from the inside.
161    ///
162    /// # Panics
163    ///
164    /// If the `window_id` is already assigned to an open or opening window.
165    pub fn open<F: Future<Output = WindowRoot> + Send + 'static>(
166        &self,
167        window_id: impl Into<WindowId>,
168        new_window: impl IntoFuture<IntoFuture = F>,
169    ) -> ResponseVar<WindowVars> {
170        self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, false)
171    }
172
173    /// Focus a window if it is open or loading, otherwise opens it focused.
174    ///
175    /// Returns a variable that updates once the window starts building or is already open. You can
176    /// track the focused status using [`WindowVars::is_focused`].
177    pub fn focus_or_open<F: Future<Output = WindowRoot> + Send + 'static>(
178        &self,
179        window_id: impl Into<WindowId>,
180        new_window: impl IntoFuture<IntoFuture = F>,
181    ) -> ResponseVar<WindowVars> {
182        self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, true)
183    }
184    /// Requests a new headless window.
185    ///
186    /// This is similar to `open`, but the window will not show on screen and can optionally not even have a renderer.
187    ///
188    /// # Panics
189    ///
190    /// If the `window_id` is already assigned to an open or opening window.
191    pub fn open_headless<F: Future<Output = WindowRoot> + Send + 'static>(
192        &self,
193        window_id: impl Into<WindowId>,
194        new_window: impl IntoFuture<IntoFuture = F>,
195        with_renderer: bool,
196    ) -> ResponseVar<WindowVars> {
197        self.open_impl(
198            window_id.into(),
199            Box::pin(new_window.into_future()),
200            if with_renderer {
201                WindowMode::HeadlessWithRenderer
202            } else {
203                WindowMode::Headless
204            },
205            false,
206        )
207    }
208    fn open_impl(
209        &self,
210        window_id: WindowId,
211        new_window: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
212        mode: WindowMode,
213        focus_existing: bool,
214    ) -> ResponseVar<WindowVars> {
215        let mode = match (mode, APP.window_mode()) {
216            (m, WindowMode::Headed) => m,
217            (m, WindowMode::HeadlessWithRenderer) => {
218                if m.is_headless() {
219                    m
220                } else {
221                    WindowMode::HeadlessWithRenderer
222                }
223            }
224            (_, WindowMode::Headless) => WindowMode::Headless,
225        };
226
227        let mut s = WINDOWS_SV.write();
228        match s.windows.entry(window_id) {
229            IdEntry::Vacant(e) => {
230                let (r, rsp) = response_var();
231                e.insert(WindowInstance::new(window_id, mode, new_window, r));
232                rsp
233            }
234            IdEntry::Occupied(e) => {
235                if focus_existing {
236                    match &e.get().vars {
237                        Some(v) => {
238                            v.0.focused.set(true);
239                            response_done_var(v.clone())
240                        }
241                        None => {
242                            // just requested, did not start building yet
243                            let (r, rsp) = response_var();
244                            UPDATES.once_update("WINDOWS wait build start", move || match WINDOWS.vars(window_id) {
245                                Some(v) => r.respond(v),
246                                None => tracing::error!("window {window_id:?} build did not start"),
247                            });
248                            rsp
249                        }
250                    }
251                } else {
252                    panic!("{window_id:?} is already open or opening");
253                }
254            }
255        }
256    }
257
258    /// Gets a handle that stops the window from loading while the handle is alive.
259    ///
260    /// A window is only opened in the view-process after it is loaded, without any loading handles the window is considered loaded
261    /// after the first layout pass. Nodes in the window can request a loading handle to delay the view opening to after all async resources
262    /// it requires are loaded.
263    ///
264    /// Note that a window is only loaded after all handles are dropped or expired, you should set a reasonable `deadline`,  
265    /// it is best to partially render a window after a short time than not show anything.
266    ///
267    /// Returns `None` if the window has already loaded or is not found.
268    pub fn loading_handle(
269        &self,
270        window_id: impl Into<WindowId>,
271        deadline: impl Into<Deadline>,
272        debug_name: impl Into<Txt>,
273    ) -> Option<WindowLoadingHandle> {
274        self.loading_handle_impl(window_id.into(), deadline.into(), debug_name.into())
275    }
276    fn loading_handle_impl(&self, window_id: WindowId, deadline: Deadline, debug_name: Txt) -> Option<WindowLoadingHandle> {
277        let mut s = WINDOWS_SV.write();
278
279        let window = s.windows.get_mut(&window_id)?;
280        if let Some(vars) = &window.vars
281            && !matches!(
282                vars.0.instance_state.get(),
283                WindowInstanceState::Building | WindowInstanceState::Loading
284            )
285        {
286            tracing::debug!("cannot get loading handle `{debug_name}` for window `{window_id:?}`, already loaded");
287            return None;
288        }
289        let h = if let Some(h) = window.pending_loading.upgrade() {
290            h
291        } else {
292            let h: Arc<dyn Any + Send + Sync> = Arc::new(RunOnDrop::new(move || {
293                if let Some(vars) = WINDOWS.vars(window_id) {
294                    vars.0.instance_state.modify(move |a| {
295                        if matches!(a.value(), WindowInstanceState::Loading) {
296                            UPDATES.layout_window(window_id);
297                        }
298                    });
299                }
300            }));
301            window.pending_loading = Arc::downgrade(&h);
302            h
303        };
304
305        let handle = TIMERS.deadline(deadline);
306        handle
307            .hook(move |a| {
308                let _hold = &h;
309                if a.value().has_elapsed() {
310                    tracing::debug!("loading handle `{debug_name}` timeout");
311                    false
312                } else {
313                    true
314                }
315            })
316            .perm();
317        Some(WindowLoadingHandle(handle))
318    }
319
320    /// Starts closing a window, the operation can be canceled by listeners of
321    /// [`WINDOW_CLOSE_REQUESTED_EVENT`]. If the window has children they are closed together.
322    ///
323    /// Returns a response var that will update once with the result of the operation.
324    ///
325    /// If the window is not found returns `Closed`.
326    pub fn close(&self, window_id: impl Into<WindowId>) -> ResponseVar<CloseWindowResult> {
327        self.close_together([window_id.into()])
328    }
329
330    /// Starts closing multiple windows together, the operation can be canceled by listeners of
331    /// [`WINDOW_CLOSE_REQUESTED_EVENT`]. If canceled none of the windows are closed. Children of each window
332    /// are also selected the close together.
333    ///
334    /// Returns a response var that will update once with the result of the operation. Returns
335    /// [`Cancel`] if `windows` is empty.
336    ///
337    /// [`Cancel`]: CloseWindowResult::Cancel
338    pub fn close_together(&self, windows: impl IntoIterator<Item = WindowId>) -> ResponseVar<CloseWindowResult> {
339        self.close_together_impl(windows.into_iter().collect())
340    }
341    fn close_together_impl(&self, request: Vec<WindowId>) -> ResponseVar<CloseWindowResult> {
342        let (r, rsp) = response_var();
343
344        let mut s = WINDOWS_SV.write();
345
346        // collect requests that are still building
347        let mut building = vec![];
348        for id in request.iter().copied() {
349            if let IdEntry::Occupied(w) = s.windows.entry(id) {
350                match &w.get().vars {
351                    Some(v) => {
352                        let state = v.instance_state();
353                        if let WindowInstanceState::Building = state.get() {
354                            building.push(state);
355                        }
356                    }
357                    None => {
358                        // did not start building yet, drop
359                        w.remove();
360                    }
361                }
362            }
363        }
364        drop(s);
365
366        if building.is_empty() {
367            close_together_all_built(request, r);
368        } else {
369            UPDATES
370                .run(async move {
371                    for b in building {
372                        b.wait_match(|s| !matches!(s, WindowInstanceState::Building)).await;
373                    }
374                    close_together_all_built(request, r);
375                })
376                .perm();
377        }
378
379        rsp
380    }
381
382    /// Starts closing all open windows together, the operation can be canceled by listeners of
383    /// [`WINDOW_CLOSE_REQUESTED_EVENT`]. If canceled none of the windows are closed.
384    ///
385    /// Returns a response var that will update once with the result of the operation. Returns
386    /// [`Cancel`] if no window is open.
387    ///
388    /// [`Cancel`]: CloseWindowResult::Cancel
389    pub fn close_all(&self) -> ResponseVar<CloseWindowResult> {
390        let set: Vec<_> = WINDOWS_SV.read().windows.keys().copied().collect();
391        self.close_together_impl(set)
392    }
393}
394
395fn close_together_all_built(request: Vec<WindowId>, r: ResponderVar<CloseWindowResult>) {
396    let s = WINDOWS_SV.read();
397    let mut open = IdSet::new();
398    fn collect(s: &WindowsService, request: &mut dyn Iterator<Item = WindowId>, open: &mut IdSet<WindowId>) {
399        for id in request {
400            if let Some(w) = s.windows.get(&id)
401                && open.insert(id)
402            {
403                collect(s, &mut w.vars.as_ref().unwrap().children().get().into_iter(), open);
404            }
405        }
406    }
407    collect(&s, &mut request.into_iter(), &mut open);
408
409    WINDOW_CLOSE_REQUESTED_EVENT.notify(WindowCloseRequestedArgs::now(open));
410    WINDOW_CLOSE_REQUESTED_EVENT
411        .on_event(
412            true,
413            hn_once!(|args: &WindowCloseRequestedArgs| {
414                if args.propagation.is_stopped() {
415                    r.respond(CloseWindowResult::Cancel);
416                    return;
417                }
418
419                // notify close first, so that the closed windows receive on_close.
420                WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
421                WINDOW_CLOSE_EVENT
422                    .on_event(
423                        true,
424                        hn_once!(|args: &WindowCloseArgs| {
425                            // deinit windows
426                            let mut nodes;
427                            let parallel;
428                            {
429                                let mut s = WINDOWS_SV.write();
430                                // UPDATE includes info rebuild
431                                parallel = s.parallel.get().contains(ParallelWin::UPDATE);
432                                // take root nodes to allow widgets to use WINDOWS
433                                nodes = s.start_widget_update(true);
434                            };
435
436                            let deinit = |(id, n, _): &mut (WindowId, WindowNode, Option<WindowVars>)| {
437                                if args.windows.contains(id) {
438                                    n.with_root(|n| n.deinit());
439                                }
440                            };
441                            if parallel {
442                                nodes.par_iter_mut().with_ctx().for_each(deinit);
443                            } else {
444                                nodes.iter_mut().for_each(deinit);
445                            }
446
447                            // drop windows
448                            let mut s = WINDOWS_SV.write();
449                            s.finish_widget_update(nodes);
450
451                            for id in &args.windows {
452                                if let Some(w) = s.windows.remove(id) {
453                                    let vars = w.vars.unwrap();
454                                    vars.0.instance_state.set(WindowInstanceState::Closed);
455                                    if vars.0.focused.get() && APP.window_mode().is_headless() {
456                                        // simulate focus loss in headless app (mostly for tests)
457                                        RAW_WINDOW_FOCUS_EVENT.notify(RawWindowFocusArgs::now(Some(*id), None));
458                                    }
459                                }
460                            }
461
462                            if s.exit_on_last_close.get()
463                                && !s.windows.iter().any(|w| w.1.mode.is_headed())
464                                && APP.window_mode().is_headed()
465                            {
466                                zng_app::APP.exit();
467                            }
468
469                            r.respond(CloseWindowResult::Closed);
470                        }),
471                    )
472                    .perm();
473
474                // notify
475                WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
476            }),
477        )
478        .perm();
479}
480
481impl WINDOWS {
482    /// Gets if the window is headed or headless.
483    ///
484    /// Returns `None` if the window is not found.
485    pub fn mode(&self, window_id: impl Into<WindowId>) -> Option<WindowMode> {
486        self.mode_impl(window_id.into())
487    }
488    fn mode_impl(&self, window_id: WindowId) -> Option<WindowMode> {
489        Some(WINDOWS_SV.read().windows.get(&window_id)?.mode)
490    }
491
492    /// Returns a shared reference the variables that control the window, if the window exists.
493    pub fn vars(&self, window_id: impl Into<WindowId>) -> Option<WindowVars> {
494        self.vars_impl(window_id.into())
495    }
496    fn vars_impl(&self, window_id: WindowId) -> Option<WindowVars> {
497        WINDOWS_SV.read().windows.get(&window_id)?.vars.clone()
498    }
499
500    /// Get the latest info tree for the window.
501    pub fn widget_tree(&self, id: impl Into<WindowId>) -> Option<WidgetInfoTree> {
502        zng_app::window::WindowsService::widget_tree(self, id.into())
503    }
504
505    /// Search for the widget in the latest info tree of each open window.
506    pub fn widget_info(&self, id: impl Into<WidgetId>) -> Option<WidgetInfo> {
507        zng_app::window::WindowsService::widget_info(self, id.into())
508    }
509
510    /// Returns shared references to the widget trees of each open window.
511    pub fn widget_trees(&self) -> Vec<WidgetInfoTree> {
512        WINDOWS_SV.read().windows.values().filter_map(|v| v.info.clone()).collect()
513    }
514}
515impl zng_app::window::WindowsService for WINDOWS {
516    fn widget_tree(&self, id: WindowId) -> Option<WidgetInfoTree> {
517        WINDOWS_SV.read().windows.get(&id)?.info.clone()
518    }
519
520    fn widget_info(&self, id: WidgetId) -> Option<WidgetInfo> {
521        WINDOWS_SV.read().windows.values().find_map(|w| w.info.as_ref()?.get(id))
522    }
523
524    fn update_info(&self, updates: &mut InfoUpdates) {
525        let mut nodes;
526        let parallel;
527        {
528            let mut s = WINDOWS_SV.write();
529            // fulfill delivery search
530            if updates.delivery_list_mut().has_pending_search() {
531                updates
532                    .delivery_list_mut()
533                    .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
534            }
535            // UPDATE includes info rebuild
536            parallel = s.parallel.get().contains(ParallelWin::UPDATE);
537            // take root nodes to allow widgets to use WINDOWS
538            nodes = s.start_widget_update(true);
539        };
540
541        // for each window
542        let updates = Arc::new(mem::take(updates));
543        let rebuild_info = |(id, n, vars): &mut (WindowId, WindowNode, Option<WindowVars>)| {
544            if updates.delivery_list().enter_window(*id) {
545                // rebuild info
546                let vars = vars.as_ref().unwrap();
547                let access_enabled = vars.access_enabled().get();
548                let info = n.with_root(|n| {
549                    let mut builder = WidgetInfoBuilder::new(
550                        updates.clone(),
551                        *id,
552                        access_enabled,
553                        WIDGET.id(),
554                        WIDGET.bounds(),
555                        WIDGET.border(),
556                        vars.scale_factor().get(),
557                    );
558                    n.info(&mut builder);
559
560                    builder.finalize(WINDOW.try_info(), true)
561                });
562                n.win_ctx.set_widget_tree(info.clone());
563                WINDOWS_SV.write().windows.get_mut(id).unwrap().info = Some(info.clone());
564
565                if access_enabled.contains(AccessEnabled::VIEW) {
566                    // access data is send in the frame display list
567                    UPDATES.render_window(n.win_ctx.id());
568                }
569            }
570        };
571        if parallel {
572            nodes.par_iter_mut().with_ctx().for_each(rebuild_info);
573        } else {
574            nodes.iter_mut().for_each(rebuild_info);
575        }
576
577        // restore root nodes
578        WINDOWS_SV.write().finish_widget_update(nodes);
579    }
580
581    fn update_widgets(&self, updates: &mut WidgetUpdates) {
582        let mut nodes;
583        let parallel;
584        {
585            let mut s = WINDOWS_SV.write();
586            // fulfill delivery search
587            if updates.delivery_list_mut().has_pending_search() {
588                updates
589                    .delivery_list_mut()
590                    .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
591            }
592            parallel = s.parallel.get().contains(ParallelWin::UPDATE);
593            // take root nodes to allow widgets to use WINDOWS
594            nodes = s.start_widget_update(false);
595        };
596
597        // for each window
598        let update = |(id, n, _): &mut (WindowId, WindowNode, _)| {
599            if updates.delivery_list().enter_window(*id) {
600                if n.wgt_ctx.take_reinit() {
601                    n.with_root(|n| {
602                        n.deinit();
603                        n.init();
604                    })
605                } else {
606                    n.with_root(|n| n.update(updates));
607                }
608            }
609        };
610        if parallel {
611            nodes.par_iter_mut().with_ctx().for_each(update);
612        } else {
613            nodes.iter_mut().for_each(update);
614        }
615
616        // restore root nodes
617        WINDOWS_SV.write().finish_widget_update(nodes);
618    }
619
620    fn update_layout(&self, updates: &mut LayoutUpdates) {
621        let mut nodes;
622        let parallel;
623        {
624            let mut s = WINDOWS_SV.write();
625            // fulfill delivery search
626            if updates.delivery_list_mut().has_pending_search() {
627                updates
628                    .delivery_list_mut()
629                    .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
630            }
631            parallel = s.parallel.get().contains(ParallelWin::LAYOUT);
632            // take root nodes to allow widgets to use WINDOWS
633            nodes = s.start_widget_update(true);
634        };
635
636        // for each window
637        let updates = Arc::new(mem::take(updates));
638        let layout = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
639            crate::window::layout_open_view(args, &updates);
640        };
641        if parallel {
642            nodes.par_iter_mut().with_ctx().for_each(layout);
643        } else {
644            nodes.iter_mut().for_each(layout);
645        }
646
647        // restore root nodes
648        WINDOWS_SV.write().finish_widget_update(nodes);
649    }
650
651    fn update_render(&self, render_widgets: &mut RenderUpdates, render_update_widgets: &mut RenderUpdates) {
652        let mut nodes;
653        let parallel;
654        {
655            let mut s = WINDOWS_SV.write();
656            // fulfill delivery search
657            for d in [&mut *render_widgets, &mut *render_update_widgets] {
658                if d.delivery_list_mut().has_pending_search() {
659                    d.delivery_list_mut()
660                        .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
661                }
662            }
663            parallel = s.parallel.get().contains(ParallelWin::RENDER);
664            // take root nodes to allow widgets to use WINDOWS
665            nodes = s.start_widget_update(true);
666        };
667
668        // for each window
669        let render_widgets = Arc::new(mem::take(render_widgets));
670        let render_update_widgets = Arc::new(mem::take(render_update_widgets));
671        let render = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
672            crate::window::render(args, &render_widgets, &render_update_widgets);
673        };
674        if parallel {
675            nodes.par_iter_mut().with_ctx().for_each(render);
676        } else {
677            nodes.iter_mut().for_each(render);
678        }
679
680        // restore root nodes
681        WINDOWS_SV.write().finish_widget_update(nodes);
682    }
683}
684
685#[cfg(feature = "image")]
686impl WINDOWS {
687    /// Generate an image from the current rendered frame of the window or the first frame of the window.
688    ///
689    /// The image is not loaded at the moment of return, it will update when the frame pixels are copied.
690    ///
691    /// If the window is not found or an error is reported in the [image error].
692    ///
693    /// [image error]: zng_ext_image::ImageEntry::error
694    pub fn frame_image(&self, window_id: impl Into<WindowId>, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
695        self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image(mask)))
696    }
697
698    /// Generate an image from a rectangular selection of the current rendered frame of the window, or of the first frame of the window.
699    ///
700    // The image is not loaded at the moment of return, it will update when the frame pixels are copied.
701    ///
702    /// If the window is not found the error is reported in the [image error].
703    ///
704    /// [image error]: zng_ext_image::ImageEntry::error
705    pub fn frame_image_rect(
706        &self,
707        window_id: impl Into<WindowId>,
708        rect: zng_layout::unit::PxRect,
709        mask: Option<zng_ext_image::ImageMaskMode>,
710    ) -> zng_ext_image::ImageVar {
711        self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image_rect(rect, mask)))
712    }
713
714    fn frame_image_task(
715        &self,
716        window_id: WindowId,
717        task: Box<
718            dyn FnOnce(
719                    &zng_app::view_process::ViewRenderer,
720                ) -> Result<zng_app::view_process::ViewImageHandle, zng_task::channel::ChannelError>
721                + Send
722                + Sync,
723        >,
724    ) -> zng_ext_image::ImageVar {
725        use zng_ext_image::*;
726        use zng_txt::*;
727        use zng_var::*;
728
729        let r = var(ImageEntry::new_loading());
730        let rr = r.read_only();
731
732        UPDATES.once_update("WINDOWS.frame_image", move || {
733            let s = WINDOWS_SV.read();
734            if let Some(w) = &s.windows.get(&window_id) {
735                if !w.mode.has_renderer() {
736                    return r.set(ImageEntry::new_error(formatx!("window {window_id} has no renderer")));
737                }
738
739                if let Some(n) = &w.root
740                    && let Some(v) = &n.renderer
741                    && n.frame_id != zng_view_api::window::FrameId::INVALID
742                {
743                    // already has a frame
744                    return match task(v) {
745                        Ok(handle) => {
746                            let img = IMAGES.register(None, (handle, Default::default()));
747                            img.set_bind(&r).perm();
748                            r.hold(img).perm();
749                        }
750                        Err(e) => r.set(ImageEntry::new_error(e.to_txt())),
751                    };
752                }
753
754                // first frame not available yet, await it
755                use zng_app::view_process::raw_events::RAW_FRAME_RENDERED_EVENT;
756                let mut task = Some(task);
757                RAW_FRAME_RENDERED_EVENT
758                    .hook(move |args| {
759                        if args.window_id == window_id {
760                            let img = WINDOWS.frame_image_task(window_id, task.take().unwrap());
761                            img.set_bind(&r).perm();
762                            r.hold(r.clone()).perm();
763                            false
764                        } else {
765                            WINDOWS_SV.read().windows.contains_key(&window_id)
766                        }
767                    })
768                    .perm();
769            } else {
770                r.set(ImageEntry::new_error(formatx!("window {window_id} not found")));
771            }
772        });
773
774        rr
775    }
776}
777impl WINDOWS {
778    /// Move the window to the front of the operating system Z stack.
779    ///
780    /// Note that the window is not focused, the `FOCUS.focus_window` operation brings to top and sets focus.
781    pub fn bring_to_top(&self, window_id: impl Into<WindowId>) {
782        self.bring_to_top_impl(window_id.into());
783    }
784    fn bring_to_top_impl(&self, window_id: WindowId) {
785        UPDATES.once_update("WINDOWS.bring_to_top", move || {
786            if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
787                && let Some(root) = &w.root
788                && let Some(v) = &root.view_window
789            {
790                let _ = v.bring_to_top();
791            } else {
792                tracing::error!("cannot bring_to_top {window_id}, not open in view-process");
793            }
794        });
795    }
796}
797
798/// Windows focus service integration.
799///
800/// The `FOCUS` uses this.
801#[expect(non_camel_case_types)]
802pub struct WINDOWS_FOCUS;
803impl WINDOWS_FOCUS {
804    /// Setup a var that is controlled by the focus service and tracks the focused widget.
805    ///
806    /// This must be called by the focus implementation only.
807    pub fn hook_focus_service(&self, focused: Var<Option<InteractionPath>>) {
808        let mut s = WINDOWS_SV.write();
809        assert!(!s.focused_set, "focus service already hooked");
810        s.focused = focused;
811        let mut handler = crate::hooks::focused_widget_handler();
812        s.focused
813            .hook(move |a| {
814                handler(a.value());
815                true
816            })
817            .perm();
818    }
819
820    /// Request operating system focus for the window.
821    ///
822    /// The window will be made active and steal keyboard focus from the current focused window.
823    pub fn focus(&self, window_id: impl Into<WindowId>) {
824        self.focus_impl(window_id.into());
825    }
826    fn focus_impl(&self, window_id: WindowId) {
827        UPDATES.once_update("WINDOWS.focus", move || {
828            if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
829                && let Some(root) = &w.root
830                && let Some(v) = &root.view_window
831            {
832                if !RAW_WINDOW_FOCUS_EVENT.with(|a| matches!(a.latest(), Some(w) if w.new_focus == Some(window_id))) {
833                    let _ = v.focus();
834                } else {
835                    // multiple repeated focus requests have weird effects in Windows,
836                    // it may even return focus to previous window
837                    tracing::debug!("skipping focus window request, already focused");
838                }
839            } else {
840                tracing::error!("cannot focus {window_id}, not open in view-process");
841            }
842        });
843    }
844}
845
846/// Windows extensions hooks.
847#[expect(non_camel_case_types)]
848pub struct WINDOWS_EXTENSIONS;
849
850/// Arguments for [`WINDOWS_EXTENSIONS.register_root_extender`].
851///
852/// [`WINDOWS_EXTENSIONS.register_root_extender`]: WINDOWS_EXTENSIONS::register_root_extender
853#[non_exhaustive]
854pub struct WindowRootExtenderArgs {
855    /// The window root content, extender must wrap this node with extension nodes or return
856    /// it for no-op.
857    pub root: UiNode,
858}
859impl WINDOWS_EXTENSIONS {
860    /// Register the closure `extender` to be called with the root of every new window starting on the next update.
861    ///
862    /// The closure returns the new root node that will be passed to any other root extender until
863    /// the actual final root node is created. The closure is called in the [`WINDOW`] context of the new window,
864    /// so it can be used to modify the window context too.
865    ///
866    /// This is an advanced API that enables app wide features, like themes, to inject context in every new window. The
867    /// extender is called in the context of the window, after the window creation future has completed.
868    ///
869    /// Note that the *root* node passed to the extender is the child node of the `WindowRoot` widget, not the widget itself.
870    /// The extended root will be wrapped in the root widget node, that is, the final root widget will be
871    /// `root(extender_nodes(CONTEXT(EVENT(..))))`, so extension nodes should operate as `CONTEXT` properties.
872    ///
873    /// Note that for themes the `zng-wgt-window` crate provides a `register_style_fn` API that is built over this
874    /// method and more oriented for theming.
875    pub fn register_root_extender(&self, extender: impl FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static) {
876        self.register_root_extender_impl(Box::new(extender));
877    }
878    fn register_root_extender_impl(&self, extender: Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>) {
879        UPDATES.once_update("WINDOWS.register_root_extender", move || {
880            WINDOWS_SV.write().root_extenders.get_mut().push(extender);
881        });
882    }
883
884    /// Register the closure `handler` to be called for every new window starting on the next update.
885    ///
886    /// The closure is called in the new [`WINDOW`] context and can optionally call [`OpenNestedHandlerArgs::nest`] to convert to a nested window.
887    /// Nested windows can be manipulated using the `WINDOWS` API just like other windows, but are layout and rendered inside another window.
888    ///
889    /// This is primarily an adapter for mobile platforms that only support one real window, it accelerates cross platform support from
890    /// projects originally desktop only.
891    ///
892    /// Note that this API is not recommended for implementing features such as *window docking* or
893    /// *tabbing*, for that you probably need to model *tabs* as objects that can outlive their host windows and use [`ArcNode`]
894    /// to transfer the content between host windows.
895    ///
896    /// [`NestedWindowNode`]: crate::NestedWindowNode
897    /// [`ArcNode`]: zng_app::widget::node::ArcNode
898    pub fn register_open_nested_handler(&self, handler: impl FnMut(&mut OpenNestedHandlerArgs) + Send + 'static) {
899        self.register_open_nested_handler_impl(Box::new(handler));
900    }
901    fn register_open_nested_handler_impl(&self, handler: Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>) {
902        UPDATES.once_update("WINDOWS.register_open_nested_handler", move || {
903            WINDOWS_SV.write().open_nested_handlers.get_mut().push(handler);
904        });
905    }
906
907    /// Add a view-process extension payload to the window request for the view-process.
908    ///
909    /// This will only work if called on the first [`UiNode::init`] and at most the first [`UiNode::layout`] of the window.
910    ///
911    /// The payload is dropped after it is send, this method must be called again on [`VIEW_PROCESS_INITED_EVENT`]
912    /// to reinitialize the extensions after view-process respawn.
913    ///
914    /// [`UiNode::init`]: zng_app::widget::node::UiNode::init
915    /// [`UiNode::layout`]: zng_app::widget::node::UiNode::layout
916    /// [`VIEW_PROCESS_INITED_EVENT`]: zng_app::view_process::VIEW_PROCESS_INITED_EVENT
917    pub fn view_extensions_init(
918        &self,
919        window_id: impl Into<WindowId>,
920        extension_id: ApiExtensionId,
921        request: ApiExtensionPayload,
922    ) -> Result<(), ViewExtensionError> {
923        self.view_extensions_init_impl(window_id.into(), extension_id, request)
924    }
925    fn view_extensions_init_impl(
926        &self,
927        window_id: WindowId,
928        extension_id: ApiExtensionId,
929        request: ApiExtensionPayload,
930    ) -> Result<(), ViewExtensionError> {
931        match WINDOWS_SV.write().windows.get_mut(&window_id) {
932            Some(w) => {
933                if matches!(w.mode, WindowMode::HeadlessWithRenderer) {
934                    Err(ViewExtensionError::NotOpenInViewProcess(window_id))
935                } else if let Some(exts) = &mut w.extensions_init {
936                    exts.push((extension_id, request));
937                    Ok(())
938                } else {
939                    Err(ViewExtensionError::AlreadyOpenInViewProcess(window_id))
940                }
941            }
942            None => Err(ViewExtensionError::WindowNotFound(window_id)),
943        }
944    }
945    pub(crate) fn take_view_extensions_init(&self, window_id: WindowId) -> Vec<(ApiExtensionId, ApiExtensionPayload)> {
946        WINDOWS_SV
947            .write()
948            .windows
949            .get_mut(&window_id)
950            .and_then(|w| w.extensions_init.take())
951            .unwrap_or_default()
952    }
953
954    /// Call a view-process headed window extension with custom encoded payload.
955    ///
956    /// Note that unlike most service methods this calls happens immediately.
957    pub fn view_window_extension_raw(
958        &self,
959        window_id: impl Into<WindowId>,
960        extension_id: ApiExtensionId,
961        request: ApiExtensionPayload,
962    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
963        self.view_window_extension_raw_impl(window_id.into(), extension_id, request)
964    }
965    fn view_window_extension_raw_impl(
966        &self,
967        window_id: WindowId,
968        extension_id: ApiExtensionId,
969        request: ApiExtensionPayload,
970    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
971        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
972            if let Some(r) = &w.root
973                && let Some(v) = &r.view_window
974            {
975                match v.window_extension_raw(extension_id, request) {
976                    Ok(r) => Ok(r),
977                    Err(_) => Err(ViewExtensionError::Disconnected),
978                }
979            } else {
980                Err(ViewExtensionError::NotOpenInViewProcess(window_id))
981            }
982        } else {
983            Err(ViewExtensionError::WindowNotFound(window_id))
984        }
985    }
986
987    /// Call a headed window extension with serialized payload.
988    ///
989    /// Note that unlike most service methods this call happens immediately.
990    pub fn view_window_extension<I, O>(
991        &self,
992        window_id: impl Into<WindowId>,
993        extension_id: ApiExtensionId,
994        request: &I,
995    ) -> Result<O, ViewExtensionError>
996    where
997        I: serde::Serialize,
998        O: serde::de::DeserializeOwned,
999    {
1000        let window_id = window_id.into();
1001        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1002            if let Some(r) = &w.root
1003                && let Some(v) = &r.view_window
1004            {
1005                match v.window_extension(extension_id, request) {
1006                    Ok(r) => match r {
1007                        Ok(r) => Ok(r),
1008                        Err(e) => Err(ViewExtensionError::Api(e)),
1009                    },
1010                    Err(_) => Err(ViewExtensionError::Disconnected),
1011                }
1012            } else {
1013                Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1014            }
1015        } else {
1016            Err(ViewExtensionError::WindowNotFound(window_id))
1017        }
1018    }
1019
1020    /// Call a view-process render extension with custom encoded payload for the renderer associated with the window.
1021    ///
1022    /// Note that unlike most service methods this call happens immediately.
1023    pub fn view_render_extension_raw(
1024        &self,
1025        window_id: impl Into<WindowId>,
1026        extension_id: ApiExtensionId,
1027        request: ApiExtensionPayload,
1028    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1029        self.view_render_extension_raw_impl(window_id.into(), extension_id, request)
1030    }
1031    fn view_render_extension_raw_impl(
1032        &self,
1033        window_id: WindowId,
1034        extension_id: ApiExtensionId,
1035        request: ApiExtensionPayload,
1036    ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1037        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1038            if let Some(r) = &w.root
1039                && let Some(v) = &r.renderer
1040            {
1041                match v.render_extension_raw(extension_id, request) {
1042                    Ok(r) => Ok(r),
1043                    Err(_) => Err(ViewExtensionError::Disconnected),
1044                }
1045            } else {
1046                Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1047            }
1048        } else {
1049            Err(ViewExtensionError::WindowNotFound(window_id))
1050        }
1051    }
1052
1053    /// Call a render extension with serialized payload for the renderer associated with the window.
1054    ///
1055    /// Note that unlike most service methods this call happens immediately.
1056    pub fn view_render_extension<I, O>(
1057        &self,
1058        window_id: impl Into<WindowId>,
1059        extension_id: ApiExtensionId,
1060        request: &I,
1061    ) -> Result<O, ViewExtensionError>
1062    where
1063        I: serde::Serialize,
1064        O: serde::de::DeserializeOwned,
1065    {
1066        let window_id = window_id.into();
1067        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1068            if let Some(r) = &w.root
1069                && let Some(v) = &r.renderer
1070            {
1071                match v.render_extension(extension_id, request) {
1072                    Ok(r) => match r {
1073                        Ok(r) => Ok(r),
1074                        Err(e) => Err(ViewExtensionError::Api(e)),
1075                    },
1076                    Err(_) => Err(ViewExtensionError::Disconnected),
1077                }
1078            } else {
1079                Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1080            }
1081        } else {
1082            Err(ViewExtensionError::WindowNotFound(window_id))
1083        }
1084    }
1085}
1086
1087/// Windows dialog service integration.
1088#[expect(non_camel_case_types)]
1089pub struct WINDOWS_DIALOG;
1090
1091impl WINDOWS_DIALOG {
1092    /// Show a native message dialog for the window.
1093    ///
1094    /// The dialog can be modal in the view-process, in the app-process it is always async, the
1095    /// response var will update once when the user responds to the dialog.
1096    ///
1097    /// Consider using the `DIALOG` service instead of the method directly.
1098    pub fn native_message_dialog(
1099        &self,
1100        window_id: impl Into<WindowId>,
1101        dialog: zng_view_api::dialog::MsgDialog,
1102    ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1103        self.native_message_dialog_impl(window_id.into(), dialog)
1104    }
1105    fn native_message_dialog_impl(
1106        &self,
1107        window_id: WindowId,
1108        dialog: zng_view_api::dialog::MsgDialog,
1109    ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1110        let (r, rsp) = response_var();
1111
1112        UPDATES.once_update("WINDOWS.native_message_dialog", move || {
1113            use zng_view_api::dialog::MsgDialogResponse;
1114            if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1115                && let Some(root) = &w.root
1116                && let Some(v) = &root.view_window
1117            {
1118                if let Err(e) = v.message_dialog(dialog, r.clone()) {
1119                    r.respond(MsgDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1120                }
1121            } else {
1122                r.respond(MsgDialogResponse::Error(formatx!(
1123                    "cannot show dialog, {window_id} not open in view-process"
1124                )));
1125            }
1126        });
1127
1128        rsp
1129    }
1130
1131    /// Show a native file dialog for the window.
1132    ///
1133    /// The dialog can be modal in the view-process, in the app-process it is always async, the
1134    /// response var will update once when the user responds to the dialog.
1135    ///
1136    /// Consider using the `DIALOG` service instead of the method directly.
1137    pub fn native_file_dialog(
1138        &self,
1139        window_id: impl Into<WindowId>,
1140        dialog: zng_view_api::dialog::FileDialog,
1141    ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1142        self.native_file_dialog_impl(window_id.into(), dialog)
1143    }
1144    fn native_file_dialog_impl(
1145        &self,
1146        window_id: WindowId,
1147        dialog: zng_view_api::dialog::FileDialog,
1148    ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1149        let (r, rsp) = response_var();
1150
1151        UPDATES.once_update("WINDOWS.native_file_dialog", move || {
1152            use zng_view_api::dialog::FileDialogResponse;
1153            if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1154                && let Some(root) = &w.root
1155                && let Some(v) = &root.view_window
1156            {
1157                if let Err(e) = v.file_dialog(dialog, r.clone()) {
1158                    r.respond(FileDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1159                }
1160            } else {
1161                r.respond(FileDialogResponse::Error(formatx!(
1162                    "cannot show dialog, {window_id} not open in view-process"
1163                )));
1164            }
1165        });
1166
1167        rsp
1168    }
1169
1170    /// Window operations supported by the current view-process instance for headed windows.
1171    ///
1172    /// Not all window operations may be available, depending on the operating system and build. When an operation
1173    /// is not available an error is logged and otherwise ignored.
1174    pub fn available_operations(&self) -> WindowCapability {
1175        VIEW_PROCESS.info().window
1176    }
1177}
1178
1179/// Arguments for the [`WINDOWS_EXTENSIONS.register_open_nested_handler`] handler.
1180///
1181/// [`WINDOWS_EXTENSIONS.register_open_nested_handler`]: WINDOWS_EXTENSIONS::register_open_nested_handler
1182pub struct OpenNestedHandlerArgs {
1183    pub(crate) has_nested: bool,
1184}
1185impl OpenNestedHandlerArgs {
1186    pub(crate) fn new() -> Self {
1187        Self { has_nested: true }
1188    }
1189
1190    /// Instantiate a node that layouts and renders the window content.
1191    ///
1192    /// Calling this will stop the normal window chrome from opening, the caller is responsible for inserting the node into the
1193    /// main window layout.
1194    ///
1195    /// Note that the window will notify *open* like normal, but it will only be visible on this node.
1196    pub fn nest(&mut self) -> NestedWindowNode {
1197        NestedWindowNode::new(WINDOW.id())
1198    }
1199}
1200
1201/// Raw drag&drop API.
1202#[allow(non_camel_case_types)]
1203pub struct WINDOWS_DRAG_DROP;
1204impl WINDOWS_DRAG_DROP {
1205    /// Start of drag&drop from the window.
1206    ///
1207    /// Note that unlike normal service methods this applies immediately.
1208    pub fn start_drag_drop(
1209        &self,
1210        window_id: WindowId,
1211        data: Vec<DragDropData>,
1212        allowed_effects: DragDropEffect,
1213    ) -> Result<DragDropId, DragDropError> {
1214        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1215            && let Some(root) = &w.root
1216            && let Some(v) = &root.view_window
1217        {
1218            v.start_drag_drop(data, allowed_effects)
1219                .map_err(|e| DragDropError::CannotStart(e.to_txt()))?
1220        } else {
1221            Err(DragDropError::CannotStart(formatx!("window {window_id} not open in view-process")))
1222        }
1223    }
1224
1225    /// Notify the drag source of what effect was applied for a received drag&drop.
1226    ///
1227    /// Note that unlike normal service methods this applies immediately.
1228    pub fn drag_dropped(&self, window_id: WindowId, drop_id: DragDropId, applied: DragDropEffect) {
1229        if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1230            && let Some(root) = &w.root
1231            && let Some(v) = &root.view_window
1232        {
1233            let _ = v.drag_dropped(drop_id, applied);
1234        }
1235    }
1236}
1237
1238#[cfg(feature = "image")]
1239impl zng_ext_image::ImageRenderWindowsService for WINDOWS {
1240    fn clone_boxed(&self) -> Box<dyn zng_ext_image::ImageRenderWindowsService> {
1241        Box::new(WINDOWS)
1242    }
1243
1244    fn new_window_root(&self, node: UiNode, render_mode: RenderMode) -> Box<dyn zng_ext_image::ImageRenderWindowRoot> {
1245        Box::new(WindowRoot::new_container(
1246            WidgetId::new_unique(),
1247            crate::StartPosition::Default,
1248            false,
1249            true,
1250            Some(render_mode),
1251            crate::HeadlessMonitor::default(),
1252            false,
1253            node,
1254        ))
1255    }
1256
1257    fn set_parent_in_window_context(&self, parent_id: WindowId) {
1258        use crate::WINDOW_Ext as _;
1259
1260        WINDOW.vars().0.parent.set(parent_id);
1261    }
1262
1263    fn enable_frame_capture_in_window_context(&self, mask: Option<zng_ext_image::ImageMaskMode>) {
1264        use crate::WINDOW_Ext as _;
1265
1266        let mode = if let Some(mask) = mask {
1267            crate::FrameCaptureMode::AllMask(mask)
1268        } else {
1269            crate::FrameCaptureMode::All
1270        };
1271        WINDOW.vars().0.frame_capture_mode.set(mode);
1272    }
1273
1274    fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn zng_ext_image::ImageRenderWindowRoot> + Send>) {
1275        WINDOWS.open_headless(
1276            WindowId::new_unique(),
1277            async move {
1278                use crate::WINDOW_Ext as _;
1279
1280                let root: Box<dyn std::any::Any> = new_window_root();
1281                let w = *root.downcast::<WindowRoot>().expect("expected `WindowRoot` in image render window");
1282                let vars = WINDOW.vars();
1283                vars.auto_size().set(true);
1284                vars.min_size().set(zng_layout::unit::Length::Px(zng_layout::unit::Px(1)));
1285                w
1286            },
1287            true,
1288        );
1289    }
1290
1291    fn close_window(&self, window_id: WindowId) {
1292        WINDOWS.close(window_id);
1293    }
1294}
1295#[cfg(feature = "image")]
1296impl zng_ext_image::ImageRenderWindowRoot for WindowRoot {}