Skip to main content

cranpose_ui/layout/
layout_node_data.rs

1//! What a layout box keeps of its node: the node's modifier, slices,
2//! semantics and kind, read from the applier when the box is made.
3
4use std::{cell::RefCell, fmt, rc::Rc};
5
6use cranpose_core::{MemoryApplier, NodeError, NodeId};
7use cranpose_foundation::SemanticsConfiguration;
8use cranpose_ui_graphics::{ProjectiveTransform, layer_transform::layer_transform_to_window};
9
10use super::{SemanticsRole, SemanticsText};
11use crate::{
12    modifier::{Modifier, ModifierNodeSlices, Point, ResolvedModifiers, Size},
13    subcompose_layout::SubcomposeLayoutNode,
14    widgets::nodes::{LayoutNode, LayoutState},
15};
16
17/// Snapshot of the data required to render a layout node.
18#[derive(Debug, Clone)]
19pub struct LayoutNodeData {
20    /// Composable origins retained by an inspection-enabled layout node.
21    #[cfg(feature = "inspection")]
22    pub source_trace: Rc<[cranpose_core::source_trace::SourceLocation]>,
23    /// The modifier chain of the node.
24    pub modifier: Modifier,
25    /// Padding, layout properties and offset that the modifier chain resolved to.
26    pub resolved_modifiers: ResolvedModifiers,
27    /// Draw commands, pointer input handlers, text and graphics layer data collected from the
28    /// modifier nodes.
29    pub modifier_slices: Rc<ModifierNodeSlices>,
30    /// Semantics that the modifier chain reported, if any.
31    pub semantics: Option<Rc<SemanticsConfiguration>>,
32    /// What kind of node this is.
33    pub kind: LayoutNodeKind,
34    /// Where the node draws among its siblings: its `Modifier::z_index`
35    /// plus the z index its parent placed it with.
36    pub z_index: f32,
37}
38
39impl LayoutNodeData {
40    /// Creates node data from its parts.
41    pub fn new(
42        modifier: Modifier,
43        resolved_modifiers: ResolvedModifiers,
44        modifier_slices: Rc<ModifierNodeSlices>,
45        semantics: Option<Rc<SemanticsConfiguration>>,
46        kind: LayoutNodeKind,
47    ) -> Self {
48        Self {
49            #[cfg(feature = "inspection")]
50            source_trace: Rc::default(),
51            modifier,
52            resolved_modifiers,
53            modifier_slices,
54            semantics,
55            kind,
56            z_index: 0.0,
57        }
58    }
59
60    /// Semantics the node's live modifier chain reported when the snapshot
61    /// was taken.
62    pub fn semantics(&self) -> Option<&SemanticsConfiguration> {
63        self.semantics.as_deref()
64    }
65
66    /// Returns the padding, layout properties and offset that the modifier chain resolved to.
67    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
68        self.resolved_modifiers
69    }
70
71    /// Returns the draw, pointer input, text and graphics layer data collected from the modifier
72    /// nodes.
73    pub fn modifier_slices(&self) -> &ModifierNodeSlices {
74        &self.modifier_slices
75    }
76}
77
78/// Classification of the node captured inside a [`LayoutBox`](super::LayoutBox).
79///
80/// Note: Text content is no longer represented as a distinct LayoutNodeKind.
81/// Text nodes now use `LayoutNodeKind::Layout` with their content stored in
82/// `modifier_slices.text_content()` via TextModifierNode, following Jetpack
83/// Compose's pattern where text is a modifier node capability.
84#[derive(Clone)]
85pub enum LayoutNodeKind {
86    /// A node that measures and places its children.
87    Layout,
88    /// A subcomposition boundary, such as a lazy list or a `SubcomposeLayout`.
89    Subcompose,
90    /// A non-interactive spacer.
91    Spacer,
92    /// A button node.
93    Button {
94        /// Handler that runs when the button is clicked.
95        on_click: Rc<RefCell<dyn FnMut()>>,
96    },
97    /// A node that fits no other kind.
98    Unknown,
99}
100
101impl fmt::Debug for LayoutNodeKind {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            Self::Layout => f.write_str("Layout"),
105            Self::Subcompose => f.write_str("Subcompose"),
106            Self::Spacer => f.write_str("Spacer"),
107            Self::Button { .. } => f.write_str("Button"),
108            Self::Unknown => f.write_str("Unknown"),
109        }
110    }
111}
112
113pub(super) fn snapshot_node_data(
114    applier: &mut MemoryApplier,
115    node_id: NodeId,
116    top_left: Point,
117    size: Size,
118    parent_transform: ProjectiveTransform,
119) -> Result<(LayoutNodeData, ProjectiveTransform), NodeError> {
120    let info = runtime_metadata_for(applier, node_id)?;
121    let kind = layout_kind_from_metadata(node_id, &info);
122    let RuntimeNodeMetadata {
123        modifier,
124        resolved_modifiers,
125        modifier_slices,
126        semantics,
127        z_index,
128        ..
129    } = info;
130
131    let window_transform = modifier_slices
132        .graphics_layer()
133        .map_or(parent_transform, |layer| {
134            layer_transform_to_window(
135                parent_transform,
136                top_left,
137                modifier_slices.layer_bounds(size),
138                &layer,
139            )
140        });
141    modifier_slices.publish_window_geometry(top_left, window_transform, size);
142
143    let data = LayoutNodeData {
144        #[cfg(feature = "inspection")]
145        source_trace: applier
146            .with_node::<LayoutNode, _>(node_id, |node| Rc::clone(&node.source_trace))
147            .unwrap_or_default(),
148        modifier,
149        resolved_modifiers,
150        modifier_slices,
151        semantics,
152        kind,
153        z_index,
154    };
155    Ok((data, window_transform))
156}
157
158#[derive(Clone)]
159struct RuntimeNodeMetadata {
160    modifier: Modifier,
161    resolved_modifiers: ResolvedModifiers,
162    modifier_slices: Rc<ModifierNodeSlices>,
163    semantics: Option<Rc<SemanticsConfiguration>>,
164    role: SemanticsRole,
165    // The node's `Modifier::z_index` plus its placement's.
166    z_index: f32,
167    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
168}
169
170impl Default for RuntimeNodeMetadata {
171    fn default() -> Self {
172        Self {
173            modifier: Modifier::empty(),
174            resolved_modifiers: ResolvedModifiers::default(),
175            modifier_slices: Rc::default(),
176            semantics: None,
177            role: SemanticsRole::Unknown,
178            button_handler: None,
179            z_index: 0.0,
180        }
181    }
182}
183
184pub(super) fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
185    modifier_slices
186        .annotated_text()
187        .map_or(SemanticsRole::Layout, |text| SemanticsRole::Text {
188            value: SemanticsText(Rc::clone(&text)),
189        })
190}
191
192fn runtime_metadata_for(
193    applier: &mut MemoryApplier,
194    node_id: NodeId,
195) -> Result<RuntimeNodeMetadata, NodeError> {
196    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
197        let modifier = layout.modifier.clone();
198        let resolved_modifiers = layout.resolved_modifiers();
199        let modifier_slices = layout.modifier_slices_snapshot();
200        let role = role_from_modifier_slices(&modifier_slices);
201        let z_index = modifier_slices.z_index() + layout.with_layout_state(LayoutState::z_index);
202
203        RuntimeNodeMetadata {
204            modifier,
205            resolved_modifiers,
206            modifier_slices,
207            semantics: layout.semantics_configuration().map(Rc::new),
208            role,
209            button_handler: None,
210            z_index,
211        }
212    }) {
213        return Ok(meta);
214    }
215
216    if let Ok((modifier, resolved_modifiers, modifier_slices, semantics, z_index)) =
217        applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
218            let modifier_slices = node.modifier_slices_snapshot();
219            let placed = node.layout_parts().layout_state().borrow().z_index();
220            let z_index = modifier_slices.z_index() + placed;
221            (
222                node.modifier(),
223                node.resolved_modifiers(),
224                modifier_slices,
225                node.semantics_configuration().map(Rc::new),
226                z_index,
227            )
228        })
229    {
230        return Ok(RuntimeNodeMetadata {
231            modifier,
232            resolved_modifiers,
233            modifier_slices,
234            semantics,
235            role: SemanticsRole::Subcompose,
236            button_handler: None,
237            z_index,
238        });
239    }
240    Ok(RuntimeNodeMetadata::default())
241}
242
243fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
244    match &info.role {
245        SemanticsRole::Layout => LayoutNodeKind::Layout,
246        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
247        SemanticsRole::Text { .. } => LayoutNodeKind::Layout,
248        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
249        SemanticsRole::Button => {
250            let handler = info
251                .button_handler
252                .as_ref()
253                .cloned()
254                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
255            LayoutNodeKind::Button { on_click: handler }
256        }
257        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
258    }
259}