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