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