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/// Reusable ancestry storage for [`nearest_window_roots_into`].
145#[derive(Default)]
146pub struct WindowRootRoutingScratch {
147    owners: HashMap<NodeId, Option<NodeId>>,
148    path: Vec<NodeId>,
149}
150
151/// [`nearest_window_root`] for each of `nodes`, in order, with every
152/// ancestor looked up at most once. In an app context with no window root
153/// attached, every node belongs to the primary root and none is looked up.
154pub fn nearest_window_roots(applier: &mut MemoryApplier, nodes: &[NodeId]) -> Vec<Option<NodeId>> {
155    let mut output = Vec::new();
156    nearest_window_roots_into(
157        applier,
158        nodes.iter().copied(),
159        &mut output,
160        &mut WindowRootRoutingScratch::default(),
161    );
162    output
163}
164
165/// Fills `output` with [`nearest_window_roots`] results, reusing its storage
166/// and the ancestry cache in `scratch` across batches.
167///
168/// The cache is cleared for every batch, so changes to window-root modifiers
169/// and tree attachment are observed immediately.
170///
171/// ```
172/// use cranpose_core::MemoryApplier;
173/// use cranpose_ui::{WindowRootRoutingScratch, nearest_window_roots_into};
174///
175/// let mut applier = MemoryApplier::new();
176/// let mut scratch = WindowRootRoutingScratch::default();
177/// let mut owners = Vec::new();
178/// nearest_window_roots_into(&mut applier, [], &mut owners, &mut scratch);
179/// assert!(owners.is_empty());
180/// ```
181pub fn nearest_window_roots_into(
182    applier: &mut MemoryApplier,
183    nodes: impl IntoIterator<Item = NodeId>,
184    output: &mut Vec<Option<NodeId>>,
185    scratch: &mut WindowRootRoutingScratch,
186) {
187    let nodes = nodes.into_iter();
188    output.clear();
189    scratch.owners.clear();
190    scratch.path.clear();
191    let (lower_bound, upper_bound) = nodes.size_hint();
192    if lower_bound == 0 && upper_bound == Some(0) {
193        return;
194    }
195    output.reserve(lower_bound);
196    if current_app_context().is_some_and(|context| context.window_roots().is_empty()) {
197        output.extend(nodes.map(|_| None));
198        return;
199    }
200    for node in nodes {
201        let mut current = node;
202        scratch.path.clear();
203        let owner = loop {
204            if let Some(known) = scratch.owners.get(&current) {
205                break *known;
206            }
207            scratch.path.push(current);
208            if is_window_root(applier, current) {
209                break Some(current);
210            }
211            match applier.get_mut(current).ok().and_then(|node| node.parent()) {
212                Some(parent) if scratch.path.len() < 100_000 => current = parent,
213                _ => break None,
214            }
215        };
216        for visited in scratch.path.drain(..) {
217            scratch.owners.insert(visited, owner);
218        }
219        output.push(owner);
220    }
221}
222
223/// Node that lays its content out into the window's size. That size is the
224/// node's own, for the window's scene; the layout pass reports a zero size
225/// to the node's parent.
226pub struct WindowRootNode {
227    descriptor: Rc<dyn WindowRootDescriptor>,
228    node_id: Cell<Option<NodeId>>,
229    owner: Cell<Option<AppContextId>>,
230    state: NodeState,
231}
232
233impl fmt::Debug for WindowRootNode {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        f.debug_struct("WindowRootNode")
236            .field("node_id", &self.node_id.get())
237            .finish()
238    }
239}
240
241impl WindowRootNode {
242    /// A window root described by `descriptor`, for a platform node that
243    /// delegates its layout and registration here.
244    pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
245        Self {
246            descriptor,
247            node_id: Cell::new(None),
248            owner: Cell::new(None),
249            state: NodeState::new(),
250        }
251    }
252
253    /// Takes a new description of the window and re-registers the root when
254    /// it is attached.
255    pub fn set_descriptor(&mut self, descriptor: Rc<dyn WindowRootDescriptor>) {
256        self.descriptor = descriptor;
257        self.register();
258    }
259
260    fn entry(&self, node: NodeId) -> WindowRootEntry {
261        WindowRootEntry {
262            node,
263            descriptor: Rc::clone(&self.descriptor),
264        }
265    }
266
267    fn register(&self) {
268        let Some(node) = self.node_id.get() else {
269            return;
270        };
271        let Some(context) = current_app_context() else {
272            log::debug!("window root {node} attached outside an app context");
273            return;
274        };
275        self.owner.set(Some(context.id()));
276        context.window_roots().register(self.entry(node));
277    }
278
279    fn unregister(&self) {
280        let (Some(node), Some(owner)) = (self.node_id.get(), self.owner.take()) else {
281            return;
282        };
283        with_app_context_by_id(owner, |context| context.window_roots().unregister(node));
284    }
285}
286
287impl DelegatableNode for WindowRootNode {
288    fn node_state(&self) -> &NodeState {
289        &self.state
290    }
291}
292
293impl ModifierNode for WindowRootNode {
294    fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
295        self.node_id.set(context.node_id());
296        self.register();
297        context.invalidate(InvalidationKind::Layout);
298    }
299
300    fn on_detach(&mut self) {
301        self.unregister();
302    }
303
304    fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
305        Some(self)
306    }
307
308    fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
309        Some(self)
310    }
311}
312
313impl LayoutModifierNode for WindowRootNode {
314    fn measure(
315        &self,
316        _context: &mut dyn ModifierNodeContext,
317        measurable: &dyn Measurable,
318        _constraints: Constraints,
319    ) -> LayoutModifierMeasureResult {
320        let size = self.descriptor.layout_size();
321        let size = Size::new(size.width.max(0.0), size.height.max(0.0));
322        let _content = measurable.measure(Constraints {
323            min_width: 0.0,
324            max_width: size.width,
325            min_height: 0.0,
326            max_height: size.height,
327        });
328        LayoutModifierMeasureResult::with_size(size)
329    }
330
331    fn min_intrinsic_width(
332        &self,
333        _measurable: &dyn Measurable,
334        _height: f32,
335        _density: f32,
336    ) -> f32 {
337        0.0
338    }
339
340    fn max_intrinsic_width(
341        &self,
342        _measurable: &dyn Measurable,
343        _height: f32,
344        _density: f32,
345    ) -> f32 {
346        0.0
347    }
348
349    fn min_intrinsic_height(
350        &self,
351        _measurable: &dyn Measurable,
352        _width: f32,
353        _density: f32,
354    ) -> f32 {
355        0.0
356    }
357
358    fn max_intrinsic_height(
359        &self,
360        _measurable: &dyn Measurable,
361        _width: f32,
362        _density: f32,
363    ) -> f32 {
364        0.0
365    }
366}
367
368/// Element that creates and updates window root nodes.
369#[derive(Clone)]
370pub struct WindowRootElement {
371    descriptor: Rc<dyn WindowRootDescriptor>,
372}
373
374impl WindowRootElement {
375    /// A window root described by `descriptor`.
376    pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
377        Self { descriptor }
378    }
379
380    fn descriptor_address(&self) -> usize {
381        Rc::as_ptr(&self.descriptor).cast::<()>() as usize
382    }
383}
384
385impl fmt::Debug for WindowRootElement {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        f.debug_struct("WindowRootElement").finish()
388    }
389}
390
391impl PartialEq for WindowRootElement {
392    fn eq(&self, other: &Self) -> bool {
393        Rc::ptr_eq(&self.descriptor, &other.descriptor)
394    }
395}
396
397impl Hash for WindowRootElement {
398    fn hash<H: Hasher>(&self, state: &mut H) {
399        self.descriptor_address().hash(state);
400    }
401}
402
403impl ModifierNodeElement for WindowRootElement {
404    type Node = WindowRootNode;
405
406    fn create(&self) -> Self::Node {
407        WindowRootNode::new(Rc::clone(&self.descriptor))
408    }
409
410    fn update(&self, node: &mut Self::Node) {
411        node.set_descriptor(Rc::clone(&self.descriptor));
412    }
413
414    fn capabilities(&self) -> NodeCapabilities {
415        NodeCapabilities::LAYOUT | NodeCapabilities::WINDOW_ROOT
416    }
417
418    fn inspector_name(&self) -> &'static str {
419        "windowRoot"
420    }
421}
422
423impl Modifier {
424    /// Makes the node the root of its own window, described by `descriptor`
425    /// and identified by the node itself. The subtree is laid out into the
426    /// descriptor's size and drawn into that window's scene; the parent sees
427    /// a node of zero size and its scene skips the subtree. Platforms wrap
428    /// this in a modifier that takes their own window configuration.
429    pub fn window_root(self, descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
430        self.then(Self::with_element(WindowRootElement::new(descriptor)))
431    }
432}