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