ailloli_ui_runtime/layout/layout_result.rs
1//! Computed node and child geometry returned by retained layout.
2
3#[cfg(feature = "devtools")]
4use ailloli_ui_core::Constraints;
5use ailloli_ui_core::{ClipShape, Offset, Rect, Size};
6use ailloli_ui_text::TextLayoutHandle;
7
8#[derive(Debug, Clone, PartialEq)]
9/// Geometry assigned to one direct child by its parent.
10///
11/// All coordinates and dimensions are logical pixels. `offset` is relative to
12/// the parent, while both bounds are expressed in that same parent-local space.
13/// Values are stored verbatim; this type does not reject negative or non-finite
14/// geometry.
15///
16/// # Examples
17///
18/// ```
19/// use ailloli_ui_core::{Offset, Rect, Size};
20/// use ailloli_ui_runtime::layout::ChildLayout;
21///
22/// let child = ChildLayout {
23/// offset: Offset::new(4.0, 6.0),
24/// size: Size::new(20.0, 10.0),
25/// paint_bounds: Rect::new(4.0, 6.0, 20.0, 10.0),
26/// visual_bounds: Rect::new(3.0, 5.0, 22.0, 12.0),
27/// };
28/// assert_eq!(child.offset.x, 4.0);
29/// ```
30pub struct ChildLayout {
31 /// Child origin relative to the parent, in logical pixels.
32 pub offset: Offset,
33 /// Measured child size in logical pixels.
34 pub size: Size,
35 /// Region the child can paint, expressed in parent-local coordinates.
36 pub paint_bounds: Rect,
37 /// Region containing the child's visual effects, in parent-local coordinates.
38 pub visual_bounds: Rect,
39}
40
41/// Reusable, subsystem-specific data produced during layout.
42///
43/// Keeping an artifact in [`LayoutResult`] lets paint reuse expensive work
44/// rather than shape or measure it again. Artifacts do not participate in the
45/// runtime's geometry-change comparison.
46///
47/// # Examples
48///
49/// ```
50/// use ailloli_ui_runtime::layout::LayoutArtifact;
51///
52/// fn is_text(artifact: &LayoutArtifact) -> bool {
53/// matches!(artifact, LayoutArtifact::Text(_))
54/// }
55/// # let _ = is_text;
56/// ```
57#[derive(Debug, Clone)]
58pub enum LayoutArtifact {
59 /// A shared shaped-text layout owned by `ailloli_ui_text`.
60 Text(TextLayoutHandle),
61}
62
63#[cfg(feature = "devtools")]
64/// Per-element layout measurements retained for developer tooling.
65///
66/// The first constraints observed in a frame remain in `constraints_in`;
67/// repeated records update `constraints_final` and `layout_size`.
68///
69/// # Examples
70///
71/// ```
72/// use ailloli_ui_core::{Constraints, Size};
73/// use ailloli_ui_runtime::layout::LayoutDebugInfo;
74///
75/// let info = LayoutDebugInfo {
76/// constraints_in: Constraints::loose(100.0, 80.0),
77/// constraints_final: None,
78/// layout_size: Size::new(40.0, 20.0),
79/// };
80/// assert!(info.constraints_final.is_none());
81/// ```
82#[derive(Debug, Clone)]
83pub struct LayoutDebugInfo {
84 /// First constraints received for the element, in logical pixels.
85 pub constraints_in: Constraints,
86 /// Most recently recorded constraints, or `None` before a record is finalized.
87 pub constraints_final: Option<Constraints>,
88 /// Most recently recorded output size, in logical pixels.
89 pub layout_size: Size,
90}
91
92/// Layout output for one element: geometry, child placement, clipping, and an artifact.
93///
94/// Rectangles use element-local logical pixels unless a field states otherwise.
95/// `children` is positional: entry `n` describes retained child `n`. An empty
96/// vector means that no direct child was laid out. No constructor validates
97/// finite or non-negative geometry.
98///
99/// # Examples
100///
101/// ```
102/// use ailloli_ui_core::{Rect, Size};
103/// use ailloli_ui_runtime::layout::LayoutResult;
104///
105/// let mut result = LayoutResult::empty();
106/// result.size = Size::new(80.0, 24.0);
107/// result.paint_bounds = Rect::new(0.0, 0.0, 80.0, 24.0);
108/// assert_eq!(result.children.len(), 0);
109/// ```
110#[derive(Debug, Clone)]
111pub struct LayoutResult {
112 /// Element size in logical pixels.
113 pub size: Size,
114 /// Direct-child geometry, in the same order as the retained children.
115 pub children: Vec<ChildLayout>,
116 /// Element-local region that may receive draw commands.
117 pub paint_bounds: Rect,
118 /// Element-local region containing paint plus visual effects such as shadows.
119 pub visual_bounds: Rect,
120 /// Extra local hit-test regions for top-level overlays owned by this widget.
121 ///
122 /// These rects do not affect layout, paint bounds, visual bounds, or parent
123 /// clipping. The input router translates them to absolute coordinates and
124 /// checks them before normal tree hit-testing.
125 pub overlay_hit_bounds: Vec<Rect>,
126 /// Optional element-local clip; `None` means this result introduces no clip.
127 pub clip: Option<ClipShape>,
128 /// Window root clip (`Window::radius` + `clip_children` on the surface wrapper).
129 pub is_window_root_clip: bool,
130 /// Optional reusable layout data; `None` means paint must not expect an artifact.
131 pub artifact: Option<LayoutArtifact>,
132}
133
134/// Provides the operations defined for LayoutResult.
135impl LayoutResult {
136 /// Returns a zero-sized result with no children, clips, overlays, or artifact.
137 ///
138 /// Both bounds are `(0, 0, 0, 0)` and `is_window_root_clip` is `false`.
139 ///
140 /// # Examples
141 ///
142 /// ```
143 /// use ailloli_ui_core::Size;
144 /// use ailloli_ui_runtime::layout::LayoutResult;
145 ///
146 /// let result = LayoutResult::empty();
147 /// assert_eq!(result.size, Size::default());
148 /// assert!(result.children.is_empty() && result.artifact.is_none());
149 /// ```
150 pub fn empty() -> Self {
151 Self {
152 size: Size::default(),
153 children: Vec::new(),
154 paint_bounds: Rect::new(0.0, 0.0, 0.0, 0.0),
155 visual_bounds: Rect::new(0.0, 0.0, 0.0, 0.0),
156 overlay_hit_bounds: Vec::new(),
157 clip: None,
158 is_window_root_clip: false,
159 artifact: None,
160 }
161 }
162
163 /// Alias for [`Self::empty`].
164 ///
165 /// # Examples
166 ///
167 /// ```
168 /// use ailloli_ui_runtime::layout::LayoutResult;
169 ///
170 /// let result = LayoutResult::zero();
171 /// assert_eq!(result.paint_bounds.w, 0.0);
172 /// ```
173 pub fn zero() -> Self {
174 Self::empty()
175 }
176
177 /// Compares layout geometry while deliberately ignoring cached artifacts.
178 ///
179 /// This crate-visible helper compares sizes, child records, both bounds,
180 /// overlay hit regions, the clip, and the window-root flag. It lets a newly
181 /// shaped text artifact replace an older handle without by itself marking
182 /// committed geometry as changed.
183 ///
184 /// # Examples
185 ///
186 /// ```
187 /// use ailloli_ui_runtime::layout::LayoutResult;
188 ///
189 /// let left = LayoutResult::empty();
190 /// let right = LayoutResult::zero();
191 /// // These are geometrically equal even though artifact identity is not a
192 /// // public comparison concern.
193 /// assert_eq!(left.size, right.size);
194 /// assert_eq!(left.paint_bounds, right.paint_bounds);
195 /// ```
196 pub(crate) fn geometry_eq(&self, other: &Self) -> bool {
197 self.size == other.size
198 && self.children == other.children
199 && self.paint_bounds == other.paint_bounds
200 && self.visual_bounds == other.visual_bounds
201 && self.overlay_hit_bounds == other.overlay_hit_bounds
202 && self.clip == other.clip
203 && self.is_window_root_clip == other.is_window_root_clip
204 }
205}