cranpose_ui_layout/core.rs
1//! Core layout traits and types shared by Compose UI widgets.
2
3use crate::constraints::Constraints;
4use cranpose_core::NodeId;
5use cranpose_ui_graphics::Size;
6use std::rc::Rc;
7
8/// Parent data for flex layouts (Row/Column weights and alignment).
9#[derive(Clone, Copy, Debug, Default)]
10pub struct FlexParentData {
11 /// Weight for distributing remaining space in the main axis.
12 /// If > 0.0, this child participates in weighted distribution.
13 pub weight: f32,
14
15 /// Whether to fill the allocated space when using weight.
16 /// If true, child gets tight constraints; if false, child gets loose constraints.
17 pub fill: bool,
18}
19
20impl FlexParentData {
21 pub fn new(weight: f32, fill: bool) -> Self {
22 Self { weight, fill }
23 }
24
25 pub fn has_weight(&self) -> bool {
26 self.weight > 0.0
27 }
28}
29
30/// Object capable of measuring a layout child and exposing intrinsic sizes.
31pub trait Measurable {
32 /// Measures the child with the provided constraints, returning a [`Placeable`].
33 fn measure(&self, constraints: Constraints) -> Placeable;
34
35 /// Returns the minimum width achievable for the given height.
36 fn min_intrinsic_width(&self, height: f32) -> f32;
37
38 /// Returns the maximum width achievable for the given height.
39 fn max_intrinsic_width(&self, height: f32) -> f32;
40
41 /// Returns the minimum height achievable for the given width.
42 fn min_intrinsic_height(&self, width: f32) -> f32;
43
44 /// Returns the maximum height achievable for the given width.
45 fn max_intrinsic_height(&self, width: f32) -> f32;
46
47 /// Returns flex parent data if this measurable has weight/fill properties.
48 /// Default implementation returns None (no weight).
49 fn flex_parent_data(&self) -> Option<FlexParentData> {
50 None
51 }
52}
53
54/// Result of running a measurement pass for a single child.
55///
56/// Concrete struct replacing the former `dyn Placeable` trait object.
57/// This avoids a heap allocation per node per measure pass — the hot
58/// coordinator path (16-byte value) now lives entirely on the stack.
59pub struct Placeable {
60 width: f32,
61 height: f32,
62 node_id: NodeId,
63 content_offset_x: f32,
64 content_offset_y: f32,
65 /// Optional side-effect executed by `place()`. `None` for pure-value
66 /// placeables (coordinators, subcompose).
67 place_fn: Option<Rc<dyn Fn(f32, f32)>>,
68}
69
70impl Placeable {
71 /// Creates a pure-value placeable with no side effects on `place()`.
72 pub fn value(width: f32, height: f32, node_id: NodeId) -> Self {
73 Self {
74 width,
75 height,
76 node_id,
77 content_offset_x: 0.0,
78 content_offset_y: 0.0,
79 place_fn: None,
80 }
81 }
82
83 /// Creates a pure-value placeable with a content offset.
84 pub fn value_with_offset(
85 width: f32,
86 height: f32,
87 node_id: NodeId,
88 content_offset: (f32, f32),
89 ) -> Self {
90 Self {
91 width,
92 height,
93 node_id,
94 content_offset_x: content_offset.0,
95 content_offset_y: content_offset.1,
96 place_fn: None,
97 }
98 }
99
100 /// Creates a node-backed placeable whose `place()` triggers a side effect.
101 pub fn with_place_fn(
102 width: f32,
103 height: f32,
104 node_id: NodeId,
105 place_fn: Rc<dyn Fn(f32, f32)>,
106 ) -> Self {
107 Self {
108 width,
109 height,
110 node_id,
111 content_offset_x: 0.0,
112 content_offset_y: 0.0,
113 place_fn: Some(place_fn),
114 }
115 }
116
117 /// Places the child at the provided coordinates relative to its parent.
118 pub fn place(&self, x: f32, y: f32) {
119 if let Some(f) = &self.place_fn {
120 f(x, y);
121 }
122 }
123
124 /// Returns the measured width of the child.
125 pub fn width(&self) -> f32 {
126 self.width
127 }
128
129 /// Returns the measured height of the child.
130 pub fn height(&self) -> f32 {
131 self.height
132 }
133
134 /// Returns the identifier for the underlying layout node.
135 pub fn node_id(&self) -> NodeId {
136 self.node_id
137 }
138
139 /// Returns the accumulated content offset from the coordinator chain.
140 pub fn content_offset(&self) -> (f32, f32) {
141 (self.content_offset_x, self.content_offset_y)
142 }
143}
144
145/// Scope for measurement operations.
146pub trait MeasureScope {
147 /// Returns the current density for converting Dp to pixels.
148 fn density(&self) -> f32 {
149 1.0
150 }
151
152 /// Returns the current font scale for converting Sp to pixels.
153 fn font_scale(&self) -> f32 {
154 1.0
155 }
156}
157
158/// Policy responsible for measuring and placing children.
159pub trait MeasurePolicy {
160 /// Runs the measurement pass with the provided children and constraints.
161 fn measure(
162 &self,
163 measurables: &[Box<dyn Measurable>],
164 constraints: Constraints,
165 ) -> MeasureResult;
166
167 /// Computes the minimum intrinsic width of this policy.
168 fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
169
170 /// Computes the maximum intrinsic width of this policy.
171 fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
172
173 /// Computes the minimum intrinsic height of this policy.
174 fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
175
176 /// Computes the maximum intrinsic height of this policy.
177 fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
178}
179
180/// Result of a measurement operation.
181#[derive(Clone, Debug)]
182pub struct MeasureResult {
183 pub size: Size,
184 pub placements: Vec<Placement>,
185}
186
187impl MeasureResult {
188 pub fn new(size: Size, placements: Vec<Placement>) -> Self {
189 Self { size, placements }
190 }
191}
192
193/// Placement information for a measured child.
194#[derive(Clone, Copy, Debug)]
195pub struct Placement {
196 pub node_id: NodeId,
197 pub x: f32,
198 pub y: f32,
199 pub z_index: i32,
200}
201
202impl Placement {
203 pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
204 Self {
205 node_id,
206 x,
207 y,
208 z_index,
209 }
210 }
211}
212
213/// Result of a layout modifier measurement operation.
214///
215/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
216/// this type is specifically for layout modifiers which wrap a single piece
217/// of content and need to specify where that wrapped content should be placed.
218#[derive(Clone, Copy, Debug)]
219pub struct LayoutModifierMeasureResult {
220 /// The size this modifier will occupy.
221 pub size: Size,
222 /// The offset at which to place the wrapped content relative to
223 /// the top-left corner of this modifier's bounds.
224 /// For example, PaddingNode returns (padding.left, padding.top) here
225 /// to offset the child by the padding amount.
226 pub placement_offset_x: f32,
227 pub placement_offset_y: f32,
228}
229
230impl LayoutModifierMeasureResult {
231 pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
232 Self {
233 size,
234 placement_offset_x,
235 placement_offset_y,
236 }
237 }
238
239 /// Creates a result with zero placement offset (wrapped content placed at 0,0).
240 pub fn with_size(size: Size) -> Self {
241 Self {
242 size,
243 placement_offset_x: 0.0,
244 placement_offset_y: 0.0,
245 }
246 }
247}