1use 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 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 fn from(name: &'static str) -> WindowId {
32 WindowId::named(name)
33 }
34 fn from(name: String) -> WindowId {
36 WindowId::named(name)
37 }
38 fn from(name: Cow<'static, str>) -> WindowId {
40 WindowId::named(name)
41 }
42 fn from(name: char) -> WindowId {
44 WindowId::named(name)
45 }
46 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 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 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
106pub struct WINDOW;
115impl WINDOW {
116 pub fn is_in_window(&self) -> bool {
118 !WINDOW_CTX.is_default()
119 }
120
121 pub fn try_id(&self) -> Option<WindowId> {
123 if WINDOW_CTX.is_default() { None } else { Some(WINDOW_CTX.get().id) }
124 }
125
126 pub fn id(&self) -> WindowId {
128 WINDOW_CTX.get().id
129 }
130
131 pub fn mode(&self) -> WindowMode {
133 WINDOW_CTX.get().mode
134 }
135
136 pub fn info(&self) -> WidgetInfoTree {
140 WINDOW_CTX.get().widget_tree.read().clone().expect("window not init")
141 }
142
143 pub fn with_state<R>(&self, f: impl FnOnce(StateMapRef<WINDOW>) -> R) -> R {
145 f(WINDOW_CTX.get().state.read().borrow())
146 }
147
148 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 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 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 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 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 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 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 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 #[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 #[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#[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 impl WINDOW {
262 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 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 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 pub fn test_init(&self, content: &mut UiNode) -> ContextUpdates {
299 content.init();
300 WIDGET.test_root_updates();
301 UPDATES.apply()
302 }
303
304 pub fn test_deinit(&self, content: &mut UiNode) -> ContextUpdates {
308 content.deinit();
309 WIDGET.test_root_updates();
310 UPDATES.apply()
311 }
312
313 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 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 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 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 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 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 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
497pub struct WindowCtx(Option<Arc<WindowCtxData>>);
501impl WindowCtx {
502 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 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 pub fn id(&self) -> WindowId {
525 self.0.as_ref().unwrap().id
526 }
527
528 pub fn mode(&self) -> WindowMode {
530 self.0.as_ref().unwrap().mode
531 }
532
533 pub fn widget_tree(&self) -> WidgetInfoTree {
535 self.0.as_ref().unwrap().widget_tree.read().as_ref().unwrap().clone()
536 }
537
538 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 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#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
569pub enum WindowMode {
570 Headed,
572 Headless,
577 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 pub fn is_headed(self) -> bool {
596 match self {
597 WindowMode::Headed => true,
598 WindowMode::Headless | WindowMode::HeadlessWithRenderer => false,
599 }
600 }
601
602 pub fn is_headless(self) -> bool {
604 match self {
605 WindowMode::Headless | WindowMode::HeadlessWithRenderer => true,
606 WindowMode::Headed => false,
607 }
608 }
609
610 pub fn has_renderer(self) -> bool {
612 match self {
613 WindowMode::Headed | WindowMode::HeadlessWithRenderer => true,
614 WindowMode::Headless => false,
615 }
616 }
617}