Skip to main content

cranpose_ui/modifier/
window_root.rs

1//! The window root modifier: a node whose subtree is the content of its own
2//! window while staying inside the one composition.
3//!
4//! The node measures its content into the window's size and reports a zero
5//! size to its parent, so the parent lays out as if the subtree were absent.
6//! It registers itself with the app context's window root registry, which a
7//! platform reads after each update to learn which windows exist; the window
8//! is identified by the layout node that carries the modifier. The scene
9//! builder skips window roots when it builds a parent's scene and starts at
10//! one when it builds that window's scene.
11
12use std::{
13    any::Any,
14    cell::{Cell, RefCell},
15    fmt,
16    hash::{Hash, Hasher},
17    rc::Rc,
18};
19
20use cranpose_core::{Applier, MemoryApplier, NodeId};
21use cranpose_foundation::{
22    Constraints, DelegatableNode, InvalidationKind, LayoutModifierNode, Measurable, ModifierNode,
23    ModifierNodeContext, ModifierNodeElement, NodeCapabilities, NodeState, Size,
24};
25use cranpose_ui_layout::LayoutModifierMeasureResult;
26
27use super::Modifier;
28use crate::{
29    render_state::{AppContextId, current_app_context, with_app_context_by_id},
30    widgets::nodes::layout_node::LayoutNode,
31};
32
33/// What a window root needs from the platform's description of its window:
34/// the logical size to lay the content out into, read on every measure so a
35/// resize needs no new modifier, and the description itself for the platform
36/// to take back.
37pub trait WindowRootDescriptor: Any {
38    /// The window's content size in logical pixels.
39    fn layout_size(&self) -> Size;
40
41    /// The descriptor as `Any`, so the platform that made it can downcast it.
42    fn as_any(&self) -> &dyn Any;
43}
44
45/// A window root the registry knows about.
46#[derive(Clone)]
47pub struct WindowRootEntry {
48    /// The layout node carrying the window root modifier, which identifies
49    /// the window: it stays the same node across recompositions.
50    pub node: NodeId,
51    /// The platform's description of the window.
52    pub descriptor: Rc<dyn WindowRootDescriptor>,
53}
54
55impl fmt::Debug for WindowRootEntry {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("WindowRootEntry")
58            .field("node", &self.node)
59            .finish()
60    }
61}
62
63/// The window roots attached in an app context, in attach order, with a
64/// revision that changes whenever the set changes.
65#[derive(Default)]
66pub struct WindowRootRegistry {
67    entries: RefCell<Vec<WindowRootEntry>>,
68    revision: Cell<u64>,
69}
70
71impl WindowRootRegistry {
72    fn register(&self, entry: WindowRootEntry) {
73        let mut entries = self.entries.borrow_mut();
74        if let Some(existing) = entries.iter_mut().find(|known| known.node == entry.node) {
75            *existing = entry;
76        } else {
77            entries.push(entry);
78        }
79        self.bump();
80    }
81
82    fn unregister(&self, node: NodeId) {
83        let mut entries = self.entries.borrow_mut();
84        let before = entries.len();
85        entries.retain(|entry| entry.node != node);
86        if entries.len() != before {
87            self.bump();
88        }
89    }
90
91    fn bump(&self) {
92        self.revision.set(self.revision.get().wrapping_add(1));
93    }
94
95    /// Every attached window root.
96    pub fn entries(&self) -> Vec<WindowRootEntry> {
97        self.entries.borrow().clone()
98    }
99
100    /// Changes whenever a window root attaches, detaches or is updated.
101    pub fn revision(&self) -> u64 {
102        self.revision.get()
103    }
104}
105
106/// The window roots attached in the current app context.
107pub fn window_roots() -> Vec<WindowRootEntry> {
108    current_app_context().map_or_else(Vec::new, |context| context.window_roots().entries())
109}
110
111/// The current app context's window root revision; see
112/// [`WindowRootRegistry::revision`].
113pub fn window_roots_revision() -> u64 {
114    current_app_context().map_or(0, |context| context.window_roots().revision())
115}
116
117/// Whether `node` is a layout node whose modifier chain carries a window root.
118pub fn is_window_root(applier: &mut MemoryApplier, node: NodeId) -> bool {
119    applier
120        .with_node::<LayoutNode, _>(node, |layout_node| layout_node.is_window_root())
121        .unwrap_or(false)
122}
123
124/// The window root that owns `node`: the nearest node, `node` included, whose
125/// modifier chain carries a window root. `None` when the node belongs to the
126/// primary root.
127pub fn nearest_window_root(applier: &mut MemoryApplier, node: NodeId) -> Option<NodeId> {
128    let mut current = node;
129    for _ in 0..100_000 {
130        let is_root = applier
131            .with_node::<LayoutNode, _>(current, |layout_node| layout_node.is_window_root())
132            .unwrap_or(false);
133        if is_root {
134            return Some(current);
135        }
136        current = applier.get_mut(current).ok()?.parent()?;
137    }
138    None
139}
140
141/// Node that lays its content out into the window's size. That size is the
142/// node's own, for the window's scene; the layout pass reports a zero size
143/// to the node's parent.
144pub struct WindowRootNode {
145    descriptor: Rc<dyn WindowRootDescriptor>,
146    node_id: Cell<Option<NodeId>>,
147    owner: Cell<Option<AppContextId>>,
148    state: NodeState,
149}
150
151impl fmt::Debug for WindowRootNode {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.debug_struct("WindowRootNode")
154            .field("node_id", &self.node_id.get())
155            .finish()
156    }
157}
158
159impl WindowRootNode {
160    /// A window root described by `descriptor`, for a platform node that
161    /// delegates its layout and registration here.
162    pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
163        Self {
164            descriptor,
165            node_id: Cell::new(None),
166            owner: Cell::new(None),
167            state: NodeState::new(),
168        }
169    }
170
171    /// Takes a new description of the window and re-registers the root when
172    /// it is attached.
173    pub fn set_descriptor(&mut self, descriptor: Rc<dyn WindowRootDescriptor>) {
174        self.descriptor = descriptor;
175        self.register();
176    }
177
178    fn entry(&self, node: NodeId) -> WindowRootEntry {
179        WindowRootEntry {
180            node,
181            descriptor: Rc::clone(&self.descriptor),
182        }
183    }
184
185    fn register(&self) {
186        let Some(node) = self.node_id.get() else {
187            return;
188        };
189        let Some(context) = current_app_context() else {
190            log::debug!("window root {node} attached outside an app context");
191            return;
192        };
193        self.owner.set(Some(context.id()));
194        context.window_roots().register(self.entry(node));
195    }
196
197    fn unregister(&self) {
198        let (Some(node), Some(owner)) = (self.node_id.get(), self.owner.take()) else {
199            return;
200        };
201        with_app_context_by_id(owner, |context| context.window_roots().unregister(node));
202    }
203}
204
205impl DelegatableNode for WindowRootNode {
206    fn node_state(&self) -> &NodeState {
207        &self.state
208    }
209}
210
211impl ModifierNode for WindowRootNode {
212    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
213        self.node_id.set(context.node_id());
214        self.register();
215        context.invalidate(InvalidationKind::Layout);
216    }
217
218    fn on_detach(&mut self) {
219        self.unregister();
220    }
221
222    fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
223        Some(self)
224    }
225
226    fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
227        Some(self)
228    }
229}
230
231impl LayoutModifierNode for WindowRootNode {
232    fn measure(
233        &self,
234        _context: &mut dyn ModifierNodeContext,
235        measurable: &dyn Measurable,
236        _constraints: Constraints,
237    ) -> LayoutModifierMeasureResult {
238        let size = self.descriptor.layout_size();
239        let size = Size::new(size.width.max(0.0), size.height.max(0.0));
240        let _content = measurable.measure(Constraints {
241            min_width: 0.0,
242            max_width: size.width,
243            min_height: 0.0,
244            max_height: size.height,
245        });
246        LayoutModifierMeasureResult::with_size(size)
247    }
248
249    fn min_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
250        0.0
251    }
252
253    fn max_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
254        0.0
255    }
256
257    fn min_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
258        0.0
259    }
260
261    fn max_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
262        0.0
263    }
264}
265
266/// Element that creates and updates window root nodes.
267#[derive(Clone)]
268pub struct WindowRootElement {
269    descriptor: Rc<dyn WindowRootDescriptor>,
270}
271
272impl WindowRootElement {
273    /// A window root described by `descriptor`.
274    pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
275        Self { descriptor }
276    }
277
278    fn descriptor_address(&self) -> usize {
279        Rc::as_ptr(&self.descriptor).cast::<()>() as usize
280    }
281}
282
283impl fmt::Debug for WindowRootElement {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        f.debug_struct("WindowRootElement").finish()
286    }
287}
288
289impl PartialEq for WindowRootElement {
290    fn eq(&self, other: &Self) -> bool {
291        Rc::ptr_eq(&self.descriptor, &other.descriptor)
292    }
293}
294
295impl Hash for WindowRootElement {
296    fn hash<H: Hasher>(&self, state: &mut H) {
297        self.descriptor_address().hash(state);
298    }
299}
300
301impl ModifierNodeElement for WindowRootElement {
302    type Node = WindowRootNode;
303
304    fn create(&self) -> Self::Node {
305        WindowRootNode::new(Rc::clone(&self.descriptor))
306    }
307
308    fn update(&self, node: &mut Self::Node) {
309        node.set_descriptor(Rc::clone(&self.descriptor));
310    }
311
312    fn capabilities(&self) -> NodeCapabilities {
313        NodeCapabilities::LAYOUT | NodeCapabilities::WINDOW_ROOT
314    }
315
316    fn inspector_name(&self) -> &'static str {
317        "windowRoot"
318    }
319}
320
321impl Modifier {
322    /// Makes the node the root of its own window, described by `descriptor`
323    /// and identified by the node itself. The subtree is laid out into the
324    /// descriptor's size and drawn into that window's scene; the parent sees
325    /// a node of zero size and its scene skips the subtree. Platforms wrap
326    /// this in a modifier that takes their own window configuration.
327    pub fn window_root(self, descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
328        self.then(Self::with_element(WindowRootElement::new(descriptor)))
329    }
330}