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