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