use std::{borrow::Cow, fmt, sync::Arc};
use crate::{update::UpdatesTrace, widget::info::WidgetInfoTree};
use parking_lot::RwLock;
use zng_app_context::context_local;
use zng_state_map::{OwnedStateMap, StateId, StateMapMut, StateMapRef, StateValue};
use zng_txt::Txt;
zng_unique_id::unique_id_32! {
pub struct WindowId;
}
zng_unique_id::impl_unique_id_name!(WindowId);
zng_unique_id::impl_unique_id_fmt!(WindowId);
zng_unique_id::impl_unique_id_bytemuck!(WindowId);
zng_var::impl_from_and_into_var! {
fn from(name: &'static str) -> WindowId {
WindowId::named(name)
}
fn from(name: String) -> WindowId {
WindowId::named(name)
}
fn from(name: Cow<'static, str>) -> WindowId {
WindowId::named(name)
}
fn from(name: char) -> WindowId {
WindowId::named(name)
}
fn from(name: Txt) -> WindowId {
WindowId::named(name)
}
fn from(some: WindowId) -> Option<WindowId>;
}
impl serde::Serialize for WindowId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let name = self.name();
if name.is_empty() {
use serde::ser::Error;
return Err(S::Error::custom("cannot serialize unnamed `WindowId`"));
}
name.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for WindowId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let name = Txt::deserialize(deserializer)?;
Ok(WindowId::named(name))
}
}
zng_unique_id::unique_id_32! {
pub struct MonitorId;
}
zng_unique_id::impl_unique_id_bytemuck!(MonitorId);
impl fmt::Debug for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("MonitorId")
.field("id", &self.get())
.field("sequential", &self.sequential())
.finish()
} else {
write!(f, "MonitorId({})", self.sequential())
}
}
}
impl fmt::Display for MonitorId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MonitorId({})", self.sequential())
}
}
impl MonitorId {
pub fn fallback() -> MonitorId {
static FALLBACK: once_cell::sync::Lazy<MonitorId> = once_cell::sync::Lazy::new(MonitorId::new_unique);
*FALLBACK
}
}
pub struct WINDOW;
impl WINDOW {
pub fn is_in_window(&self) -> bool {
!WINDOW_CTX.is_default()
}
pub fn try_id(&self) -> Option<WindowId> {
if WINDOW_CTX.is_default() {
None
} else {
Some(WINDOW_CTX.get().id)
}
}
pub fn id(&self) -> WindowId {
WINDOW_CTX.get().id
}
pub fn mode(&self) -> WindowMode {
WINDOW_CTX.get().mode
}
pub fn info(&self) -> WidgetInfoTree {
WINDOW_CTX.get().widget_tree.read().clone().expect("window not init")
}
pub fn with_state<R>(&self, f: impl FnOnce(StateMapRef<WINDOW>) -> R) -> R {
f(WINDOW_CTX.get().state.read().borrow())
}
pub fn with_state_mut<R>(&self, f: impl FnOnce(StateMapMut<WINDOW>) -> R) -> R {
f(WINDOW_CTX.get().state.write().borrow_mut())
}
pub fn get_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> Option<T> {
let id = id.into();
self.with_state(|s| s.get_clone(id))
}
pub fn req_state<T: StateValue + Clone>(&self, id: impl Into<StateId<T>>) -> T {
let id = id.into();
self.with_state(|s| s.req(id).clone())
}
pub fn set_state<T: StateValue>(&self, id: impl Into<StateId<T>>, value: impl Into<T>) -> Option<T> {
let id = id.into();
let value = value.into();
self.with_state_mut(|mut s| s.set(id, value))
}
pub fn flag_state(&self, id: impl Into<StateId<()>>) -> bool {
let id = id.into();
self.with_state_mut(|mut s| s.flag(id))
}
pub fn init_state<T: StateValue>(&self, id: impl Into<StateId<T>>, init: impl FnOnce() -> T) {
let id = id.into();
self.with_state_mut(|mut s| {
s.entry(id).or_insert_with(init);
});
}
pub fn init_state_default<T: StateValue + Default>(&self, id: impl Into<StateId<T>>) {
self.init_state(id.into(), Default::default)
}
pub fn contains_state<T: StateValue>(&self, id: impl Into<StateId<T>>) -> bool {
let id = id.into();
self.with_state(|s| s.contains(id))
}
pub fn with_context<R>(&self, ctx: &mut WindowCtx, f: impl FnOnce() -> R) -> R {
let _span = match ctx.0.as_mut() {
Some(c) => UpdatesTrace::window_span(c.id),
None => panic!("window is required"),
};
WINDOW_CTX.with_context(&mut ctx.0, f)
}
pub fn with_no_context<R>(&self, f: impl FnOnce() -> R) -> R {
WINDOW_CTX.with_default(f)
}
}
#[cfg(any(test, doc, feature = "test_util"))]
mod _impl {
use zng_color::colors;
use zng_layout::{
context::{InlineConstraints, InlineConstraintsLayout, InlineConstraintsMeasure, LayoutMetrics, LAYOUT},
unit::{FactorUnits, Length, Px, PxConstraints2d, PxSize, PxTransform},
};
use zng_state_map::{static_id, StateId};
use zng_view_api::config::FontAntiAliasing;
use super::*;
use crate::{
render::FrameValueKey,
update::{ContextUpdates, EventUpdate, LayoutUpdates, UpdateDeliveryList, WidgetUpdates, UPDATES},
widget::{
info::{WidgetBorderInfo, WidgetBoundsInfo, WidgetPath},
node::UiNode,
WidgetCtx, WidgetId, WidgetUpdateMode, WIDGET, WIDGET_CTX,
},
};
use atomic::Ordering::Relaxed;
static_id! {
static ref TEST_WINDOW_CFG: StateId<TestWindowCfg>;
}
struct TestWindowCfg {
size: PxSize,
}
impl WINDOW {
pub fn with_test_context<R>(&self, update_mode: WidgetUpdateMode, f: impl FnOnce() -> R) -> R {
let window_id = WindowId::new_unique();
let root_id = WidgetId::new_unique();
let mut ctx = WindowCtx::new(window_id, WindowMode::Headless);
ctx.set_widget_tree(WidgetInfoTree::wgt(window_id, root_id));
WINDOW.with_context(&mut ctx, || {
WINDOW.set_state(
*TEST_WINDOW_CFG,
TestWindowCfg {
size: PxSize::new(Px(1132), Px(956)),
},
);
let mut ctx = WidgetCtx::new(root_id);
WIDGET.with_context(&mut ctx, update_mode, f)
})
}
pub fn test_window_size(&self) -> PxSize {
WINDOW.with_state_mut(|mut s| s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size)
}
pub fn set_test_window_size(&self, size: PxSize) {
WINDOW.with_state_mut(|mut s| {
s.get_mut(*TEST_WINDOW_CFG).expect("not in test window").size = size;
})
}
pub fn test_init(&self, content: &mut impl UiNode) -> ContextUpdates {
content.init();
WIDGET.test_root_updates();
UPDATES.apply()
}
pub fn test_deinit(&self, content: &mut impl UiNode) -> ContextUpdates {
content.deinit();
WIDGET.test_root_updates();
UPDATES.apply()
}
pub fn test_info(&self, content: &mut impl UiNode) -> ContextUpdates {
let l_size = self.test_window_size();
let mut info = crate::widget::info::WidgetInfoBuilder::new(
Arc::default(),
WINDOW.id(),
crate::widget::info::access::AccessEnabled::APP,
WIDGET.id(),
WidgetBoundsInfo::new_size(l_size, l_size),
WidgetBorderInfo::new(),
1.fct(),
);
content.info(&mut info);
let tree = info.finalize(Some(self.info()), false);
*WINDOW_CTX.get().widget_tree.write() = Some(tree);
WIDGET.test_root_updates();
UPDATES.apply()
}
pub fn test_event(&self, content: &mut impl UiNode, update: &mut EventUpdate) -> ContextUpdates {
update.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
content.event(update);
WIDGET.test_root_updates();
UPDATES.apply()
}
pub fn test_update(&self, content: &mut impl UiNode, updates: Option<&mut WidgetUpdates>) -> ContextUpdates {
if let Some(updates) = updates {
updates.delivery_list_mut().fulfill_search([&WINDOW.info()].into_iter());
content.update(updates)
} else {
let target = if let Some(content_id) = content.with_context(WidgetUpdateMode::Ignore, || WIDGET.id()) {
WidgetPath::new(WINDOW.id(), vec![WIDGET.id(), content_id].into())
} else {
WidgetPath::new(WINDOW.id(), vec![WIDGET.id()].into())
};
let mut updates = WidgetUpdates::new(UpdateDeliveryList::new_any());
updates.delivery_list.insert_wgt(&target);
content.update(&updates);
}
WIDGET.test_root_updates();
UPDATES.apply()
}
pub fn test_layout(&self, content: &mut impl UiNode, constraints: Option<PxConstraints2d>) -> (PxSize, ContextUpdates) {
let font_size = Length::pt_to_px(14.0, 1.0.fct());
let viewport = self.test_window_size();
let mut metrics = LayoutMetrics::new(1.fct(), viewport, font_size);
if let Some(c) = constraints {
metrics = metrics.with_constraints(c);
}
let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
let size = LAYOUT.with_context(metrics, || {
crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
});
WIDGET.test_root_updates();
(size, UPDATES.apply())
}
pub fn test_layout_inline(
&self,
content: &mut impl UiNode,
measure_constraints: (PxConstraints2d, InlineConstraintsMeasure),
layout_constraints: (PxConstraints2d, InlineConstraintsLayout),
) -> ((PxSize, PxSize), ContextUpdates) {
let font_size = Length::pt_to_px(14.0, 1.0.fct());
let viewport = self.test_window_size();
let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
.with_constraints(measure_constraints.0)
.with_inline_constraints(Some(InlineConstraints::Measure(measure_constraints.1)));
let measure_size = LAYOUT.with_context(metrics, || {
content.measure(&mut crate::widget::info::WidgetMeasure::new(Arc::default()))
});
let metrics = LayoutMetrics::new(1.fct(), viewport, font_size)
.with_constraints(layout_constraints.0)
.with_inline_constraints(Some(InlineConstraints::Layout(layout_constraints.1)));
let mut updates = LayoutUpdates::new(UpdateDeliveryList::new_any());
updates.delivery_list.insert_updates_root(WINDOW.id(), WIDGET.id());
let layout_size = LAYOUT.with_context(metrics, || {
crate::widget::info::WidgetLayout::with_root_widget(Arc::new(updates), |wl| content.layout(wl))
});
WIDGET.test_root_updates();
((measure_size, layout_size), UPDATES.apply())
}
pub fn test_render(&self, content: &mut impl UiNode) -> (crate::render::BuiltFrame, ContextUpdates) {
use crate::render::*;
let mut frame = {
let win = WINDOW_CTX.get();
let wgt = WIDGET_CTX.get();
let frame_id = win.frame_id.load(Relaxed);
win.frame_id.store(frame_id.next(), Relaxed);
let f = FrameBuilder::new_renderless(
Arc::default(),
Arc::default(),
frame_id,
wgt.id,
&wgt.bounds.lock(),
win.widget_tree.read().as_ref().unwrap(),
1.fct(),
FontAntiAliasing::Default,
);
f
};
frame.push_inner(self.test_root_translation_key(), false, |frame| {
content.render(frame);
});
let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
let f = frame.finalize(&tree);
WIDGET.test_root_updates();
(f, UPDATES.apply())
}
pub fn test_render_update(&self, content: &mut impl UiNode) -> (crate::render::BuiltFrameUpdate, ContextUpdates) {
use crate::render::*;
let mut update = {
let win = WINDOW_CTX.get();
let wgt = WIDGET_CTX.get();
let frame_id = win.frame_id.load(Relaxed);
win.frame_id.store(frame_id.next_update(), Relaxed);
let f = FrameUpdate::new(Arc::default(), frame_id, wgt.id, wgt.bounds.lock().clone(), colors::BLACK);
f
};
update.update_inner(self.test_root_translation_key(), false, |update| {
content.render_update(update);
});
let tree = WINDOW_CTX.get().widget_tree.read().as_ref().unwrap().clone();
let f = update.finalize(&tree);
WIDGET.test_root_updates();
(f, UPDATES.apply())
}
fn test_root_translation_key(&self) -> FrameValueKey<PxTransform> {
static_id! {
static ref ID: StateId<FrameValueKey<PxTransform>>;
}
WINDOW.with_state_mut(|mut s| *s.entry(*ID).or_insert_with(FrameValueKey::new_unique))
}
}
}
context_local! {
static WINDOW_CTX: WindowCtxData = WindowCtxData::no_context();
}
pub struct WindowCtx(Option<Arc<WindowCtxData>>);
impl WindowCtx {
pub fn new(id: WindowId, mode: WindowMode) -> Self {
Self(Some(Arc::new(WindowCtxData {
id,
mode,
state: RwLock::new(OwnedStateMap::default()),
widget_tree: RwLock::new(None),
#[cfg(any(test, doc, feature = "test_util"))]
frame_id: atomic::Atomic::new(zng_view_api::window::FrameId::first()),
})))
}
pub fn set_widget_tree(&mut self, widget_tree: WidgetInfoTree) {
*self.0.as_mut().unwrap().widget_tree.write() = Some(widget_tree);
}
pub fn id(&self) -> WindowId {
self.0.as_ref().unwrap().id
}
pub fn mode(&self) -> WindowMode {
self.0.as_ref().unwrap().mode
}
pub fn widget_tree(&self) -> WidgetInfoTree {
self.0.as_ref().unwrap().widget_tree.read().as_ref().unwrap().clone()
}
pub fn with_state<R>(&mut self, f: impl FnOnce(&mut OwnedStateMap<WINDOW>) -> R) -> R {
f(&mut self.0.as_mut().unwrap().state.write())
}
pub fn share(&mut self) -> Self {
Self(self.0.clone())
}
}
struct WindowCtxData {
id: WindowId,
mode: WindowMode,
state: RwLock<OwnedStateMap<WINDOW>>,
widget_tree: RwLock<Option<WidgetInfoTree>>,
#[cfg(any(test, doc, feature = "test_util"))]
frame_id: atomic::Atomic<zng_view_api::window::FrameId>,
}
impl WindowCtxData {
#[track_caller]
fn no_context() -> Self {
panic!("no window in context")
}
}
#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum WindowMode {
Headed,
Headless,
HeadlessWithRenderer,
}
impl fmt::Debug for WindowMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "WindowMode::")?;
}
match self {
WindowMode::Headed => write!(f, "Headed"),
WindowMode::Headless => write!(f, "Headless"),
WindowMode::HeadlessWithRenderer => write!(f, "HeadlessWithRenderer"),
}
}
}
impl WindowMode {
pub fn is_headed(self) -> bool {
match self {
WindowMode::Headed => true,
WindowMode::Headless | WindowMode::HeadlessWithRenderer => false,
}
}
pub fn is_headless(self) -> bool {
match self {
WindowMode::Headless | WindowMode::HeadlessWithRenderer => true,
WindowMode::Headed => false,
}
}
pub fn has_renderer(self) -> bool {
match self {
WindowMode::Headed | WindowMode::HeadlessWithRenderer => true,
WindowMode::Headless => false,
}
}
}