ailloli_ui_runtime/element/element_node.rs
1//! One retained element node and its layout, paint, and interaction metadata.
2
3use std::rc::Rc;
4
5use ailloli_ui_core::style::{FlexItemStyle, LayoutSizeHint};
6use ailloli_ui_core::{ElementId, WidgetId};
7
8use super::{DirtyFlags, Key};
9use crate::component::reactive::MountGeneration;
10use crate::component::reactive::ReactiveReadSet;
11use crate::component::{ComponentNode, Widget};
12#[cfg(feature = "devtools")]
13use crate::layout::LayoutDebugInfo;
14use crate::layout::{LayoutPass, LayoutResult};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17/// Exact cache identity for one retained layout result.
18///
19/// Floating-point inputs are stored as raw `f32::to_bits` values, so signed
20/// zero and NaN payloads remain distinct. Revisions use zero as the absence
21/// sentinel where their producer has no applicable dependency.
22///
23/// # Examples
24///
25/// The public layout path constructs this internal key when it caches a result;
26/// repeating the same inputs can therefore reuse that result.
27///
28/// ```
29/// use ailloli_ui_core::{Constraints, Scale};
30/// use ailloli_ui_runtime::element::{ElementKind, ElementTree};
31/// let mut tree = ElementTree::<()>::new();
32/// let id = tree.create_element(ElementKind::Empty, None, None);
33/// assert!(tree.get(id).unwrap().layout.is_none());
34/// let _inputs = (Constraints::tight(20.0, 10.0), Scale::new(1.0));
35/// ```
36pub(crate) struct LayoutCacheKey {
37 /// `[min_w, max_w, min_h, max_h]` raw constraint bits.
38 pub constraints: [u32; 4],
39 /// Raw device-pixel-ratio bits.
40 pub scale: u32,
41 /// Speculative or authoritative authority of the cached traversal.
42 pub layout_pass: LayoutPass,
43 /// Text-system metrics revision, or zero without a text system.
44 pub text_metrics_revision: u64,
45 /// Element-local nonzero wrapping layout revision.
46 pub layout_revision: u64,
47 /// Widget-owned layout dependency revision, or zero for non-widgets.
48 pub layout_dependency_revision: u64,
49 /// Nonzero wrapping direct-child topology revision.
50 pub topology_revision: u64,
51 /// Viewport rect and overscan bits, or `None` when virtualization is unscoped.
52 pub virtual_viewport: Option<[u32; 5]>,
53}
54
55/// Runtime payload retained for one element.
56///
57/// Cloning a widget or component kind clones its `Rc`, preserving the same
58/// object identity and UI-thread ownership.
59///
60/// # Examples
61///
62/// ```
63/// use ailloli_ui_runtime::element::ElementKind;
64/// let kind: ElementKind<()> = ElementKind::Empty;
65/// assert!(matches!(kind, ElementKind::Empty));
66/// ```
67pub enum ElementKind<A> {
68 /// Structural placeholder with no widget callbacks.
69 Empty,
70 /// Shared retained widget implementation.
71 Widget(Rc<dyn Widget<A>>),
72 /// Shared stateful declarative component implementation.
73 Component(Rc<dyn ComponentNode<A>>),
74}
75
76/// One node in an [`super::ElementTree`].
77///
78/// Public fields expose retained state for framework integration. Mutating
79/// relationships or layout directly can violate tree/cache invariants; prefer
80/// [`super::ElementTree`] methods when available.
81///
82/// # Examples
83///
84/// ```
85/// use ailloli_ui_runtime::element::{ElementKind, ElementTree};
86/// let mut tree = ElementTree::<()>::new();
87/// let id = tree.create_element(ElementKind::Empty, None, None);
88/// let element = tree.get(id).unwrap();
89/// assert_eq!(element.id, id);
90/// assert!(element.dirty.layout && element.layout.is_none());
91/// ```
92pub struct Element<A> {
93 /// Stable element identity allocated by its tree.
94 pub id: ElementId,
95 /// Stable widget-facing identity allocated alongside the element.
96 pub widget_id: WidgetId,
97 /// Generation of the payload currently mounted at this stable element ID.
98 pub(crate) mount_generation: MountGeneration,
99 /// Optional reconciliation key; `None` selects positional matching.
100 pub key: Option<Key>,
101 /// Empty, widget, or component payload.
102 pub kind: ElementKind<A>,
103 /// Pending layout, paint, and input work.
104 pub dirty: DirtyFlags,
105 /// Retained parent ID, or `None` for a root/detached node.
106 pub parent: Option<ElementId>,
107 /// Direct children in layout, paint, hit-test, and reconciliation order.
108 pub children: Vec<ElementId>,
109 /// Most recent authoritative layout, or `None` before a committed pass.
110 pub layout: Option<LayoutResult>,
111 /// Cache inputs associated with `layout`, or `None` when invalidated.
112 pub(crate) layout_cache_key: Option<LayoutCacheKey>,
113 /// Direct reactive reads required by the authoritative layout cache.
114 pub(crate) layout_reactive_dependencies: ReactiveReadSet,
115 /// Reactive reads retained by the last successful `layout_committed` hook.
116 pub(crate) layout_commit_reactive_dependencies: ReactiveReadSet,
117 /// Payload generation that produced the authoritative layout and artifact.
118 pub(crate) committed_layout_generation: Option<MountGeneration>,
119 /// Exact validated attempt that most recently refreshed committed layout.
120 pub(crate) committed_layout_attempt: Option<crate::layout::LayoutAttemptToken>,
121 /// Most recent speculative layout, kept separate from committed geometry.
122 pub(crate) measurement_layout: Option<LayoutResult>,
123 /// Cache inputs associated with `measurement_layout`.
124 pub(crate) measurement_layout_cache_key: Option<LayoutCacheKey>,
125 /// Direct reactive reads required by the speculative layout cache.
126 pub(crate) measurement_reactive_dependencies: ReactiveReadSet,
127 /// Nonzero wrapping revision of element-owned layout inputs.
128 pub(crate) layout_revision: u64,
129 /// Nonzero wrapping revision of direct-child ordering.
130 pub(crate) topology_revision: u64,
131 /// Whether geometry differs from the previously cached result.
132 pub(crate) layout_changed: bool,
133 /// Whether the final authoritative result came from a real layout callback.
134 pub(crate) layout_callback_executed: bool,
135 /// Whether layout commit must reconsider this element/subtree.
136 pub(crate) commit_dirty: bool,
137 /// Last absolute logical-pixel bounds delivered during commit.
138 pub(crate) committed_bounds: Option<ailloli_ui_core::Rect>,
139 /// Flex behavior when this element is a direct flex-container child.
140 pub flex_item: FlexItemStyle,
141 /// Declarative width and height hints used by parent layout.
142 pub size_hint: LayoutSizeHint,
143 #[cfg(feature = "devtools")]
144 /// Latest developer-tooling layout record, or `None` before layout.
145 pub layout_debug: Option<LayoutDebugInfo>,
146}
147
148/// Clones the retained payload while sharing widget/component allocation identity.
149impl<A> Clone for ElementKind<A> {
150 /// Produces the identity-preserving payload clone.
151 fn clone(&self) -> Self {
152 match self {
153 Self::Empty => Self::Empty,
154 Self::Widget(w) => Self::Widget(w.clone()),
155 Self::Component(c) => Self::Component(c.clone()),
156 }
157 }
158}
159
160/// Clones an element snapshot, including retained relationships and cache state.
161impl<A> Clone for Element<A> {
162 /// Produces a structural clone whose reference-counted payload stays shared.
163 fn clone(&self) -> Self {
164 Self {
165 id: self.id,
166 widget_id: self.widget_id,
167 mount_generation: self.mount_generation,
168 key: self.key.clone(),
169 kind: self.kind.clone(),
170 dirty: self.dirty,
171 parent: self.parent,
172 children: self.children.clone(),
173 layout: self.layout.clone(),
174 layout_cache_key: self.layout_cache_key,
175 layout_reactive_dependencies: self.layout_reactive_dependencies.clone(),
176 layout_commit_reactive_dependencies: self.layout_commit_reactive_dependencies.clone(),
177 committed_layout_generation: self.committed_layout_generation,
178 committed_layout_attempt: self.committed_layout_attempt,
179 measurement_layout: self.measurement_layout.clone(),
180 measurement_layout_cache_key: self.measurement_layout_cache_key,
181 measurement_reactive_dependencies: self.measurement_reactive_dependencies.clone(),
182 layout_revision: self.layout_revision,
183 topology_revision: self.topology_revision,
184 layout_changed: self.layout_changed,
185 layout_callback_executed: self.layout_callback_executed,
186 commit_dirty: self.commit_dirty,
187 committed_bounds: self.committed_bounds,
188 flex_item: self.flex_item,
189 size_hint: self.size_hint,
190 #[cfg(feature = "devtools")]
191 layout_debug: self.layout_debug.clone(),
192 }
193 }
194}
195
196impl<A> Element<A> {
197 /// Returns the exact retained payload generation for dependency tracking.
198 #[doc(hidden)]
199 pub const fn mount_generation(&self) -> MountGeneration {
200 self.mount_generation
201 }
202
203 /// Advances the payload generation without silent identity reuse.
204 pub(crate) fn advance_mount_generation(&mut self) -> MountGeneration {
205 self.mount_generation = self.mount_generation.next();
206 self.mount_generation
207 }
208}