Skip to main content

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;