cranpose_ui_layout/core.rs
1//! Core layout traits and types shared by Compose UI widgets.
2
3use std::rc::Rc;
4
5use cranpose_core::NodeId;
6use cranpose_ui_graphics::Size;
7
8use crate::{Alignment, HorizontalAlignment, VerticalAlignment, constraints::Constraints};
9
10/// Parent data for flex layouts (Row/Column weights and alignment).
11#[derive(Clone, Copy, Debug, Default)]
12pub struct FlexParentData {
13 /// Weight for distributing remaining space in the main axis.
14 /// If > 0.0, this child participates in weighted distribution.
15 pub weight: f32,
16
17 /// Whether to fill the allocated space when using weight.
18 /// If true, child gets tight constraints; if false, child gets loose constraints.
19 pub fill: bool,
20}
21
22impl FlexParentData {
23 pub fn new(weight: f32, fill: bool) -> Self {
24 Self { weight, fill }
25 }
26
27 pub fn has_weight(&self) -> bool {
28 self.weight > 0.0
29 }
30}
31
32/// Layout metadata supplied by a child for its direct parent.
33///
34/// Weight/fill apply to Row and Column. Alignment values override the
35/// corresponding parent layout's default alignment for this child only.
36#[derive(Clone, Copy, Debug, Default, PartialEq)]
37pub struct ParentData {
38 pub weight: f32,
39 pub fill: bool,
40 pub box_alignment: Option<Alignment>,
41 pub row_alignment: Option<VerticalAlignment>,
42 pub column_alignment: Option<HorizontalAlignment>,
43}
44
45impl From<FlexParentData> for ParentData {
46 fn from(value: FlexParentData) -> Self {
47 Self {
48 weight: value.weight,
49 fill: value.fill,
50 ..Self::default()
51 }
52 }
53}
54
55impl ParentData {
56 pub fn has_weight(&self) -> bool {
57 self.weight > 0.0
58 }
59}
60
61/// Object capable of measuring a layout child and exposing intrinsic sizes.
62pub trait Measurable {
63 /// Measures the child with the provided constraints, returning a [`Placeable`].
64 fn measure(&self, constraints: Constraints) -> Placeable;
65
66 /// Returns the minimum width achievable for the given height.
67 fn min_intrinsic_width(&self, height: f32) -> f32;
68
69 /// Returns the maximum width achievable for the given height.
70 fn max_intrinsic_width(&self, height: f32) -> f32;
71
72 /// Returns the minimum height achievable for the given width.
73 fn min_intrinsic_height(&self, width: f32) -> f32;
74
75 /// Returns the maximum height achievable for the given width.
76 fn max_intrinsic_height(&self, width: f32) -> f32;
77
78 /// Returns flex parent data if this measurable has weight/fill properties.
79 /// Default implementation returns None (no weight).
80 fn flex_parent_data(&self) -> Option<FlexParentData> {
81 None
82 }
83
84 /// Returns all metadata consumed by the direct parent layout.
85 ///
86 /// The default preserves the older `flex_parent_data` customization
87 /// point so existing custom measurables continue to provide weights.
88 fn parent_data(&self) -> ParentData {
89 self.flex_parent_data().map(Into::into).unwrap_or_default()
90 }
91}
92
93/// Result of running a measurement pass for a single child.
94///
95/// Concrete struct replacing the former `dyn Placeable` trait object.
96/// This avoids a heap allocation per node per measure pass — the hot
97/// coordinator path (16-byte value) now lives entirely on the stack.
98pub struct Placeable {
99 width: f32,
100 height: f32,
101 node_id: NodeId,
102 content_offset_x: f32,
103 content_offset_y: f32,
104 place_fn: Option<Rc<dyn Fn(f32, f32)>>,
105}
106
107impl Placeable {
108 /// Creates a pure-value placeable with no side effects on `place()`.
109 pub fn value(width: f32, height: f32, node_id: NodeId) -> Self {
110 Self {
111 width,
112 height,
113 node_id,
114 content_offset_x: 0.0,
115 content_offset_y: 0.0,
116 place_fn: None,
117 }
118 }
119
120 /// Creates a pure-value placeable with a content offset.
121 pub fn value_with_offset(
122 width: f32,
123 height: f32,
124 node_id: NodeId,
125 content_offset: (f32, f32),
126 ) -> Self {
127 Self {
128 width,
129 height,
130 node_id,
131 content_offset_x: content_offset.0,
132 content_offset_y: content_offset.1,
133 place_fn: None,
134 }
135 }
136
137 /// Creates a node-backed placeable whose `place()` triggers a side effect.
138 pub fn with_place_fn(
139 width: f32,
140 height: f32,
141 node_id: NodeId,
142 place_fn: Rc<dyn Fn(f32, f32)>,
143 ) -> Self {
144 Self {
145 width,
146 height,
147 node_id,
148 content_offset_x: 0.0,
149 content_offset_y: 0.0,
150 place_fn: Some(place_fn),
151 }
152 }
153
154 /// Places the child at the provided coordinates relative to its parent.
155 pub fn place(&self, x: f32, y: f32) {
156 if let Some(f) = &self.place_fn {
157 f(x, y);
158 }
159 }
160
161 /// Returns the measured width of the child.
162 pub fn width(&self) -> f32 {
163 self.width
164 }
165
166 /// Returns the measured height of the child.
167 pub fn height(&self) -> f32 {
168 self.height
169 }
170
171 /// Returns the identifier for the underlying layout node.
172 pub fn node_id(&self) -> NodeId {
173 self.node_id
174 }
175
176 /// Returns the accumulated content offset from the coordinator chain.
177 pub fn content_offset(&self) -> (f32, f32) {
178 (self.content_offset_x, self.content_offset_y)
179 }
180}
181
182/// Scope for measurement operations.
183///
184/// This is Compose's `MeasureScope` -- the receiver `MeasurePolicy.measure` runs
185/// on, which is what lets a measure pass see the density of the subtree it is
186/// measuring rather than some process-wide default. There is no sensible
187/// fallback value for either method: a policy that reads density must be given
188/// the real grid, so both are required rather than defaulted.
189pub trait MeasureScope {
190 /// Returns the current density for converting Dp to pixels.
191 fn density(&self) -> f32;
192
193 /// Returns the current font scale for converting Sp to pixels.
194 fn font_scale(&self) -> f32;
195}
196
197/// Policy responsible for measuring and placing children.
198pub trait MeasurePolicy {
199 /// Runs the measurement pass with the provided children and constraints.
200 fn measure(
201 &self,
202 scope: &dyn MeasureScope,
203 measurables: &[Box<dyn Measurable>],
204 constraints: Constraints,
205 ) -> MeasureResult;
206
207 /// Runs measurement into caller-owned placement storage.
208 ///
209 /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
210 /// policies. Built-in policies override this to avoid allocating a fresh placement
211 /// vector on every measure pass.
212 fn measure_into(
213 &self,
214 scope: &dyn MeasureScope,
215 measurables: &[Box<dyn Measurable>],
216 constraints: Constraints,
217 placements: &mut Vec<Placement>,
218 ) -> Size {
219 let result = self.measure(scope, measurables, constraints);
220 placements.clear();
221 placements.extend(result.placements);
222 result.size
223 }
224
225 /// Computes the minimum intrinsic width of this policy.
226 fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
227
228 /// Computes the maximum intrinsic width of this policy.
229 fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
230
231 /// Computes the minimum intrinsic height of this policy.
232 fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
233
234 /// Computes the maximum intrinsic height of this policy.
235 fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
236}
237
238/// Result of a measurement operation.
239#[derive(Clone, Debug)]
240pub struct MeasureResult {
241 pub size: Size,
242 pub placements: Vec<Placement>,
243}
244
245impl MeasureResult {
246 pub fn new(size: Size, placements: Vec<Placement>) -> Self {
247 Self { size, placements }
248 }
249}
250
251/// Placement information for a measured child.
252#[derive(Clone, Copy, Debug)]
253pub struct Placement {
254 pub node_id: NodeId,
255 pub x: f32,
256 pub y: f32,
257 pub z_index: i32,
258}
259
260impl Placement {
261 pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
262 Self {
263 node_id,
264 x,
265 y,
266 z_index,
267 }
268 }
269}
270
271/// Result of a layout modifier measurement operation.
272///
273/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
274/// this type is specifically for layout modifiers which wrap a single piece
275/// of content and need to specify where that wrapped content should be placed.
276#[derive(Clone, Copy, Debug)]
277pub struct LayoutModifierMeasureResult {
278 /// The size this modifier will occupy.
279 pub size: Size,
280 /// The offset at which to place the wrapped content relative to
281 /// the top-left corner of this modifier's bounds.
282 /// For example, PaddingNode returns (padding.left, padding.top) here
283 /// to offset the child by the padding amount.
284 pub placement_offset_x: f32,
285 pub placement_offset_y: f32,
286}
287
288impl LayoutModifierMeasureResult {
289 pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
290 Self {
291 size,
292 placement_offset_x,
293 placement_offset_y,
294 }
295 }
296
297 /// Creates a result with zero placement offset (wrapped content placed at 0,0).
298 pub fn with_size(size: Size) -> Self {
299 Self {
300 size,
301 placement_offset_x: 0.0,
302 placement_offset_y: 0.0,
303 }
304 }
305}