Skip to main content

cranpose_ui_layout/
core.rs

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