Skip to main content

zng_ext_window/
window.rs

1use std::{any::Any, mem, pin::Pin, sync::Arc};
2
3use parking_lot::Mutex;
4use zng_app::{
5    APP, Deadline, async_hn_once,
6    render::{FrameBuilder, FrameUpdate},
7    static_id,
8    timer::{DeadlineHandle, TIMERS},
9    update::{LayoutUpdates, RenderUpdates, UPDATES},
10    view_process::{
11        VIEW_PROCESS, ViewHeadless, ViewRenderer, ViewWindow,
12        raw_events::{
13            RAW_COLORS_CONFIG_CHANGED_EVENT, RAW_HEADLESS_OPEN_EVENT, RAW_MONITORS_CHANGED_EVENT, RAW_WINDOW_FOCUS_EVENT,
14            RAW_WINDOW_OPEN_EVENT, RawWindowFocusArgs,
15        },
16    },
17    widget::{VarLayout as _, WIDGET, WidgetCtx, base::PARALLEL_VAR, info::WidgetInfoTree},
18    window::{MonitorId, WINDOW, WindowCtx, WindowId, WindowMode},
19};
20use zng_app_context::LocalContext;
21use zng_color::{COLOR_SCHEME_VAR, Rgba, colors::ACCENT_COLOR_VAR};
22use zng_layout::unit::{DipSize, TimeUnits as _};
23use zng_layout::{
24    context::LayoutPassId,
25    unit::{
26        Dip, DipPoint, DipToPx as _, FactorUnits as _, Layout2d as _, Length, Px, PxConstraints, PxConstraints2d, PxPoint, PxRect, PxSize,
27        PxToDip as _, PxVector,
28    },
29};
30use zng_state_map::StateId;
31use zng_txt::Txt;
32use zng_unique_id::IdSet;
33use zng_var::{ResponderVar, ResponseVar, VarHandle};
34use zng_view_api::{
35    ViewProcessGen,
36    api_extension::{ApiExtensionId, ApiExtensionPayload},
37    config::{ColorsConfig, FontAntiAliasing},
38    window::{FrameId, FrameRequest, FrameUpdateRequest, FrameWaitId, HeadlessRequest, WindowCapability, WindowRequest, WindowState},
39};
40use zng_wgt::{
41    node::with_context_var,
42    prelude::{DIRECTION_VAR, LAYOUT, LayoutMetrics, UiNode, UiNodeImpl, WidgetInfo, WidgetInfoBuilder, WidgetLayout},
43};
44
45use crate::{
46    AutoSize, CloseWindowResult, MONITORS, OpenNestedHandlerArgs, StartPosition, WINDOW_CLOSE_EVENT, WINDOW_LOAD_EVENT, WINDOW_OPEN_EVENT,
47    WINDOWS, WINDOWS_EXTENSIONS, WINDOWS_SV, WidgetInfoImeArea as _, WidgetUpdateArgs, WindowCloseArgs, WindowInstanceState,
48    WindowLoadingHandle, WindowOpenArgs, WindowRoot, WindowRootExtenderArgs, WindowVars,
49};
50
51/// Extensions methods for [`WINDOW`] contexts of windows open by [`WINDOWS`].
52///
53/// [`WINDOW`]: zng_app::window::WINDOW
54#[expect(non_camel_case_types)]
55pub trait WINDOW_Ext {
56    /// Clone a reference to the variables that get and set window properties.
57    fn vars(&self) -> WindowVars {
58        WindowVars::req()
59    }
60
61    /// Enable accessibility info.
62    ///
63    /// If access is not already enabled, enables it in the app-process only.
64    fn enable_access(&self) {
65        let vars = WINDOW.vars();
66        let access_enabled = &vars.0.access_enabled;
67        if access_enabled.get().is_disabled() {
68            access_enabled.modify(|e| **e |= zng_app::widget::info::access::AccessEnabled::APP);
69        }
70    }
71
72    /// Gets a handle that stops the window from loading while the handle is alive.
73    ///
74    /// A window is only opened in the view-process after it is loaded, without any loading handles the window is considered loaded
75    /// after the first layout pass. Nodes in the window can request a loading handle to delay the view opening to after all async resources
76    /// it requires are loaded.
77    ///
78    /// Note that a window is only loaded after all handles are dropped or expired, you should set a reasonable `deadline`,  
79    /// it is best to partially render a window after a short time than not show anything.
80    ///
81    /// Returns `None` if the window has already loaded.
82    fn loading_handle(&self, deadline: impl Into<Deadline>, debug_name: impl Into<Txt>) -> Option<WindowLoadingHandle> {
83        WINDOWS.loading_handle(WINDOW.id(), deadline, debug_name)
84    }
85
86    /// Generate an image from the current rendered frame of the window.
87    ///
88    /// The image is not loaded at the moment of return, it will update when it is loaded.
89    #[cfg(feature = "image")]
90    fn frame_image(&self, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
91        WINDOWS.frame_image(WINDOW.id(), mask)
92    }
93
94    /// Generate an image from a selection of the current rendered frame of the window.
95    ///
96    /// The image is not loaded at the moment of return, it will update when it is loaded.
97    ///
98    /// If the window is not found the error is reported in the image error.
99    #[cfg(feature = "image")]
100    fn frame_image_rect(&self, rect: zng_layout::unit::PxRect, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
101        WINDOWS.frame_image_rect(WINDOW.id(), rect, mask)
102    }
103
104    /// Move the window to the front of the operating system Z stack.
105    ///
106    /// See [`WINDOWS.bring_to_top`] for more details.
107    ///
108    /// [`WINDOWS.bring_to_top`]: WINDOWS::bring_to_top
109    fn bring_to_top(&self) {
110        WINDOWS.bring_to_top(WINDOW.id());
111    }
112
113    /// Starts closing the window, the operation can be canceled by listeners of
114    /// [`WINDOW_CLOSE_REQUESTED_EVENT`]. If the window has children they are closed together.
115    ///
116    /// Returns a response var that will update once with the result of the operation.
117    ///
118    /// See [`WINDOWS.close`] for more details.
119    ///
120    /// [`WINDOWS.close`]: WINDOWS::close
121    /// [`WINDOW_CLOSE_REQUESTED_EVENT`]: crate::WINDOW_CLOSE_REQUESTED_EVENT
122    fn close(&self) -> ResponseVar<CloseWindowResult> {
123        WINDOWS.close(WINDOW.id())
124    }
125}
126impl WINDOW_Ext for WINDOW {}
127
128pub(crate) struct WindowInstance {
129    pub(crate) mode: WindowMode,
130    pub(crate) pending_loading: std::sync::Weak<dyn Any + Send + Sync>,
131    pub(crate) vars: Option<WindowVars>,
132    pub(crate) info: Option<WidgetInfoTree>,
133    pub(crate) extensions_init: Vec<(ApiExtensionId, ApiExtensionPayload)>,
134    pub(crate) root: Option<WindowNode>,
135
136    pub(crate) view_window: Option<ViewWindow>,
137    pub(crate) view_headless: Option<ViewHeadless>,
138    pub(crate) renderer: Option<ViewRenderer>,
139
140    pub(crate) view_generation: ViewProcessGen,
141}
142impl WindowInstance {
143    pub(crate) fn new(
144        id: WindowId,
145        mode: WindowMode,
146        new_window: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
147        r: ResponderVar<WindowVars>,
148    ) -> Self {
149        let w = Self {
150            mode,
151            pending_loading: std::sync::Weak::<()>::new(),
152            vars: None,
153            info: None,
154            extensions_init: vec![],
155            root: None,
156            view_window: None,
157            view_headless: None,
158            renderer: None,
159            view_generation: ViewProcessGen::INVALID,
160        };
161        UPDATES
162            .run(async move {
163                // init WINDOW context, vars
164                let primary_scale_factor = match mode {
165                    WindowMode::Headed => MONITORS
166                        .primary_monitor()
167                        .get()
168                        .map(|m| m.scale_factor().get())
169                        .unwrap_or_else(|| 1.fct()),
170                    WindowMode::Headless | WindowMode::HeadlessWithRenderer => 1.fct(),
171                };
172
173                let system_colors = match mode {
174                    WindowMode::Headed => RAW_COLORS_CONFIG_CHANGED_EVENT
175                        .var_latest()
176                        .get()
177                        .map(|a| a.config)
178                        .unwrap_or_default(),
179                    WindowMode::Headless | WindowMode::HeadlessWithRenderer => ColorsConfig::default(),
180                };
181
182                let vars = {
183                    let mut s = WINDOWS_SV.write();
184                    let vars = WindowVars::new(s.default_render_mode.get(), primary_scale_factor, system_colors);
185                    crate::hooks::hook_window_vars_cmds(id, &vars);
186                    r.respond(vars.clone());
187                    s.windows.get_mut(&id).unwrap().vars = Some(vars.clone());
188                    vars
189                };
190                let mut ctx = WindowCtx::new(id, mode);
191                ctx.with_state(|s| s.borrow_mut().set(*crate::WINDOW_VARS_ID, vars.clone()));
192
193                // poll `new_window` with context
194                struct CtxFut {
195                    f: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
196                    ctx: Option<WindowCtx>,
197                }
198                impl Future for CtxFut {
199                    type Output = (WindowRoot, WindowCtx);
200
201                    fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
202                        let s = &mut *self.as_mut();
203                        let r = WINDOW.with_context(s.ctx.as_mut().unwrap(), || s.f.as_mut().poll(cx));
204                        match r {
205                            std::task::Poll::Ready(w) => std::task::Poll::Ready((w, s.ctx.take().unwrap())),
206                            std::task::Poll::Pending => std::task::Poll::Pending,
207                        }
208                    }
209                }
210                let new_window = CtxFut {
211                    f: new_window,
212                    ctx: Some(ctx),
213                };
214                let (mut root, mut win_ctx) = new_window.await;
215
216                win_ctx.set_widget_tree(WidgetInfoTree::wgt(id, root.id));
217
218                // await one update in case requests where made in the new_window
219                zng_task::yield_now().await;
220
221                let mut nested = None;
222
223                // lock in kiosk mode
224                if root.kiosk {
225                    vars.0.chrome.set(false);
226                    let chrome_wk = vars.0.chrome.downgrade();
227                    vars.0
228                        .chrome
229                        .hook(move |a| {
230                            if !a.value() {
231                                tracing::error!("cannot enable chrome in kiosk mode");
232                                if let Some(c) = chrome_wk.upgrade() {
233                                    c.set(false);
234                                }
235                            }
236                            true
237                        })
238                        .perm();
239
240                    if !vars.0.state.get().is_fullscreen() {
241                        let try_exclusive =
242                            !VIEW_PROCESS.is_connected() || VIEW_PROCESS.info().window.contains(WindowCapability::EXCLUSIVE);
243                        if try_exclusive {
244                            vars.0.state.set(WindowState::Exclusive);
245                        } else {
246                            vars.0.state.set(WindowState::Fullscreen);
247                        }
248                        let state_wk = vars.0.state.downgrade();
249                        vars.0
250                            .state
251                            .hook(move |a| {
252                                if !a.value().is_fullscreen() {
253                                    tracing::error!("cannot exit fullscreen in kiosk mode");
254                                    if let Some(s) = state_wk.upgrade() {
255                                        let try_exclusive = !VIEW_PROCESS.is_connected()
256                                            || VIEW_PROCESS.info().window.contains(WindowCapability::EXCLUSIVE);
257                                        if try_exclusive {
258                                            s.set(WindowState::Exclusive);
259                                        } else {
260                                            s.set(WindowState::Fullscreen);
261                                        }
262                                    }
263                                }
264
265                                true
266                            })
267                            .perm();
268                    }
269                }
270
271                // apply root extenders and nest handlers
272                let mut extenders;
273                let mut nest_handlers;
274                {
275                    let mut s = WINDOWS_SV.write();
276                    extenders = mem::take(&mut s.root_extenders).into_inner();
277                    nest_handlers = mem::take(&mut s.open_nested_handlers).into_inner();
278                }
279                WINDOW.with_context(&mut win_ctx, || {
280                    for ext in &mut extenders {
281                        root.child = ext(WindowRootExtenderArgs {
282                            root: mem::replace(&mut root.child, UiNode::nil()),
283                        });
284                    }
285                    // built-in "extenders", set context vars
286                    let child = mem::replace(&mut root.child, UiNode::nil());
287                    let child = with_context_var(child, ACCENT_COLOR_VAR, vars.actual_accent_color());
288                    let child = with_context_var(child, COLOR_SCHEME_VAR, vars.actual_color_scheme());
289                    let child = with_context_var(child, PARALLEL_VAR, vars.parallel());
290                    root.child = child;
291
292                    let mut args = OpenNestedHandlerArgs::new();
293                    for nest in &mut nest_handlers {
294                        nest(&mut args);
295                        if args.has_nested {
296                            nested = Some(NestedData {
297                                pending_layout: None,
298                                pending_render: None,
299                            });
300                            break;
301                        }
302                    }
303                });
304                {
305                    let mut s = WINDOWS_SV.write();
306                    extenders.append(s.root_extenders.get_mut());
307                    *s.root_extenders.get_mut() = extenders;
308                    nest_handlers.append(s.open_nested_handlers.get_mut());
309                    *s.open_nested_handlers.get_mut() = nest_handlers;
310                }
311
312                // init and request first info update
313                let mut root = WindowNode {
314                    win_ctx,
315                    wgt_ctx: WidgetCtx::new(root.id),
316                    root: Mutex::new(root),
317
318                    view_opening: VarHandle::dummy(),
319                    view_spawned: false,
320
321                    nested,
322
323                    layout_pass: LayoutPassId::new(),
324                    frame_id: FrameId::INVALID,
325                    clear_color: Rgba::default(),
326                    frame_wait_id: None,
327                };
328                root.with_root(|n| n.init());
329                UPDATES.update_info_window(id);
330                UPDATES.layout_window(id);
331                WINDOWS_SV.write().windows.get_mut(&id).unwrap().root = Some(root);
332                vars.0.instance_state.set(WindowInstanceState::Loading);
333                WINDOW_OPEN_EVENT.notify(WindowOpenArgs::now(id));
334
335                // will continue in WindowsService::update_info, called by app loop
336            })
337            .perm();
338        w
339    }
340}
341
342pub(crate) struct WindowNode {
343    pub(crate) win_ctx: WindowCtx,
344    pub(crate) wgt_ctx: WidgetCtx,
345    // Mutex for Sync only
346    pub(crate) root: Mutex<WindowRoot>,
347
348    pub(crate) view_opening: VarHandle,
349    // if `view_window/headless` is `None` but this is `true` the view is respawning
350    pub(crate) view_spawned: bool,
351
352    pub(crate) nested: Option<NestedData>,
353
354    pub(crate) layout_pass: LayoutPassId,
355    pub(crate) frame_id: FrameId,
356    pub(crate) clear_color: Rgba,
357    pub(crate) frame_wait_id: Option<FrameWaitId>,
358}
359impl WindowNode {
360    pub(crate) fn with_root<R>(&mut self, f: impl FnOnce(&mut UiNode) -> R) -> R {
361        WINDOW.with_context(&mut self.win_ctx, || {
362            WIDGET.with_context(&mut self.wgt_ctx, zng_app::widget::WidgetUpdateMode::Bubble, || {
363                f(&mut self.root.get_mut().child)
364            })
365        })
366    }
367}
368
369pub(crate) struct NestedData {
370    pending_layout: Option<Arc<LayoutUpdates>>,
371    pending_render: Option<[Arc<RenderUpdates>; 2]>,
372}
373
374static_id! {
375    static ref NESTED_WINDOW_INFO_ID: StateId<WindowId>;
376}
377
378/// Extension methods for widget info about a node that hosts a nested window.
379pub trait NestedWindowWidgetInfoExt {
380    /// Gets the hosted window ID if the widget hosts a nested window.
381    fn nested_window(&self) -> Option<WindowId>;
382
383    /// Gets the hosted window info tree if the widget hosts a nested window that is open.
384    fn nested_window_tree(&self) -> Option<WidgetInfoTree> {
385        WINDOWS.widget_tree(self.nested_window()?)
386    }
387}
388
389impl NestedWindowWidgetInfoExt for WidgetInfo {
390    fn nested_window(&self) -> Option<WindowId> {
391        self.meta().get_clone(*NESTED_WINDOW_INFO_ID)
392    }
393}
394
395pub(crate) fn layout_open_view(a: &mut WidgetUpdateArgs, updates: &Arc<LayoutUpdates>) {
396    if !updates.delivery_list().enter_window(a.id) {
397        return;
398    }
399
400    let vars = a.vars.take().unwrap();
401
402    if let Some(n) = &mut a.node.nested {
403        // nested window layout happens in the layout context of the parent window
404        n.pending_layout = Some(updates.clone());
405        if let Some(t) = vars.0.nest_parent.get() {
406            UPDATES.layout(t);
407        }
408        return;
409    }
410
411    // resolve monitor
412    let mut monitor_rect = PxRect::zero();
413    let monitor_density;
414    let mut scale_factor = 1.fct();
415    if a.node.win_ctx.mode().is_headed() {
416        // get real monitor data
417        let monitor = vars.0.actual_monitor.get().and_then(|id| MONITORS.monitor(id));
418
419        // run query if actual_monitor is not set, it will be set by the view-process event
420        let monitor = monitor.unwrap_or_else(|| vars.0.monitor.get().select_fallback(a.id));
421
422        if monitor.id() == MonitorId::fallback() && matches!(vars.0.instance_state.get(), WindowInstanceState::Loading) {
423            // window not open yet and view-process provided no monitor (probably loading)
424            let handle = WINDOWS.loading_handle(a.id, 1.secs(), "monitors");
425            RAW_MONITORS_CHANGED_EVENT
426                .hook(move |_| {
427                    let _hold = &handle;
428                    false
429                })
430                .perm();
431        }
432
433        monitor_rect = monitor.px_rect();
434        monitor_density = monitor.density().get();
435        scale_factor = monitor.scale_factor().get();
436    } else {
437        debug_assert!(a.node.win_ctx.mode().is_headless());
438        // uses test monitor data
439        let m = &a.node.root.get_mut().headless_monitor;
440        if let Some(f) = m.scale_factor {
441            scale_factor = f;
442        }
443        monitor_rect.size = m.size.to_px(scale_factor);
444        monitor_density = m.density;
445    }
446
447    // metrics for layout of actual root values, relative to screen size
448    let font_size_dft = Length::pt_to_px(11.0, scale_factor);
449    let monitor_metrics = || {
450        LayoutMetrics::new(scale_factor, monitor_rect.size, font_size_dft)
451            .with_screen_density(monitor_density)
452            .with_direction(DIRECTION_VAR.get())
453    };
454
455    // valid auto size config
456    let auto_size = if matches!(vars.state().get(), WindowState::Normal) {
457        vars.0.auto_size.get()
458    } else {
459        AutoSize::empty()
460    };
461
462    // layout
463    a.node.layout_pass = a.node.layout_pass.next();
464    let (final_size, min_size, max_size) = LAYOUT.with_root_context(a.node.layout_pass, monitor_metrics(), || {
465        // root font size
466        let font_size = vars.0.font_size.layout_dft_x(font_size_dft);
467        LAYOUT.with_font_size(font_size, || {
468            // root constraints
469            let min_size = vars.0.min_size.layout();
470            let max_size = vars.0.max_size.layout_dft(PxSize::splat(Px::MAX)).max(min_size);
471            let mut size = vars.0.actual_size.get().to_px(scale_factor);
472            if size.is_empty() {
473                // has not open yet, use Normal size for now
474                size = vars
475                    .0
476                    .size
477                    .layout_dft(DipSize::new(Dip::new(800), Dip::new(600)).to_px(scale_factor));
478            }
479
480            let metrics = LAYOUT.metrics();
481            let mut root_cons = metrics.constraints();
482            let mut viewport = size;
483            if auto_size.contains(AutoSize::CONTENT_WIDTH) {
484                root_cons.x = PxConstraints::new_range(min_size.width, max_size.width);
485                viewport.width = monitor_rect.size.width;
486            } else {
487                root_cons.x = PxConstraints::new_exact(size.width);
488            }
489            if auto_size.contains(AutoSize::CONTENT_HEIGHT) {
490                root_cons.y = PxConstraints::new_range(min_size.height, max_size.height);
491                viewport.height = monitor_rect.size.height;
492            } else {
493                root_cons.y = PxConstraints::new_exact(size.height);
494            }
495
496            // layout
497            let metrics = metrics.with_constraints(root_cons).with_viewport(viewport);
498            let desired_size = LAYOUT.with_context(metrics, || {
499                a.node
500                    .with_root(|n| WidgetLayout::with_root_widget(updates.clone(), |wl| n.layout(wl)))
501            });
502
503            // clamp desired_size
504            let mut final_size = size;
505            if auto_size.contains(AutoSize::CONTENT_WIDTH) {
506                final_size.width = desired_size.width.max(min_size.width).min(max_size.width);
507            }
508            if auto_size.contains(AutoSize::CONTENT_HEIGHT) {
509                final_size.height = desired_size.height.max(min_size.height).min(max_size.height);
510            }
511
512            (final_size, min_size, max_size)
513        })
514    });
515
516    if a.node.wgt_ctx.is_pending_reinit() {
517        a.node.with_root(|_| WIDGET.update());
518    }
519
520    let size_dip = final_size.to_dip(scale_factor);
521    if !auto_size.is_empty() {
522        // on view resize will skip layout again because size matches
523        vars.0.actual_size.set(size_dip);
524    }
525    let min_size_dip = min_size.to_dip(scale_factor);
526    let max_size_dip = max_size.to_dip(scale_factor);
527    vars.0.actual_min_size.set(min_size_dip);
528    vars.0.actual_max_size.set(max_size_dip);
529
530    // transition to Loaded (without view)
531    if matches!(vars.0.instance_state.get(), WindowInstanceState::Loading) {
532        let mut s = WINDOWS_SV.write();
533        let w = s.windows.get_mut(&a.id).unwrap();
534        if w.pending_loading.strong_count() > 0 {
535            // wait loading handles
536            tracing::debug!(" window {:?} skipping transition to Loaded, active loading handles", a.id);
537            return;
538        }
539        tracing::trace!("window {:?} has Loaded", a.id);
540        w.pending_loading = std::sync::Weak::<()>::new();
541        vars.0.instance_state.set(WindowInstanceState::Loaded { has_view: false });
542        if !a.node.win_ctx.mode().has_renderer() {
543            // will not open in view-process, notify loaded already
544            WINDOW_LOAD_EVENT.notify(WindowOpenArgs::now(a.id));
545        }
546    }
547
548    // transition to Loaded (with view) or update view
549    match a.node.win_ctx.mode() {
550        WindowMode::Headed => {
551            if let Some(view) = &a.view_window {
552                // update view window size if is auto_size
553                let prev_size = vars.0.actual_size.get().to_px(scale_factor);
554                if !auto_size.is_empty() && prev_size != final_size {
555                    let mut s = vars.window_state_all();
556
557                    let prev_center = LAYOUT.with_root_context(
558                        LayoutPassId::new(),
559                        monitor_metrics().with_constraints(PxConstraints2d::new_exact_size(prev_size)),
560                        || vars.0.auto_size_origin.layout(),
561                    );
562                    let new_center = LAYOUT.with_root_context(
563                        LayoutPassId::new(),
564                        monitor_metrics().with_constraints(PxConstraints2d::new_exact_size(final_size)),
565                        || vars.0.auto_size_origin.layout(),
566                    );
567                    let offset = if prev_size.is_empty() {
568                        PxVector::zero()
569                    } else {
570                        prev_center.to_vector() - new_center.to_vector()
571                    };
572                    s.restore_rect.origin += offset.to_dip(scale_factor);
573                    s.restore_rect.size = size_dip;
574                    s.min_size = min_size_dip;
575                    s.max_size = max_size_dip;
576                    vars.0.restore_rect.modify(move |a| {
577                        if a.value().size != size_dip {
578                            a.value_mut().size = size_dip;
579                        }
580                    });
581                    let _ = view.set_state(s);
582                }
583            } else if a.node.view_opening.is_dummy() {
584                // start opening view-process window
585
586                if !VIEW_PROCESS.is_available() || !VIEW_PROCESS.is_connected() {
587                    tracing::debug!("skipping view-process open window {:?}, no view-process connected", a.id);
588                    return;
589                }
590
591                let id = a.node.win_ctx.id();
592
593                a.node.view_opening = RAW_WINDOW_OPEN_EVENT.hook(move |a| {
594                    if a.window_id != id {
595                        return true;
596                    }
597
598                    let mut s = WINDOWS_SV.write();
599                    if let Some(w) = s.windows.get_mut(&id) {
600                        let vars = w.vars.as_ref().unwrap();
601                        vars.0.instance_state.set(WindowInstanceState::Loaded { has_view: true });
602                        WINDOW_LOAD_EVENT.notify(WindowOpenArgs::now(id));
603                        let r = w.root.as_mut().unwrap();
604                        let window = a.window.upgrade().unwrap();
605
606                        if mem::take(&mut r.root.get_mut().start_focused) {
607                            let _ = window.focus();
608                        }
609
610                        w.view_generation = window.generation();
611                        w.renderer = Some(window.renderer());
612                        w.view_window = Some(window);
613                        r.view_opening = VarHandle::dummy();
614                        r.view_spawned = true;
615                        UPDATES.render_window(id);
616
617                        vars.set_from_view(|v| &v.0.state, a.data.state.state);
618                        vars.set_from_view(|v| &v.0.global_position, a.data.state.global_position);
619                        vars.set_from_view(|v| &v.0.restore_rect, a.data.state.restore_rect);
620                        vars.set_from_view(|v| &v.0.restore_state, a.data.state.restore_state);
621                        vars.set_from_view(|v| &v.0.restore_state_fullscreen, a.data.state.restore_state_fullscreen);
622                        vars.set_from_view(|v| &v.0.chrome, a.data.state.chrome_visible);
623
624                        vars.set_from_view(|v| &v.0.actual_monitor, a.data.monitor);
625                        vars.set_from_view(|v| &v.0.actual_position, a.data.position.1);
626                        vars.set_from_view(|v| &v.0.global_position, a.data.position.0);
627                        vars.set_from_view(|v| &v.0.actual_size, a.data.size);
628                        vars.set_from_view(|v| &v.0.scale_factor, a.data.scale_factor);
629                        vars.set_from_view(|v| &v.0.refresh_rate, a.data.refresh_rate);
630                        vars.set_from_view(|v| &v.0.render_mode, a.data.render_mode);
631                        vars.set_from_view(|v| &v.0.safe_padding, a.data.safe_padding);
632
633                        s.set_frame_duration();
634
635                        tracing::trace!(
636                            "open window {:?} in {:?}, {:?}",
637                            id,
638                            a.data.monitor,
639                            (&a.data.state.global_position, a.data.size)
640                        );
641                    }
642
643                    false
644                });
645
646                // Layout initial position in the monitor space.
647                let mut system_pos = false;
648                let mut global_position = PxPoint::zero();
649                let mut position = DipPoint::zero();
650                if a.node.view_spawned {
651                    // is respawning view
652                    global_position = vars.0.global_position.get();
653                    position = vars.0.actual_position.get();
654                } else {
655                    match a.node.root.get_mut().start_position {
656                        StartPosition::Default => {
657                            let pos = vars.0.position.get();
658                            system_pos = pos.x.is_default() || pos.y.is_default();
659                            if !system_pos {
660                                LAYOUT.with_root_context(a.node.layout_pass, monitor_metrics(), || {
661                                    let pos = pos.layout();
662                                    position = pos.to_dip(scale_factor);
663                                    global_position = monitor_rect.origin + pos.to_vector();
664                                });
665                            } else {
666                                // in case system does not implement position
667                                position = DipPoint::splat(Dip::new(60));
668                                global_position = monitor_rect.origin + position.to_px(scale_factor).to_vector();
669                            }
670                        }
671                        start_position => {
672                            let center_rect = match start_position {
673                                StartPosition::CenterMonitor => monitor_rect,
674                                StartPosition::CenterParent => {
675                                    if let Some(parent_id) = vars.0.parent.get()
676                                        && let Some(parent_vars) = WINDOWS.vars(parent_id)
677                                        && matches!(parent_vars.0.instance_state.get(), WindowInstanceState::Loaded { has_view: true })
678                                    {
679                                        PxRect::new(parent_vars.0.global_position.get(), parent_vars.actual_size_px().get())
680                                    } else {
681                                        monitor_rect
682                                    }
683                                }
684                                _ => unreachable!(),
685                            };
686                            let center_point = center_rect.origin + center_rect.size.to_vector() / Px(2);
687
688                            global_position = center_point - final_size.to_vector() / Px(2);
689
690                            // snap title bar inside monitor area
691                            let title_height = Dip::new(30).to_px(scale_factor);
692                            let correction_y = (monitor_rect.origin.y + title_height - global_position.y).max(Px(0));
693                            global_position.y += correction_y;
694
695                            let pos = (global_position - monitor_rect.origin).to_point();
696                            position = pos.to_dip(scale_factor);
697                        }
698                    }
699                }
700
701                let mut state_all = vars.window_state_all();
702                state_all.global_position = global_position;
703                state_all.restore_rect.origin = position;
704                state_all.restore_rect.size = size_dip;
705                state_all.min_size = min_size_dip;
706                state_all.max_size = max_size_dip;
707
708                let mut ime_area = None;
709                {
710                    let s = WINDOWS_SV.read();
711                    s.focused.with(|f| {
712                        if let Some(f) = f
713                            && f.window_id() == id
714                            && let Some(w) = s.windows.get(&id)
715                            && let Some(i) = &w.info
716                            && let Some(i) = i.get(f.widget_id())
717                            && let Some(r) = i.ime_area()
718                        {
719                            ime_area = Some(r.to_dip(scale_factor));
720                        }
721                    });
722                }
723                let mut args = WindowRequest::new(
724                    zng_view_api::window::WindowId::from_raw(id.get()),
725                    vars.0.title.get(),
726                    state_all,
727                    a.node.root.get_mut().kiosk,
728                    system_pos,
729                    vars.0.video_mode.get(),
730                    vars.0.visible.get(),
731                    vars.0.taskbar_visible.get(),
732                    vars.0.always_on_top.get(),
733                    vars.0.movable.get(),
734                    vars.0.resizable.get(),
735                    #[cfg(feature = "image")]
736                    vars.0.actual_icon.with(|w| w.as_ref().map(|w| w.view_handle().image_id())),
737                    #[cfg(not(feature = "image"))]
738                    None,
739                    vars.0.cursor.with(|c| c.icon()),
740                    #[cfg(feature = "image")]
741                    vars.0
742                        .actual_cursor_img
743                        .with(|i| i.as_ref().map(|(i, p)| (i.view_handle().image_id(), *p))),
744                    #[cfg(not(feature = "image"))]
745                    None,
746                    a.node.root.get_mut().transparent,
747                    #[cfg(feature = "image")]
748                    matches!(vars.0.frame_capture_mode.get(), crate::FrameCaptureMode::All),
749                    #[cfg(not(feature = "image"))]
750                    false,
751                    a.node
752                        .root
753                        .get_mut()
754                        .render_mode
755                        .unwrap_or_else(|| WINDOWS.default_render_mode().get()),
756                    vars.0.focus_indicator.get(),
757                    vars.0.focused.get(),
758                    ime_area,
759                    vars.0.enabled_buttons.get(),
760                    vars.0.system_shutdown_warn.get(),
761                    WINDOWS_EXTENSIONS.take_view_extensions_init(id),
762                );
763                args.cache_shaders = a
764                    .node
765                    .root
766                    .get_mut()
767                    .cache_shaders
768                    .unwrap_or_else(|| WINDOWS.default_cache_shaders().get());
769                let r = VIEW_PROCESS.open_window(args);
770                if r.is_err() {
771                    tracing::error!("view-process window {id:?} open request failed, will retry on respawn");
772                    a.node.view_opening = VarHandle::dummy();
773                }
774            }
775        }
776        WindowMode::HeadlessWithRenderer => {
777            if let Some(view) = &a.view_headless {
778                if !auto_size.is_empty() {
779                    let _ = view.set_size(size_dip, scale_factor);
780                }
781            } else if a.node.view_opening.is_dummy() {
782                if APP.window_mode().is_headless() && !vars.0.instance_state.get().is_loaded() {
783                    // simulate focus, for tests mostly
784                    let args = RawWindowFocusArgs::now(WINDOWS_SV.read().focused.with(|p| p.as_ref().map(|p| p.window_id())), Some(a.id));
785                    RAW_WINDOW_FOCUS_EVENT.notify(args);
786                }
787
788                if !VIEW_PROCESS.is_connected() {
789                    tracing::debug!("skipping view-process open headless {:?}, no view-process connected", a.id);
790                    return;
791                }
792
793                // start opening view-process renderer
794                let id = a.node.win_ctx.id();
795                a.node.view_opening = RAW_HEADLESS_OPEN_EVENT.hook(move |a| {
796                    if a.window_id != id {
797                        return true;
798                    }
799
800                    let mut s = WINDOWS_SV.write();
801                    if let Some(w) = s.windows.get_mut(&id) {
802                        w.vars
803                            .as_ref()
804                            .unwrap()
805                            .0
806                            .instance_state
807                            .set(WindowInstanceState::Loaded { has_view: true });
808                        WINDOW_LOAD_EVENT.notify(WindowOpenArgs::now(id));
809
810                        let r = w.root.as_mut().unwrap();
811                        let surface = a.surface.upgrade().unwrap();
812                        w.view_generation = surface.generation();
813                        w.renderer = Some(surface.renderer());
814                        w.view_headless = Some(surface);
815                        r.view_opening = VarHandle::dummy();
816                        r.view_spawned = true;
817
818                        UPDATES.render_window(id);
819                    }
820
821                    false
822                });
823
824                let mut args = HeadlessRequest::new(
825                    zng_view_api::window::WindowId::from_raw(id.get()),
826                    scale_factor,
827                    size_dip,
828                    a.node
829                        .root
830                        .get_mut()
831                        .render_mode
832                        .unwrap_or_else(|| WINDOWS.default_render_mode().get()),
833                    WINDOWS_EXTENSIONS.take_view_extensions_init(id),
834                );
835                args.cache_shaders = a
836                    .node
837                    .root
838                    .get_mut()
839                    .cache_shaders
840                    .unwrap_or_else(|| WINDOWS.default_cache_shaders().get());
841                let r = VIEW_PROCESS.open_headless(args);
842                if r.is_err() {
843                    tracing::error!("view-process headless surface {id:?} open request failed, will retry on respawn");
844                    a.node.view_opening = VarHandle::dummy();
845                }
846            }
847        }
848        WindowMode::Headless => {
849            if APP.window_mode().is_headless() && !vars.0.instance_state.get().is_loaded() {
850                // simulate focus, for tests mostly
851                let args = RawWindowFocusArgs::now(WINDOWS_SV.read().focused.with(|p| p.as_ref().map(|p| p.window_id())), Some(a.id));
852                RAW_WINDOW_FOCUS_EVENT.notify(args);
853            }
854        }
855    }
856}
857
858pub(crate) fn render(a: &mut WidgetUpdateArgs, render_widgets: &Arc<RenderUpdates>, render_update_widgets: &Arc<RenderUpdates>) {
859    if render_widgets.delivery_list().enter_window(a.id)
860        || (a.node.frame_id == FrameId::INVALID && render_update_widgets.delivery_list().enter_window(a.id))
861    {
862        // if is pending render or is first frame and is pending render_update
863
864        if let Some(n) = &mut a.node.nested {
865            // if is nested redirect to parent window
866            n.pending_render = Some([render_widgets.clone(), render_update_widgets.clone()]);
867            if let Some(t) = a.vars.take().unwrap().0.nest_parent.get() {
868                UPDATES.render(t);
869            }
870            return;
871        }
872
873        if matches!(a.node.win_ctx.mode(), WindowMode::Headed | WindowMode::HeadlessWithRenderer) && a.renderer.is_none() {
874            // skip until there is a window.
875            tracing::debug!("skipping render {:?}, no renderer connected", a.id);
876            return;
877        }
878
879        let vars = a.vars.take().unwrap();
880        let info = a.node.win_ctx.widget_tree();
881
882        // render
883        a.node.frame_id = a.node.frame_id.next();
884        let mut frame = FrameBuilder::new(
885            render_widgets.clone(),
886            render_update_widgets.clone(),
887            a.node.frame_id,
888            a.node.wgt_ctx.id(),
889            &a.node.wgt_ctx.bounds(),
890            &info,
891            a.renderer.clone(),
892            vars.0.scale_factor.get(),
893            FontAntiAliasing::Default,
894        );
895        a.node.with_root(|n| {
896            n.render(&mut frame);
897        });
898        let frame = frame.finalize(&info);
899        a.node.clear_color = frame.clear_color;
900
901        let capture = vars.take_frame_capture();
902        let wait_id = a.node.frame_wait_id.take();
903        if let Some(r) = &a.renderer {
904            // send frame to view-process
905            let _ = r.render(FrameRequest::new(
906                a.node.frame_id,
907                a.node.clear_color,
908                frame.display_list,
909                capture,
910                wait_id,
911            ));
912        } else {
913            #[cfg(feature = "image")]
914            {
915                let error = match capture {
916                    zng_view_api::window::FrameCapture::None => "",
917                    zng_view_api::window::FrameCapture::Full => "cannot capture frame, no renderer",
918                    zng_view_api::window::FrameCapture::Mask(_) => "cannot capture frame mask, no renderer",
919                    _ => "",
920                };
921                if !error.is_empty() {
922                    let frame_image = zng_var::VarEq(zng_var::var(zng_ext_image::ImageEntry::new_error(error.into())));
923                    crate::FRAME_IMAGE_READY_EVENT.notify(crate::FrameImageReadyArgs::now(a.id, a.node.frame_id, frame_image.downgrade()));
924                    UPDATES.once_next_update("", move || {
925                        let _hold = &frame_image;
926                    });
927                }
928            }
929        }
930
931        if a.node.wgt_ctx.is_pending_reinit() {
932            a.node.with_root(|_| WIDGET.update());
933        }
934    } else if render_update_widgets.delivery_list().enter_window(a.id) {
935        if let Some(n) = &mut a.node.nested {
936            n.pending_render = Some([render_widgets.clone(), render_update_widgets.clone()]);
937            if let Some(t) = a.vars.take().unwrap().0.nest_parent.get() {
938                UPDATES.render_update(t);
939            }
940            return;
941        }
942
943        // update
944        a.node.frame_id = a.node.frame_id.next_update();
945        let mut update = FrameUpdate::new(
946            render_update_widgets.clone(),
947            a.node.frame_id,
948            a.node.wgt_ctx.id(),
949            a.node.wgt_ctx.bounds(),
950            a.node.clear_color,
951        );
952        a.node.with_root(|n| {
953            n.render_update(&mut update);
954        });
955        let update = update.finalize(&a.node.win_ctx.widget_tree());
956        if let Some(c) = update.clear_color {
957            a.node.clear_color = c;
958        }
959        let vars = a.vars.take().unwrap();
960        let capture = vars.take_frame_capture();
961        let wait_id = a.node.frame_wait_id.take();
962
963        if let Some(r) = &a.renderer {
964            // send updates to view-process
965            let _ = r.render_update(FrameUpdateRequest::new(
966                a.node.frame_id,
967                update.transforms,
968                update.floats,
969                update.colors,
970                update.clear_color,
971                capture,
972                wait_id,
973                update.extensions,
974            ));
975        }
976
977        if a.node.wgt_ctx.is_pending_reinit() {
978            a.node.with_root(|_| WIDGET.update());
979        }
980    }
981}
982
983/// UI node that hosts a nested window as defined by [`WINDOWS_EXTENSIONS.register_open_nested_handler`] handler.
984///
985/// [`WINDOWS_EXTENSIONS.register_open_nested_handler`]: WINDOWS_EXTENSIONS::register_open_nested_handler
986pub struct NestedWindowNode {
987    window_id: WindowId,
988    close_deadline: DeadlineHandle,
989}
990impl NestedWindowNode {
991    pub(crate) fn new(window_id: WindowId) -> Self {
992        Self {
993            window_id,
994            close_deadline: DeadlineHandle::dummy(),
995        }
996    }
997
998    fn take_node(&self) -> Option<WindowNode> {
999        WINDOWS_SV.write().windows.get_mut(&self.window_id)?.root.take()
1000    }
1001
1002    fn restore_node(&self, node: WindowNode) {
1003        if let Some(w) = WINDOWS_SV.write().windows.get_mut(&self.window_id) {
1004            w.root = Some(node);
1005        }
1006    }
1007}
1008// layout and render happens during parent window pass, other updates/info are disconnected
1009impl UiNodeImpl for NestedWindowNode {
1010    fn children_len(&self) -> usize {
1011        0
1012    }
1013    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
1014
1015    fn init(&mut self) {
1016        if let Some(mut n) = self.take_node() {
1017            // net parent and nest vars
1018
1019            let inner_vars = WINDOW.with_context(&mut n.win_ctx, || WINDOW.vars());
1020            let parent_id = WINDOW.id();
1021            let nest_id = WIDGET.id();
1022            inner_vars.0.parent.set(Some(parent_id));
1023            inner_vars.0.nest_parent.set(Some(nest_id));
1024
1025            // parent var allows modify, lock it
1026            let this = inner_vars.0.parent.downgrade();
1027            inner_vars
1028                .0
1029                .parent
1030                .hook(move |a| {
1031                    if *a.value() != Some(parent_id) {
1032                        this.upgrade().unwrap().set(Some(parent_id));
1033                    }
1034                    true
1035                })
1036                .perm();
1037
1038            self.restore_node(n);
1039        }
1040
1041        // cancel close in case of reinit
1042        self.close_deadline = DeadlineHandle::dummy();
1043    }
1044
1045    fn deinit(&mut self) {
1046        // can be a temporary deinit (for reinit), wait 100ms before closing window
1047        let id = self.window_id;
1048        self.close_deadline = TIMERS.on_deadline(
1049            100.ms(),
1050            async_hn_once!(|_| {
1051                let r = WINDOWS.close(id).wait_rsp().await;
1052                if matches!(r, CloseWindowResult::Cancel) {
1053                    // did not close ok, force close
1054                    tracing::error!("nested window {id} already deinited, cannot cancel close");
1055                    let mut s = WINDOWS_SV.write();
1056                    let mut windows = IdSet::new();
1057                    if let Some(mut w) = s.windows.remove(&id) {
1058                        let vars = w.vars.take().unwrap();
1059                        vars.0.instance_state.set(WindowInstanceState::Closed);
1060                        windows.insert(id);
1061
1062                        if let Some(mut r) = w.root.take() {
1063                            r.with_root(|n| n.deinit());
1064                        }
1065                    }
1066                    WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(windows));
1067                }
1068            }),
1069        );
1070    }
1071
1072    fn info(&mut self, info: &mut WidgetInfoBuilder) {
1073        info.set_meta(*NESTED_WINDOW_INFO_ID, self.window_id);
1074    }
1075
1076    fn measure(&mut self, wm: &mut zng_wgt::prelude::WidgetMeasure) -> PxSize {
1077        let mut r = PxSize::zero();
1078        // reset context to app level, only parent layout constraints should affect the nested window
1079        if let Some(mut n) = self.take_node() {
1080            let mut ctx = LocalContext::capture_filtered(zng_app_context::CaptureFilter::app_only());
1081            ctx.with_context(|| {
1082                n.with_root(|n| {
1083                    r = wm.with_widget(|wm| n.measure(wm));
1084                })
1085            });
1086            self.restore_node(n);
1087        }
1088        r
1089    }
1090
1091    fn layout(&mut self, wl: &mut WidgetLayout) -> PxSize {
1092        let mut r = PxSize::zero();
1093        if let Some(mut n) = self.take_node() {
1094            let pending = n.nested.as_mut().unwrap().pending_layout.take();
1095            let mut ctx = LocalContext::capture_filtered(zng_app_context::CaptureFilter::app_only());
1096            ctx.with_context(|| {
1097                n.with_root(|n| {
1098                    r = if let Some(p) = pending {
1099                        wl.with_layout_updates(p, |wl| n.layout(wl))
1100                    } else {
1101                        wl.with_widget(|wl| n.layout(wl))
1102                    };
1103                });
1104            });
1105            self.restore_node(n);
1106        }
1107        r
1108    }
1109
1110    fn render(&mut self, frame: &mut FrameBuilder) {
1111        if let Some(mut n) = self.take_node() {
1112            let [render_widgets, render_update_widgets] = n.nested.as_mut().unwrap().pending_render.take().unwrap_or_default();
1113            let mut ctx = LocalContext::capture_filtered(zng_app_context::CaptureFilter::app_only());
1114            ctx.with_context(|| {
1115                let root_id = n.wgt_ctx.id();
1116                let root_bounds = n.wgt_ctx.bounds();
1117                let info = n.win_ctx.widget_tree();
1118                n.with_root(|n| {
1119                    frame.with_nested_window(
1120                        render_widgets,
1121                        render_update_widgets,
1122                        root_id,
1123                        &root_bounds,
1124                        &info,
1125                        FontAntiAliasing::Default,
1126                        |frame| n.render(frame),
1127                    );
1128                });
1129            });
1130            self.restore_node(n);
1131        }
1132    }
1133
1134    fn render_update(&mut self, update: &mut FrameUpdate) {
1135        if let Some(mut n) = self.take_node() {
1136            let [_, render_update_widgets] = n.nested.as_mut().unwrap().pending_render.take().unwrap_or_default();
1137            let mut ctx = LocalContext::capture_filtered(zng_app_context::CaptureFilter::app_only());
1138            ctx.with_context(|| {
1139                let root_id = n.wgt_ctx.id();
1140                let root_bounds = n.wgt_ctx.bounds();
1141                n.with_root(|n| {
1142                    update.with_nested_window(render_update_widgets, root_id, root_bounds, |update| {
1143                        n.render_update(update);
1144                    });
1145                })
1146            });
1147            self.restore_node(n);
1148        }
1149    }
1150}