zng_app/
window.rs

1//! Window context API.
2
3use std::{borrow::Cow, fmt, sync::Arc};
4
5use crate::{update::UpdatesTrace, widget::info::WidgetInfoTree};
6use parking_lot::RwLock;
7use zng_app_context::context_local;
8use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateMapRef, StateValue};
9use zng_txt::Txt;
10
11zng_unique_id::unique_id_32! {
12    /// Unique identifier of an open window.
13    ///
14    /// Can be obtained from [`WINDOW.id`] inside a window.
15    ///
16    /// # Name
17    ///
18    /// IDs are only unique for the same process.
19    /// You can associate a [`name`] with an ID to give it a persistent identifier.
20    ///
21    /// [`WINDOW.id`]: crate::window::WINDOW::id
22    /// [`name`]: WindowId::name
23    pub struct WindowId;
24}
25zng_unique_id::impl_unique_id_name!(WindowId);
26zng_unique_id::impl_unique_id_fmt!(WindowId);
27zng_unique_id::impl_unique_id_bytemuck!(WindowId);
28
29zng_var::impl_from_and_into_var! {
30    /// Calls [`WindowId::named`].
31    fn from(name: &'static str) -> WindowId {
32        WindowId::named(name)
33    }
34    /// Calls [`WindowId::named`].
35    fn from(name: String) -> WindowId {
36        WindowId::named(name)
37    }
38    /// Calls [`WindowId::named`].
39    fn from(name: Cow<'static, str>) -> WindowId {
40        WindowId::named(name)
41    }
42    /// Calls [`WindowId::named`].
43    fn from(name: char) -> WindowId {
44        WindowId::named(name)
45    }
46    /// Calls [`WindowId::named`].
47    fn from(name: Txt) -> WindowId {
48        WindowId::named(name)
49    }
50
51    fn from(some: WindowId) -> Option<WindowId>;
52}
53impl serde::Serialize for WindowId {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: serde::Serializer,
57    {
58        let name = self.name();
59        if name.is_empty() {
60            use serde::ser::Error;
61            return Err(S::Error::custom("cannot serialize unnamed `WindowId`"));
62        }
63        name.serialize(serializer)
64    }
65}
66impl<'de> serde::Deserialize<'de> for WindowId {
67    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68    where
69        D: serde::Deserializer<'de>,
70    {
71        let name = Txt::deserialize(deserializer)?;
72        Ok(WindowId::named(name))
73    }
74}
75
76zng_unique_id::unique_id_32! {
77    /// Unique identifier of a monitor screen.
78    pub struct MonitorId;
79}
80zng_unique_id::impl_unique_id_bytemuck!(MonitorId);
81impl fmt::Debug for MonitorId {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        if f.alternate() {
84            f.debug_struct("MonitorId")
85                .field("id", &self.get())
86                .field("sequential", &self.sequential())
87                .finish()
88        } else {
89            write!(f, "MonitorId({})", self.sequential())
90        }
91    }
92}
93impl fmt::Display for MonitorId {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(f, "MonitorId({})", self.sequential())
96    }
97}
98impl MonitorId {
99    /// ID of a fake monitor for cases where no monitor is available.
100    pub fn fallback() -> MonitorId {
101        static FALLBACK: once_cell::sync::Lazy<MonitorId> = once_cell::sync::Lazy::new(MonitorId::new_unique);
102        *FALLBACK
103    }
104}
105
106/// Current context window.
107///
108/// This represents the minimum features required for a window context, see `WINDOW_Ext` for more
109/// features provided by the default window implementation.
110///
111/// # Panics
112///
113/// Most of the methods on this service panic if not called inside a window context.
114pub struct WINDOW;
115impl WINDOW {
116    /// Returns `true` if called inside a window.
117    pub fn is_in_window(&self) -> bool {
118        !WINDOW_CTX.is_default()
119    }
120
121    /// Gets the window ID, if called inside a window.
122    pub fn try_id(&self) -> Option<WindowId> {
123        if WINDOW_CTX.is_default() { None } else { Some(WINDOW_CTX.get().id) }
124    }
125
126    /// Gets the window ID.
127    pub fn id(&self) -> WindowId {
128        WINDOW_CTX.get().id
129    }
130
131    /// Gets the window mode.
132    pub fn mode(&self) -> WindowMode {
133        WINDOW_CTX.get().mode
134    }
135
136    /// Gets the window info tree.
137    ///
138    /// Panics if called before the window future yields the window.
139    pub fn info(&self) -> WidgetInfoTree {
140        WINDOW_CTX.get().widget_tree.read().clone().expect("window not init")
141    }
142
143    /// Calls `f` with a read lock on the current window state map.
144    pub fn with_state<R>(&self, f: impl FnOnce(StateMapRef<WINDOW>) -> R) -> R {
145        f(WINDOW_CTX.get().state.read().borrow())
146    }
147
148    /// Calls `f` with a write lock on the current window state map.
149    pub fn with_state_mut<R>(&self, f: impl FnOnce(StateMapMut<WINDOW>) -> R) -> R {
150        f(WINDOW_CTX.get().state.write().borrow_mut())
151    }
152
153    /// Get the window state `id`, if it is set.
154    pub fn get_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> Option<T> {
155        let id = id.into();
156        self.with_state(|s| s.get_clone(id))
157    }
158
159    /// Require the window state `id`.
160    ///
161    /// Panics if the `id` is not set.
162    pub fn req_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> T {
163        let id = id.into();
164        self.with_state(|s| s.req(id).clone())
165    }
166
167    /// Set the window state `id` to `value`.
168    ///
169    /// Returns the previous set value.
170    pub fn set_state<T: StateValue>(&self, id: impl Into<StateId<T>>, value: impl Into<T>) -> Option<T> {
171        let id = id.into();
172        let value = value.into();
173        self.with_state_mut(|mut s| s.set(id, value))
174    }
175
176    /// Sets the window state `id` without value.
177    ///
178    /// Returns if the state `id` was already flagged.
179    pub fn flag_state(&self, id: impl Into<StateId<()>>) -> bool {
180        let id = id.into();
181        self.with_state_mut(|mut s| s.flag(id))
182    }
183
184    /// Calls `init` and sets `id` if the `id` is not already set in the widget.
185    pub fn init_state<T: StateValue>(&self, id: impl Into<StateId<T>>, init: impl FnOnce() -> T) {
186        let id = id.into();
187        self.with_state_mut(|mut s| {
188            s.entry(id).or_insert_with(init);
189        });
190    }
191
192    /// Sets the `id` to the default value if it is not already set.
193    pub fn init_state_default<T: StateValue + Default>(&self, id: impl Into<StateId<T>>) {
194        self.init_state(id.into(), Default::default)
195    }
196
197    /// Returns `true` if the `id` is set or flagged in the window.
198    pub fn contains_state<T: StateValue>(&self, id: impl Into<StateId<T>>) -> bool {
199        let id = id.into();
200        self.with_state(|s| s.contains(id))
201    }
202
203    /// Calls `f` while the window is set to `ctx`.
204    pub fn with_context<R>(&self, ctx: &mut WindowCtx, f: impl FnOnce() -> R) -> R {
205        let _span = match ctx.0.as_mut() {
206            Some(c) => UpdatesTrace::window_span(c.id),
207            None => panic!("window is required"),
208        };
209        WINDOW_CTX.with_context(&mut ctx.0, f)
210    }
211
212    /// Calls `f` while no window is available in the context.
213    pub fn with_no_context<R>(&self, f: impl FnOnce() -> R) -> R {
214        WINDOW_CTX.with_default(f)
215    }
216}
217
218/// Test only methods.
219#[cfg(any(test, doc, feature = "test_util"))]
220mod _impl {
221    use zng_color::colors;
222    use zng_layout::{
223        context::{InlineConstraints, InlineConstraintsLayout, InlineConstraintsMeasure, LAYOUT, LayoutMetrics},
224        unit::{FactorUnits, Length, Px, PxConstraints2d, PxSize, PxTransform},
225    };
226    use zng_state_map::{StateId, static_id};
227    use zng_view_api::config::FontAntiAliasing;
228
229    use super::*;
230    use crate::{
231        render::FrameValueKey,
232        update::{ContextUpdates, EventUpdate, LayoutUpdates, UPDATES, UpdateDeliveryList, WidgetUpdates},
233        widget::{
234            WIDGET, WIDGET_CTX, WidgetCtx, WidgetId, WidgetUpdateMode,
235            info::{WidgetBorderInfo, WidgetBoundsInfo, WidgetPath},
236            node::UiNode,
237        },
238    };
239    use atomic::Ordering::Relaxed;
240
241    static_id! {
242        static ref TEST_WINDOW_CFG: StateId<TestWindowCfg>;
243    }
244
245    struct TestWindowCfg {
246        size: PxSize,
247    }
248
249    /// Window test helpers.
250    ///
251    /// # Panics
252    ///
253    /// Most of the test methods panic if not called inside [`with_test_context`].
254    ///
255    /// [`with_test_context`]: WINDOW::with_test_context
256    impl WINDOW {
257        /// Calls `f` inside a new headless window and root widget.
258        pub fn with_test_context<R>(&self, update_mode: WidgetUpdateMode, f: impl FnOnce() -> R) -> R {
259            let window_id = WindowId::new_unique();
260            let root_id = WidgetId::new_unique();
261            let mut ctx = WindowCtx::new(window_id, WindowMode::Headless);
262            ctx.set_widget_tree(WidgetInfoTree::wgt(window_id, root_id));
263            WINDOW.with_context(&mut ctx, || {
264                WINDOW.set_state(
265                    *TEST_WINDOW_CFG,
266                    TestWindowCfg {
267                        size: PxSize::new(Px(1132), Px(956)),
268                    },
269                );
270
271                let mut ctx = WidgetCtx::new(root_id);
272                WIDGET.with_context(&mut ctx, update_mode, f)
273            })
274        }
275
276        /// Get the test window size.
277        ///
278        /// This size is used by the `test_*` methods that need a window size.
279        pub fn test_window_size(&self) -> PxSize {
280            WINDOW.with_state_mut(|mut s| s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size)
281        }
282
283        /// Set test window `size`.
284        pub fn set_test_window_size(&self, size: PxSize) {
285            WINDOW.with_state_mut(|mut s| {
286                s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size = size;
287            })
288        }
289
290        /// Call inside [`with_test_context`] to init the `content` as a child of the test window root.
291        ///
292        /// [`with_test_context`]: Self::with_test_context
293        pub fn test_init(&self, content: &mut impl UiNode) -> ContextUpdates {
294            content.init();
295            WIDGET.test_root_updates();
296            UPDATES.apply()
297        }
298
299        /// Call inside [`with_test_context`] to deinit the `content` as a child of the test window root.
300        ///
301        /// [`with_test_context`]: Self::with_test_context
302        pub fn test_deinit(&self, content: &mut impl UiNode) -> ContextUpdates {
303            content.deinit();
304            WIDGET.test_root_updates();
305            UPDATES.apply()
306        }
307
308        /// Call inside [`with_test_context`] to rebuild info the `content` as a child of the test window root.
309        ///
310        /// [`with_test_context`]: Self::with_test_context
311        pub fn test_info(&self, content: &mut impl UiNode) -> ContextUpdates {
312            let l_size = self.test_window_size();
313            let mut info = crate::widget::info::WidgetInfoBuilder::new(
314                Arc::default(),
315                WINDOW.id(),
316                crate::widget::info::access::AccessEnabled::APP,
317                WIDGET.id(),
318                WidgetBoundsInfo::new_size(l_size, l_size),
319                WidgetBorderInfo::new(),
320                1.fct(),
321            );
322            content.info(&mut info);
323            let tree = info.finalize(Some(self.info()), false);
324            *WINDOW_CTX.get().widget_tree.write() = Some(tree);
325            WIDGET.test_root_updates();
326            UPDATES.apply()
327        }
328
329        /// Call inside [`with_test_context`] to delivery an event to the `content` as a child of the test window root.
330        ///
331        /// [`with_test_context`]: Self::with_test_context
332        pub fn test_event(&self, content: &mut impl UiNode, update: &mut EventUpdate) -> ContextUpdates {
333            update.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
334            content.event(update);
335            WIDGET.test_root_updates();
336            UPDATES.apply()
337        }
338
339        /// Call inside [`with_test_context`] to update the `content` as a child of the test window root.
340        ///
341        /// The `updates` can be set to a custom delivery list, otherwise window root and `content` widget are flagged for update.
342        ///
343        /// [`with_test_context`]: Self::with_test_context
344        pub fn test_update(&self, content: &mut impl UiNode, updates: Option<&mut WidgetUpdates>) -> ContextUpdates {
345            if let Some(updates) = updates {
346                updates.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
347                content.update(updates)
348            } else {
349                let target = if let Some(content_id) = content.with_context(WidgetUpdateMode::Ignore, || WIDGET.id()) {
350                    WidgetPath::new(WINDOW.id(), vec![WIDGET.id(), content_id].into())
351                } else {
352                    WidgetPath::new(WINDOW.id(), vec![WIDGET.id()].into())
353                };
354
355                let mut updates = WidgetUpdates::new(UpdateDeliveryList::new_any());
356                updates.delivery_list.insert_wgt(&target);
357
358                content.update(&updates);
359            }
360            WIDGET.test_root_updates();
361            UPDATES.apply()
362        }
363
364        /// Call inside [`with_test_context`] to layout the `content` as a child of the test window root.
365        ///
366        /// [`with_test_context`]: Self::with_test_context
367        pub fn test_layout(&self, content: &mut impl UiNode, constraints: Option<PxConstraints2d>) -> (PxSize, ContextUpdates) {
368            let font_size = Length::pt_to_px(14.0, 1.0.fct());
369            let viewport = self.test_window_size();
370            let mut metrics = LayoutMetrics::new(1.fct(), viewport, font_size);
371            if let Some(c) = constraints {
372                metrics = metrics.with_constraints(c);
373            }
374            let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
375            updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
376            let size = LAYOUT.with_context(metrics, || {
377                crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
378            });
379            WIDGET.test_root_updates();
380            (size, UPDATES.apply())
381        }
382
383        /// Call inside [`with_test_context`] to layout the `content` as a child of the test window root.
384        ///
385        /// Returns the measure and layout size, and the requested updates.
386        ///
387        /// [`with_test_context`]: Self::with_test_context
388        pub fn test_layout_inline(
389            &self,
390            content: &mut impl UiNode,
391            measure_constraints: (PxConstraints2d, InlineConstraintsMeasure),
392            layout_constraints: (PxConstraints2d, InlineConstraintsLayout),
393        ) -> ((PxSize, PxSize), ContextUpdates) {
394            let font_size = Length::pt_to_px(14.0, 1.0.fct());
395            let viewport = self.test_window_size();
396
397            let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
398                .with_constraints(measure_constraints.0)
399                .with_inline_constraints(Some(InlineConstraints::Measure(measure_constraints.1)));
400            let measure_size = LAYOUT.with_context(metrics, || {
401                content.measure(&mut crate::widget::info::WidgetMeasure::new(Arc::default()))
402            });
403
404            let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
405                .with_constraints(layout_constraints.0)
406                .with_inline_constraints(Some(InlineConstraints::Layout(layout_constraints.1)));
407
408            let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
409            updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
410
411            let layout_size = LAYOUT.with_context(metrics, || {
412                crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
413            });
414            WIDGET.test_root_updates();
415            ((measure_size, layout_size), UPDATES.apply())
416        }
417
418        /// Call inside [`with_test_context`] to render the `content` as a child of the test window root.
419        ///
420        /// [`with_test_context`]: Self::with_test_context
421        pub fn test_render(&self, content: &mut impl UiNode) -> (crate::render::BuiltFrame, ContextUpdates) {
422            use crate::render::*;
423
424            let mut frame = {
425                let win = WINDOW_CTX.get();
426                let wgt = WIDGET_CTX.get();
427
428                let frame_id = win.frame_id.load(Relaxed);
429                win.frame_id.store(frame_id.next(), Relaxed);
430
431                let f = FrameBuilder::new_renderless(
432                    Arc::default(),
433                    Arc::default(),
434                    frame_id,
435                    wgt.id,
436                    &wgt.bounds.lock(),
437                    win.widget_tree.read().as_ref().unwrap(),
438                    1.fct(),
439                    FontAntiAliasing::Default,
440                );
441                f
442            };
443
444            frame.push_inner(self.test_root_translation_key(), false, |frame| {
445                content.render(frame);
446            });
447
448            let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
449            let f = frame.finalize(&tree);
450            WIDGET.test_root_updates();
451            (f, UPDATES.apply())
452        }
453
454        /// Call inside [`with_test_context`] to render_update the `content` as a child of the test window root.
455        ///
456        /// [`with_test_context`]: Self::with_test_context
457        pub fn test_render_update(&self, content: &mut impl UiNode) -> (crate::render::BuiltFrameUpdate, ContextUpdates) {
458            use crate::render::*;
459
460            let mut update = {
461                let win = WINDOW_CTX.get();
462                let wgt = WIDGET_CTX.get();
463
464                let frame_id = win.frame_id.load(Relaxed);
465                win.frame_id.store(frame_id.next_update(), Relaxed);
466
467                let f = FrameUpdate::new(Arc::default(), frame_id, wgt.id, wgt.bounds.lock().clone(), colors::BLACK);
468                f
469            };
470
471            update.update_inner(self.test_root_translation_key(), false, |update| {
472                content.render_update(update);
473            });
474            let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
475            let f = update.finalize(&tree);
476            WIDGET.test_root_updates();
477            (f, UPDATES.apply())
478        }
479
480        fn test_root_translation_key(&self) -> FrameValueKey<PxTransform> {
481            static_id! {
482                static ref ID: StateId<FrameValueKey<PxTransform>>;
483            }
484            WINDOW.with_state_mut(|mut s| *s.entry(*ID).or_insert_with(FrameValueKey::new_unique))
485        }
486    }
487}
488
489context_local! {
490    static WINDOW_CTX: WindowCtxData = WindowCtxData::no_context();
491}
492
493/// Defines the backing data of [`WINDOW`].
494///
495/// Each window owns this data and calls [`WINDOW.with_context`](WINDOW::with_context) to delegate to it's child node.
496pub struct WindowCtx(Option<Arc<WindowCtxData>>);
497impl WindowCtx {
498    /// New window context.
499    pub fn new(id: WindowId, mode: WindowMode) -> Self {
500        Self(Some(Arc::new(WindowCtxData {
501            id,
502            mode,
503            state: RwLock::new(OwnedStateMap::default()),
504            widget_tree: RwLock::new(None),
505
506            #[cfg(any(test, doc, feature = "test_util"))]
507            frame_id: atomic::Atomic::new(zng_view_api::window::FrameId::first()),
508        })))
509    }
510
511    /// Sets the widget tree, must be called after every info update.
512    ///
513    /// Window contexts are partially available in the window new closure, but values like the `widget_tree` is
514    /// available on init, so a [`WidgetInfoTree::wgt`] must be set as soon as the window and widget ID are available.
515    pub fn set_widget_tree(&mut self, widget_tree: WidgetInfoTree) {
516        *self.0.as_mut().unwrap().widget_tree.write() = Some(widget_tree);
517    }
518
519    /// Gets the window ID.
520    pub fn id(&self) -> WindowId {
521        self.0.as_ref().unwrap().id
522    }
523
524    /// Gets the window mode.
525    pub fn mode(&self) -> WindowMode {
526        self.0.as_ref().unwrap().mode
527    }
528
529    /// Gets the window tree.
530    pub fn widget_tree(&self) -> WidgetInfoTree {
531        self.0.as_ref().unwrap().widget_tree.read().as_ref().unwrap().clone()
532    }
533
534    /// Call `f` with an exclusive lock to the window state.
535    pub fn with_state<R>(&mut self, f: impl FnOnce(&mut OwnedStateMap<WINDOW>) -> R) -> R {
536        f(&mut self.0.as_mut().unwrap().state.write())
537    }
538
539    /// Clone a reference to the window context.
540    ///
541    /// This must be used only if the window implementation is split.
542    pub fn share(&mut self) -> Self {
543        Self(self.0.clone())
544    }
545}
546
547struct WindowCtxData {
548    id: WindowId,
549    mode: WindowMode,
550    state: RwLock<OwnedStateMap<WINDOW>>,
551    widget_tree: RwLock<Option<WidgetInfoTree>>,
552
553    #[cfg(any(test, doc, feature = "test_util"))]
554    frame_id: atomic::Atomic<zng_view_api::window::FrameId>,
555}
556impl WindowCtxData {
557    #[track_caller]
558    fn no_context() -> Self {
559        panic!("no window in context")
560    }
561}
562
563/// Mode of an open window.
564#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
565pub enum WindowMode {
566    /// Normal mode, shows a system window with content rendered.
567    Headed,
568    /// Headless mode, no system window and no renderer. The window does layout and calls [`UiNode::render`], but
569    /// it does not actually generates frame pixels.
570    ///
571    /// [`UiNode::render`]: crate::widget::node::UiNode::render
572    Headless,
573    /// Headless mode, no visible system window but with a renderer. The window does everything a [`Headed`](WindowMode::Headed)
574    /// window does, except presenting the frame in a system window.
575    HeadlessWithRenderer,
576}
577impl fmt::Debug for WindowMode {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        if f.alternate() {
580            write!(f, "WindowMode::")?;
581        }
582        match self {
583            WindowMode::Headed => write!(f, "Headed"),
584            WindowMode::Headless => write!(f, "Headless"),
585            WindowMode::HeadlessWithRenderer => write!(f, "HeadlessWithRenderer"),
586        }
587    }
588}
589impl WindowMode {
590    /// If it is the [`Headed`](WindowMode::Headed) mode.
591    pub fn is_headed(self) -> bool {
592        match self {
593            WindowMode::Headed => true,
594            WindowMode::Headless | WindowMode::HeadlessWithRenderer => false,
595        }
596    }
597
598    /// If it is the [`Headless`](WindowMode::Headed) or [`HeadlessWithRenderer`](WindowMode::Headed) modes.
599    pub fn is_headless(self) -> bool {
600        match self {
601            WindowMode::Headless | WindowMode::HeadlessWithRenderer => true,
602            WindowMode::Headed => false,
603        }
604    }
605
606    /// If it is the [`Headed`](WindowMode::Headed) or [`HeadlessWithRenderer`](WindowMode::HeadlessWithRenderer) modes.
607    pub fn has_renderer(self) -> bool {
608        match self {
609            WindowMode::Headed | WindowMode::HeadlessWithRenderer => true,
610            WindowMode::Headless => false,
611        }
612    }
613}