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