Skip to main content

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