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 /// A child's share of the room left over, and whether it fills it.
24 pub fn new(weight: f32, fill: bool) -> Self {
25 Self { weight, fill }
26 }
27
28 /// Whether the child takes a share of the room left over.
29 pub fn has_weight(&self) -> bool {
30 self.weight > 0.0
31 }
32}
33
34/// Layout metadata supplied by a child for its direct parent.
35///
36/// Weight/fill apply to Row and Column. Alignment values override the
37/// corresponding parent layout's default alignment for this child only.
38#[derive(Clone, Copy, Debug, Default, PartialEq)]
39pub struct ParentData {
40 /// The child's share of a Row's or Column's room left over; 0 for none.
41 pub weight: f32,
42 /// Whether a weighted child fills its share or may be smaller.
43 pub fill: bool,
44 /// Where the child sits in a Box, over the Box's own alignment.
45 pub box_alignment: Option<Alignment>,
46 /// Where the child sits across a Row, over the Row's own alignment.
47 pub row_alignment: Option<VerticalAlignment>,
48 /// Aligns this child's first baseline with other baseline-aligned Row children.
49 pub row_baseline: bool,
50 /// Where the child sits across a Column, over the Column's own alignment.
51 pub column_alignment: Option<HorizontalAlignment>,
52}
53
54impl From<FlexParentData> for ParentData {
55 fn from(value: FlexParentData) -> Self {
56 Self {
57 weight: value.weight,
58 fill: value.fill,
59 ..Self::default()
60 }
61 }
62}
63
64impl ParentData {
65 /// Whether the child takes a share of the room left over.
66 pub fn has_weight(&self) -> bool {
67 self.weight > 0.0
68 }
69}
70
71/// Object capable of measuring a layout child and exposing intrinsic sizes.
72pub trait Measurable {
73 /// Measures the child with the provided constraints, returning a [`Placeable`].
74 fn measure(&self, constraints: Constraints) -> Placeable;
75
76 /// The size the last measure gave its parent and the incoming
77 /// constraints that measurement holds for: see
78 /// [`crate::ConstraintsHold`]. `None`, the default, when it cannot tell.
79 fn measured_hold(&self) -> Option<(Size, crate::ConstraintsHold)> {
80 None
81 }
82
83 /// Returns the minimum width achievable for the given height.
84 fn min_intrinsic_width(&self, height: f32) -> f32;
85
86 /// Returns the maximum width achievable for the given height.
87 fn max_intrinsic_width(&self, height: f32) -> f32;
88
89 /// Returns the minimum height achievable for the given width.
90 fn min_intrinsic_height(&self, width: f32) -> f32;
91
92 /// Returns the maximum height achievable for the given width.
93 fn max_intrinsic_height(&self, width: f32) -> f32;
94
95 /// Returns flex parent data if this measurable has weight/fill properties.
96 /// Default implementation returns None (no weight).
97 fn flex_parent_data(&self) -> Option<FlexParentData> {
98 None
99 }
100
101 /// Returns all metadata consumed by the direct parent layout.
102 ///
103 /// The default preserves the older `flex_parent_data` customization
104 /// point so existing custom measurables continue to provide weights.
105 fn parent_data(&self) -> ParentData {
106 self.flex_parent_data().map(Into::into).unwrap_or_default()
107 }
108}
109
110/// Result of running a measurement pass for a single child.
111///
112/// Carries measurement and alignment lines without allocating a trait object.
113pub struct Placeable {
114 width: f32,
115 height: f32,
116 node_id: NodeId,
117 content_offset_x: f32,
118 content_offset_y: f32,
119 alignment_lines: crate::AlignmentLines,
120 place_target: Option<Rc<dyn PlaceTarget>>,
121}
122
123/// What a node-backed [`Placeable`] tells when its parent places it.
124pub trait PlaceTarget {
125 /// Places the node at `(x, y)` in its parent.
126 fn place(&self, x: f32, y: f32);
127}
128
129impl<F: Fn(f32, f32)> PlaceTarget for F {
130 fn place(&self, x: f32, y: f32) {
131 self(x, y);
132 }
133}
134
135impl Placeable {
136 /// Creates a pure-value placeable with no side effects on `place()`.
137 pub fn value(width: f32, height: f32, node_id: NodeId) -> Self {
138 Self {
139 width,
140 height,
141 node_id,
142 content_offset_x: 0.0,
143 content_offset_y: 0.0,
144 alignment_lines: crate::AlignmentLines::default(),
145 place_target: None,
146 }
147 }
148
149 /// Creates a pure-value placeable with a content offset.
150 pub fn value_with_offset(
151 width: f32,
152 height: f32,
153 node_id: NodeId,
154 content_offset: (f32, f32),
155 ) -> Self {
156 Self {
157 width,
158 height,
159 node_id,
160 content_offset_x: content_offset.0,
161 content_offset_y: content_offset.1,
162 alignment_lines: crate::AlignmentLines::default(),
163 place_target: None,
164 }
165 }
166
167 /// Creates a node-backed placeable whose `place()` tells `target`. The
168 /// target is shared, so a node's own measure state can be it without an
169 /// allocation per measure.
170 pub fn with_place_target(
171 width: f32,
172 height: f32,
173 node_id: NodeId,
174 target: Rc<dyn PlaceTarget>,
175 ) -> Self {
176 Self {
177 width,
178 height,
179 node_id,
180 content_offset_x: 0.0,
181 content_offset_y: 0.0,
182 alignment_lines: crate::AlignmentLines::default(),
183 place_target: Some(target),
184 }
185 }
186
187 /// Places the child at the provided coordinates relative to its parent.
188 pub fn place(&self, x: f32, y: f32) {
189 if let Some(target) = &self.place_target {
190 target.place(x, y);
191 }
192 }
193
194 /// Returns the measured width of the child.
195 pub fn width(&self) -> f32 {
196 self.width
197 }
198
199 /// Returns the measured height of the child.
200 pub fn height(&self) -> f32 {
201 self.height
202 }
203
204 /// Returns the identifier for the underlying layout node.
205 pub fn node_id(&self) -> NodeId {
206 self.node_id
207 }
208
209 /// Returns the accumulated content offset from the coordinator chain.
210 pub fn content_offset(&self) -> (f32, f32) {
211 (self.content_offset_x, self.content_offset_y)
212 }
213
214 /// Supplies alignment lines relative to this placeable's top edge.
215 pub fn with_alignment_lines(mut self, alignment_lines: crate::AlignmentLines) -> Self {
216 self.alignment_lines = alignment_lines;
217 self
218 }
219
220 /// Returns the text baselines relative to this placeable's top edge.
221 pub fn alignment_lines(&self) -> crate::AlignmentLines {
222 self.alignment_lines
223 }
224}
225
226/// Scope for measurement operations.
227///
228/// This is Compose's `MeasureScope` -- the receiver `MeasurePolicy.measure` runs
229/// on, which is what lets a measure pass see the density of the subtree it is
230/// measuring rather than some process-wide default. There is no sensible
231/// fallback value for either method: a policy that reads density must be given
232/// the real grid, so both are required rather than defaulted.
233pub trait MeasureScope {
234 /// Returns the current density for converting Dp to pixels.
235 fn density(&self) -> f32;
236
237 /// Returns the current font scale for converting Sp to pixels.
238 fn font_scale(&self) -> f32;
239}
240
241/// Policy responsible for measuring and placing children.
242pub trait MeasurePolicy {
243 /// Runs the measurement pass with the provided children and constraints.
244 fn measure(
245 &self,
246 scope: &dyn MeasureScope,
247 measurables: &[Box<dyn Measurable>],
248 constraints: Constraints,
249 ) -> MeasureResult;
250
251 /// Runs measurement into caller-owned placement storage.
252 ///
253 /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
254 /// policies. Built-in policies override this to avoid allocating a fresh placement
255 /// vector on every measure pass.
256 fn measure_into(
257 &self,
258 scope: &dyn MeasureScope,
259 measurables: &[Box<dyn Measurable>],
260 constraints: Constraints,
261 placements: &mut Vec<Placement>,
262 ) -> Measurement {
263 let result = self.measure(scope, measurables, constraints);
264 placements.clear();
265 placements.extend(result.placements);
266 Measurement {
267 size: result.size,
268 alignment_lines: result.alignment_lines,
269 }
270 }
271
272 /// The incoming constraints the measure that sized the layout `size`
273 /// under `constraints` holds for, when the policy can tell: see
274 /// [`crate::ConstraintsHold`]. `None`, the default, holds for
275 /// `constraints` alone.
276 fn measure_hold(
277 &self,
278 _measurables: &[Box<dyn Measurable>],
279 _constraints: Constraints,
280 _size: Size,
281 ) -> Option<crate::ConstraintsHold> {
282 None
283 }
284
285 /// Computes the minimum intrinsic width of this policy.
286 fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
287
288 /// Computes the maximum intrinsic width of this policy.
289 fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
290
291 /// Computes the minimum intrinsic height of this policy.
292 fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
293
294 /// Computes the maximum intrinsic height of this policy.
295 fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
296}
297
298/// The size and explicit alignment lines produced by a measure policy.
299#[derive(Clone, Copy, Debug, Default, PartialEq)]
300pub struct Measurement {
301 /// The size occupied by the measured layout.
302 pub size: Size,
303 /// Explicit baselines in this layout's coordinates; unspecified lines are inherited.
304 pub alignment_lines: crate::AlignmentLines,
305}
306
307impl From<Size> for Measurement {
308 fn from(size: Size) -> Self {
309 Self {
310 size,
311 alignment_lines: crate::AlignmentLines::default(),
312 }
313 }
314}
315
316/// Result of a measurement operation.
317#[derive(Clone, Debug)]
318pub struct MeasureResult {
319 /// The size the layout takes.
320 pub size: Size,
321 /// Explicit baselines in this layout's coordinates; unspecified lines are inherited.
322 pub alignment_lines: crate::AlignmentLines,
323 /// Where the children go.
324 pub placements: Vec<Placement>,
325}
326
327impl MeasureResult {
328 /// Creates a result from a size or a measurement that already has alignment lines.
329 pub fn new(measurement: impl Into<Measurement>, placements: Vec<Placement>) -> Self {
330 let measurement = measurement.into();
331 Self {
332 size: measurement.size,
333 alignment_lines: measurement.alignment_lines,
334 placements,
335 }
336 }
337
338 /// Reports explicit lines relative to this layout's top edge.
339 pub fn with_alignment_lines(mut self, alignment_lines: crate::AlignmentLines) -> Self {
340 self.alignment_lines = alignment_lines;
341 self
342 }
343}
344
345/// Placement information for a measured child.
346#[derive(Clone, Copy, Debug)]
347pub struct Placement {
348 /// The child placed.
349 pub node_id: NodeId,
350 /// Its left edge in the parent, in logical pixels.
351 pub x: f32,
352 /// Its top edge in the parent, in logical pixels.
353 pub y: f32,
354 /// Its draw order among its siblings: higher draws on top.
355 pub z_index: i32,
356}
357
358impl Placement {
359 /// `node_id` at `(x, y)` in the parent, drawn in `z_index` order.
360 pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
361 Self {
362 node_id,
363 x,
364 y,
365 z_index,
366 }
367 }
368}
369
370/// Result of a layout modifier measurement operation.
371///
372/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
373/// this type is specifically for layout modifiers which wrap a single piece
374/// of content and need to specify where that wrapped content should be placed.
375#[derive(Clone, Copy, Debug)]
376pub struct LayoutModifierMeasureResult {
377 /// The size this modifier will occupy.
378 pub size: Size,
379 /// The offset at which to place the wrapped content relative to
380 /// the top-left corner of this modifier's bounds.
381 /// For example, PaddingNode returns (padding.left, padding.top) here
382 /// to offset the child by the padding amount.
383 pub placement_offset_x: f32,
384 /// The vertical offset of the wrapped content, as `placement_offset_x`.
385 pub placement_offset_y: f32,
386 /// Explicit alignment lines. Unspecified lines are inherited from wrapped content.
387 pub alignment_lines: crate::AlignmentLines,
388}
389
390impl LayoutModifierMeasureResult {
391 /// A modifier of `size` that places its content at the offset.
392 pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
393 Self {
394 size,
395 placement_offset_x,
396 placement_offset_y,
397 alignment_lines: crate::AlignmentLines::default(),
398 }
399 }
400
401 /// Creates a result with zero placement offset (wrapped content placed at 0,0).
402 pub fn with_size(size: Size) -> Self {
403 Self {
404 size,
405 placement_offset_x: 0.0,
406 placement_offset_y: 0.0,
407 alignment_lines: crate::AlignmentLines::default(),
408 }
409 }
410
411 /// Overrides the wrapped content's alignment lines in this modifier's coordinates.
412 pub fn with_alignment_lines(mut self, alignment_lines: crate::AlignmentLines) -> Self {
413 self.alignment_lines = alignment_lines;
414 self
415 }
416}
417
418#[cfg(test)]
419#[path = "tests/core_tests.rs"]
420mod tests;