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