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}