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 info tree if called in a window context and the window has already inited.
127    pub fn try_info(&self) -> Option<WidgetInfoTree> {
128        if !WINDOW_CTX.is_default() {
129            return WINDOW_CTX.get().widget_tree.read().clone();
130        }
131        None
132    }
133
134    /// Gets the window ID.
135    pub fn id(&self) -> WindowId {
136        WINDOW_CTX.get().id
137    }
138
139    /// Gets the window mode.
140    pub fn mode(&self) -> WindowMode {
141        WINDOW_CTX.get().mode
142    }
143
144    /// Gets the window info tree.
145    ///
146    /// Panics if called before the window future yields the window.
147    pub fn info(&self) -> WidgetInfoTree {
148        WINDOW_CTX.get().widget_tree.read().clone().expect("window not init")
149    }
150
151    /// Calls `f` with a read lock on the current window state map.
152    pub fn with_state<R>(&self, f: impl FnOnce(StateMapRef<WINDOW>) -> R) -> R {
153        f(WINDOW_CTX.get().state.read().borrow())
154    }
155
156    /// Calls `f` with a write lock on the current window state map.
157    pub fn with_state_mut<R>(&self, f: impl FnOnce(StateMapMut<WINDOW>) -> R) -> R {
158        f(WINDOW_CTX.get().state.write().borrow_mut())
159    }
160
161    /// Get the window state `id`, if it is set.
162    pub fn get_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> Option<T> {
163        let id = id.into();
164        self.with_state(|s| s.get_clone(id))
165    }
166
167    /// Require the window state `id`.
168    ///
169    /// Panics if the `id` is not set.
170    pub fn req_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> T {
171        let id = id.into();
172        self.with_state(|s| s.req(id).clone())
173    }
174
175    /// Set the window state `id` to `value`.
176    ///
177    /// Returns the previous set value.
178    pub fn set_state<T: StateValue>(&self, id: impl Into<StateId<T>>, value: impl Into<T>) -> Option<T> {
179        let id = id.into();
180        let value = value.into();
181        self.with_state_mut(|mut s| s.set(id, value))
182    }
183
184    /// Sets the window state `id` without value.
185    ///
186    /// Returns if the state `id` was already flagged.
187    pub fn flag_state(&self, id: impl Into<StateId<()>>) -> bool {
188        let id = id.into();
189        self.with_state_mut(|mut s| s.flag(id))
190    }
191
192    /// Calls `init` and sets `id` if the `id` is not already set in the widget.
193    pub fn init_state<T: StateValue>(&self, id: impl Into<StateId<T>>, init: impl FnOnce() -> T) {
194        let id = id.into();
195        self.with_state_mut(|mut s| {
196            s.entry(id).or_insert_with(init);
197        });
198    }
199
200    /// Sets the `id` to the default value if it is not already set.
201    pub fn init_state_default<T: StateValue + Default>(&self, id: impl Into<StateId<T>>) {
202        self.init_state(id.into(), Default::default)
203    }
204
205    /// Returns `true` if the `id` is set or flagged in the window.
206    pub fn contains_state<T: StateValue>(&self, id: impl Into<StateId<T>>) -> bool {
207        let id = id.into();
208        self.with_state(|s| s.contains(id))
209    }
210
211    /// Calls `f` while the window is set to `ctx`.
212    #[inline(always)]
213    pub fn with_context<R>(&self, ctx: &mut WindowCtx, f: impl FnOnce() -> R) -> R {
214        fn pre(ctx: &mut WindowCtx) -> tracing::span::EnteredSpan {
215            match ctx.0.as_mut() {
216                Some(c) => UpdatesTrace::window_span(c.id),
217                None => panic!("window is required"),
218            }
219        }
220        let _span = pre(ctx);
221        WINDOW_CTX.with_context(&mut ctx.0, f)
222    }
223
224    /// Calls `f` while no window is available in the context.
225    #[inline(always)]
226    pub fn with_no_context<R>(&self, f: impl FnOnce() -> R) -> R {
227        WINDOW_CTX.with_default(f)
228    }
229}
230
231/// Test only methods.
232#[cfg(any(test, doc, feature = "test_util"))]
233mod _impl {
234    use zng_color::colors;
235    use zng_layout::{
236        context::{InlineConstraints, InlineConstraintsLayout, InlineConstraintsMeasure, LAYOUT, LayoutMetrics},
237        unit::{FactorUnits, Length, Px, PxConstraints2d, PxSize, PxTransform},
238    };
239    use zng_state_map::{StateId, static_id};
240    use zng_view_api::config::FontAntiAliasing;
241
242    use super::*;
243    use crate::{
244        render::FrameValueKey,
245        update::{ContextUpdates, EventUpdate, LayoutUpdates, UPDATES, UpdateDeliveryList, WidgetUpdates},
246        widget::{
247            WIDGET, WIDGET_CTX, WidgetCtx, WidgetId, WidgetUpdateMode,
248            info::{WidgetBorderInfo, WidgetBoundsInfo, WidgetPath},
249            node::UiNode,
250        },
251    };
252    use atomic::Ordering::Relaxed;
253
254    static_id! {
255        static ref TEST_WINDOW_CFG: StateId<TestWindowCfg>;
256    }
257
258    struct TestWindowCfg {
259        size: PxSize,
260    }
261
262    /// Window test helpers.
263    ///
264    /// # Panics
265    ///
266    /// Most of the test methods panic if not called inside [`with_test_context`].
267    ///
268    /// [`with_test_context`]: WINDOW::with_test_context
269    impl WINDOW {
270        /// Calls `f` inside a new headless window and root widget.
271        pub fn with_test_context<R>(&self, update_mode: WidgetUpdateMode, f: impl FnOnce() -> R) -> R {
272            let window_id = WindowId::new_unique();
273            let root_id = WidgetId::new_unique();
274            let mut ctx = WindowCtx::new(window_id, WindowMode::Headless);
275            ctx.set_widget_tree(WidgetInfoTree::wgt(window_id, root_id));
276            WINDOW.with_context(&mut ctx, || {
277                WINDOW.set_state(
278                    *TEST_WINDOW_CFG,
279                    TestWindowCfg {
280                        size: PxSize::new(Px(1132), Px(956)),
281                    },
282                );
283
284                let mut ctx = WidgetCtx::new(root_id);
285                WIDGET.with_context(&mut ctx, update_mode, f)
286            })
287        }
288
289        /// Get the test window size.
290        ///
291        /// This size is used by the `test_*` methods that need a window size.
292        pub fn test_window_size(&self) -> PxSize {
293            WINDOW.with_state_mut(|mut s| s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size)
294        }
295
296        /// Set test window `size`.
297        pub fn set_test_window_size(&self, size: PxSize) {
298            WINDOW.with_state_mut(|mut s| {
299                s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size = size;
300            })
301        }
302
303        /// Call inside [`with_test_context`] to init the `content` as a child of the test window root.
304        ///
305        /// [`with_test_context`]: Self::with_test_context
306        pub fn test_init(&self, content: &mut UiNode) -> ContextUpdates {
307            content.init();
308            WIDGET.test_root_updates();
309            UPDATES.apply()
310        }
311
312        /// Call inside [`with_test_context`] to deinit the `content` as a child of the test window root.
313        ///
314        /// [`with_test_context`]: Self::with_test_context
315        pub fn test_deinit(&self, content: &mut UiNode) -> ContextUpdates {
316            content.deinit();
317            WIDGET.test_root_updates();
318            UPDATES.apply()
319        }
320
321        /// Call inside [`with_test_context`] to rebuild info the `content` as a child of the test window root.
322        ///
323        /// [`with_test_context`]: Self::with_test_context
324        pub fn test_info(&self, content: &mut UiNode) -> ContextUpdates {
325            let l_size = self.test_window_size();
326            let mut info = crate::widget::info::WidgetInfoBuilder::new(
327                Arc::default(),
328                WINDOW.id(),
329                crate::widget::info::access::AccessEnabled::APP,
330                WIDGET.id(),
331                WidgetBoundsInfo::new_size(l_size, l_size),
332                WidgetBorderInfo::new(),
333                1.fct(),
334            );
335            content.info(&mut info);
336            let tree = info.finalize(Some(self.info()), false);
337            *WINDOW_CTX.get().widget_tree.write() = Some(tree);
338            WIDGET.test_root_updates();
339            UPDATES.apply()
340        }
341
342        /// Call inside [`with_test_context`] to delivery an event to the `content` as a child of the test window root.
343        ///
344        /// [`with_test_context`]: Self::with_test_context
345        pub fn test_event(&self, content: &mut UiNode, update: &mut EventUpdate) -> ContextUpdates {
346            update.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
347            content.event(update);
348            WIDGET.test_root_updates();
349            UPDATES.apply()
350        }
351
352        /// Call inside [`with_test_context`] to update the `content` as a child of the test window root.
353        ///
354        /// The `updates` can be set to a custom delivery list, otherwise window root and `content` widget are flagged for update.
355        ///
356        /// [`with_test_context`]: Self::with_test_context
357        pub fn test_update(&self, content: &mut UiNode, updates: Option<&mut WidgetUpdates>) -> ContextUpdates {
358            if let Some(updates) = updates {
359                updates.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
360                content.update(updates)
361            } else {
362                let target = if let Some(mut wgt) = content.as_widget() {
363                    let content_id = wgt.id();
364                    WidgetPath::new(WINDOW.id(), vec![WIDGET.id(), content_id].into())
365                } else {
366                    WidgetPath::new(WINDOW.id(), vec![WIDGET.id()].into())
367                };
368
369                let mut updates = WidgetUpdates::new(UpdateDeliveryList::new_any());
370                updates.delivery_list.insert_wgt(&target);
371
372                content.update(&updates);
373            }
374            WIDGET.test_root_updates();
375            UPDATES.apply()
376        }
377
378        /// Call inside [`with_test_context`] to layout the `content` as a child of the test window root.
379        ///
380        /// [`with_test_context`]: Self::with_test_context
381        pub fn test_layout(&self, content: &mut UiNode, constraints: Option<PxConstraints2d>) -> (PxSize, ContextUpdates) {
382            let font_size = Length::pt_to_px(14.0, 1.0.fct());
383            let viewport = self.test_window_size();
384            let mut metrics = LayoutMetrics::new(1.fct(), viewport, font_size);
385            if let Some(c) = constraints {
386                metrics = metrics.with_constraints(c);
387            }
388            let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
389            updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
390            let size = LAYOUT.with_context(metrics, || {
391                crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
392            });
393            WIDGET.test_root_updates();
394            (size, UPDATES.apply())
395        }
396
397        /// Call inside [`with_test_context`] to layout the `content` as a child of the test window root.
398        ///
399        /// Returns the measure and layout size, and the requested updates.
400        ///
401        /// [`with_test_context`]: Self::with_test_context
402        pub fn test_layout_inline(
403            &self,
404            content: &mut UiNode,
405            measure_constraints: (PxConstraints2d, InlineConstraintsMeasure),
406            layout_constraints: (PxConstraints2d, InlineConstraintsLayout),
407        ) -> ((PxSize, PxSize), ContextUpdates) {
408            let font_size = Length::pt_to_px(14.0, 1.0.fct());
409            let viewport = self.test_window_size();
410
411            let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
412                .with_constraints(measure_constraints.0)
413                .with_inline_constraints(Some(InlineConstraints::Measure(measure_constraints.1)));
414            let measure_size = LAYOUT.with_context(metrics, || {
415                content.measure(&mut crate::widget::info::WidgetMeasure::new(Arc::default()))
416            });
417
418            let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
419                .with_constraints(layout_constraints.0)
420                .with_inline_constraints(Some(InlineConstraints::Layout(layout_constraints.1)));
421
422            let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
423            updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
424
425            let layout_size = LAYOUT.with_context(metrics, || {
426                crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
427            });
428            WIDGET.test_root_updates();
429            ((measure_size, layout_size), UPDATES.apply())
430        }
431
432        /// Call inside [`with_test_context`] to render the `content` as a child of the test window root.
433        ///
434        /// [`with_test_context`]: Self::with_test_context
435        pub fn test_render(&self, content: &mut UiNode) -> (crate::render::BuiltFrame, ContextUpdates) {
436            use crate::render::*;
437
438            let mut frame = {
439                let win = WINDOW_CTX.get();
440                let wgt = WIDGET_CTX.get();
441
442                let frame_id = win.frame_id.load(Relaxed);
443                win.frame_id.store(frame_id.next(), Relaxed);
444
445                FrameBuilder::new_renderless(
446                    Arc::default(),
447                    Arc::default(),
448                    frame_id,
449                    wgt.id,
450                    &wgt.bounds.lock(),
451                    win.widget_tree.read().as_ref().unwrap(),
452                    1.fct(),
453                    FontAntiAliasing::Default,
454                )
455            };
456
457            frame.push_inner(self.test_root_translation_key(), false, |frame| {
458                content.render(frame);
459            });
460
461            let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
462            let f = frame.finalize(&tree);
463            WIDGET.test_root_updates();
464            (f, UPDATES.apply())
465        }
466
467        /// Call inside [`with_test_context`] to render_update the `content` as a child of the test window root.
468        ///
469        /// [`with_test_context`]: Self::with_test_context
470        pub fn test_render_update(&self, content: &mut UiNode) -> (crate::render::BuiltFrameUpdate, ContextUpdates) {
471            use crate::render::*;
472
473            let mut update = {
474                let win = WINDOW_CTX.get();
475                let wgt = WIDGET_CTX.get();
476
477                let frame_id = win.frame_id.load(Relaxed);
478                win.frame_id.store(frame_id.next_update(), Relaxed);
479
480                FrameUpdate::new(Arc::default(), frame_id, wgt.id, wgt.bounds.lock().clone(), colors::BLACK)
481            };
482
483            update.update_inner(self.test_root_translation_key(), false, |update| {
484                content.render_update(update);
485            });
486            let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
487            let f = update.finalize(&tree);
488            WIDGET.test_root_updates();
489            (f, UPDATES.apply())
490        }
491
492        fn test_root_translation_key(&self) -> FrameValueKey<PxTransform> {
493            static_id! {
494                static ref ID: StateId<FrameValueKey<PxTransform>>;
495            }
496            WINDOW.with_state_mut(|mut s| *s.entry(*ID).or_insert_with(FrameValueKey::new_unique))
497        }
498    }
499}
500
501context_local! {
502    static WINDOW_CTX: WindowCtxData = WindowCtxData::no_context();
503}
504
505/// Defines the backing data of [`WINDOW`].
506///
507/// Each window owns this data and calls [`WINDOW.with_context`](WINDOW::with_context) to delegate to it's child node.
508pub struct WindowCtx(Option<Arc<WindowCtxData>>);
509impl WindowCtx {
510    /// New window context.
511    pub fn new(id: WindowId, mode: WindowMode) -> Self {
512        Self(Some(Arc::new(WindowCtxData {
513            id,
514            mode,
515            state: RwLock::new(OwnedStateMap::default()),
516            widget_tree: RwLock::new(None),
517
518            #[cfg(any(test, doc, feature = "test_util"))]
519            frame_id: atomic::Atomic::new(zng_view_api::window::FrameId::first()),
520        })))
521    }
522
523    /// Sets the widget tree, must be called after every info update.
524    ///
525    /// Window contexts are partially available in the window new closure, but values like the `widget_tree` is
526    /// available on init, so a [`WidgetInfoTree::wgt`] must be set as soon as the window and widget ID are available.
527    pub fn set_widget_tree(&mut self, widget_tree: WidgetInfoTree) {
528        *self.0.as_mut().unwrap().widget_tree.write() = Some(widget_tree);
529    }
530
531    /// Gets the window ID.
532    pub fn id(&self) -> WindowId {
533        self.0.as_ref().unwrap().id
534    }
535
536    /// Gets the window mode.
537    pub fn mode(&self) -> WindowMode {
538        self.0.as_ref().unwrap().mode
539    }
540
541    /// Gets the window tree.
542    pub fn widget_tree(&self) -> WidgetInfoTree {
543        self.0.as_ref().unwrap().widget_tree.read().as_ref().unwrap().clone()
544    }
545
546    /// Call `f` with an exclusive lock to the window state.
547    pub fn with_state<R>(&mut self, f: impl FnOnce(&mut OwnedStateMap<WINDOW>) -> R) -> R {
548        f(&mut self.0.as_mut().unwrap().state.write())
549    }
550
551    /// Clone a reference to the window context.
552    ///
553    /// This must be used only if the window implementation is split.
554    pub fn share(&mut self) -> Self {
555        Self(self.0.clone())
556    }
557}
558
559struct WindowCtxData {
560    id: WindowId,
561    mode: WindowMode,
562    state: RwLock<OwnedStateMap<WINDOW>>,
563    widget_tree: RwLock<Option<WidgetInfoTree>>,
564
565    #[cfg(any(test, doc, feature = "test_util"))]
566    frame_id: atomic::Atomic<zng_view_api::window::FrameId>,
567}
568impl WindowCtxData {
569    #[track_caller]
570    fn no_context() -> Self {
571        panic!("no window in context")
572    }
573}
574
575/// Mode of an open window.
576#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
577pub enum WindowMode {
578    /// Normal mode, shows a system window with content rendered.
579    Headed,
580    /// Headless mode, no system window and no renderer. The window does layout and calls [`UiNode::render`], but
581    /// it does not actually generates frame pixels.
582    ///
583    /// [`UiNode::render`]: crate::widget::node::UiNode::render
584    Headless,
585    /// Headless mode, no visible system window but with a renderer. The window does everything a [`Headed`](WindowMode::Headed)
586    /// window does, except presenting the frame in a system window.
587    HeadlessWithRenderer,
588}
589impl fmt::Debug for WindowMode {
590    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591        if f.alternate() {
592            write!(f, "WindowMode::")?;
593        }
594        match self {
595            WindowMode::Headed => write!(f, "Headed"),
596            WindowMode::Headless => write!(f, "Headless"),
597            WindowMode::HeadlessWithRenderer => write!(f, "HeadlessWithRenderer"),
598        }
599    }
600}
601impl WindowMode {
602    /// If it is the [`Headed`](WindowMode::Headed) mode.
603    pub fn is_headed(self) -> bool {
604        match self {
605            WindowMode::Headed => true,
606            WindowMode::Headless | WindowMode::HeadlessWithRenderer => false,
607        }
608    }
609
610    /// If it is the [`Headless`](WindowMode::Headed) or [`HeadlessWithRenderer`](WindowMode::Headed) modes.
611    pub fn is_headless(self) -> bool {
612        match self {
613            WindowMode::Headless | WindowMode::HeadlessWithRenderer => true,
614            WindowMode::Headed => false,
615        }
616    }
617
618    /// If it is the [`Headed`](WindowMode::Headed) or [`HeadlessWithRenderer`](WindowMode::HeadlessWithRenderer) modes.
619    pub fn has_renderer(self) -> bool {
620        match self {
621            WindowMode::Headed | WindowMode::HeadlessWithRenderer => true,
622            WindowMode::Headless => false,
623        }
624    }
625}