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. A layout compares
300/// the policy it is given with the one its node holds, through [`Any`].
301pub trait MeasurePolicy: Any {
302    /// Runs the measurement pass with the provided children and constraints.
303    fn measure(
304        &self,
305        scope: &dyn MeasureScope,
306        measurables: &[Box<dyn Measurable>],
307        constraints: Constraints,
308    ) -> MeasureResult;
309
310    /// Runs measurement into caller-owned placement storage.
311    ///
312    /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
313    /// policies. Built-in policies override this to avoid allocating a fresh placement
314    /// vector on every measure pass.
315    fn measure_into(
316        &self,
317        scope: &dyn MeasureScope,
318        measurables: &[Box<dyn Measurable>],
319        constraints: Constraints,
320        placements: &mut Vec<Placement>,
321    ) -> Measurement {
322        let result = self.measure(scope, measurables, constraints);
323        placements.clear();
324        placements.extend(result.placements);
325        Measurement {
326            size: result.size,
327            alignment_lines: result.alignment_lines,
328        }
329    }
330
331    /// The incoming constraints the measure that sized the layout `size`
332    /// under `constraints` holds for, when the policy can tell: see
333    /// [`crate::ConstraintsHold`]. `None`, the default, holds for
334    /// `constraints` alone.
335    fn measure_hold(
336        &self,
337        _measurables: &[Box<dyn Measurable>],
338        _constraints: Constraints,
339        _size: Size,
340    ) -> Option<crate::ConstraintsHold> {
341        None
342    }
343
344    /// Whether this policy may lay its children out otherwise when a child's
345    /// parent data goes from `before` to `after`. The default reads all of
346    /// it; a policy that reads part of it answers for that part, so a change
347    /// elsewhere keeps its measurement.
348    fn reads_parent_data_change(&self, before: &ParentData, after: &ParentData) -> bool {
349        before != after
350    }
351
352    /// Whether the measure under `constraints` gave a size and child
353    /// positions that no child's size enters: a child that measures to
354    /// another size then leaves this layout as it is, and the layout keeps
355    /// its measurement. `false`, the default, measures the layout again.
356    fn ignores_child_sizes(
357        &self,
358        _measurables: &[Box<dyn Measurable>],
359        _constraints: Constraints,
360    ) -> bool {
361        false
362    }
363
364    /// Computes the minimum intrinsic width of this policy.
365    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
366
367    /// Computes the maximum intrinsic width of this policy.
368    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
369
370    /// Computes the minimum intrinsic height of this policy.
371    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
372
373    /// Computes the maximum intrinsic height of this policy.
374    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
375}
376
377/// The size and explicit alignment lines produced by a measure policy.
378#[derive(Clone, Copy, Debug, Default, PartialEq)]
379pub struct Measurement {
380    /// The size occupied by the measured layout.
381    pub size: Size,
382    /// Explicit baselines in this layout's coordinates; unspecified lines are inherited.
383    pub alignment_lines: crate::AlignmentLines,
384}
385
386impl From<Size> for Measurement {
387    fn from(size: Size) -> Self {
388        Self {
389            size,
390            alignment_lines: crate::AlignmentLines::default(),
391        }
392    }
393}
394
395/// Result of a measurement operation.
396#[derive(Clone, Debug)]
397pub struct MeasureResult {
398    /// The size the layout takes.
399    pub size: Size,
400    /// Explicit baselines in this layout's coordinates; unspecified lines are inherited.
401    pub alignment_lines: crate::AlignmentLines,
402    /// Where the children go.
403    pub placements: Vec<Placement>,
404}
405
406impl MeasureResult {
407    /// Creates a result from a size or a measurement that already has alignment lines.
408    pub fn new(measurement: impl Into<Measurement>, placements: Vec<Placement>) -> Self {
409        let measurement = measurement.into();
410        Self {
411            size: measurement.size,
412            alignment_lines: measurement.alignment_lines,
413            placements,
414        }
415    }
416
417    /// Reports explicit lines relative to this layout's top edge.
418    pub fn with_alignment_lines(mut self, alignment_lines: crate::AlignmentLines) -> Self {
419        self.alignment_lines = alignment_lines;
420        self
421    }
422}
423
424/// Placement information for a measured child.
425#[derive(Clone, Copy, Debug)]
426pub struct Placement {
427    /// The child placed.
428    pub node_id: NodeId,
429    /// Its left edge in the parent, in logical pixels.
430    pub x: f32,
431    /// Its top edge in the parent, in logical pixels.
432    pub y: f32,
433    /// Its draw order among its siblings: higher draws on top.
434    pub z_index: i32,
435}
436
437impl Placement {
438    /// `node_id` at `(x, y)` in the parent, drawn in `z_index` order.
439    pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
440        Self {
441            node_id,
442            x,
443            y,
444            z_index,
445        }
446    }
447}
448
449/// Result of a layout modifier measurement operation.
450///
451/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
452/// this type is specifically for layout modifiers which wrap a single piece
453/// of content and need to specify where that wrapped content should be placed.
454#[derive(Clone, Copy, Debug)]
455pub struct LayoutModifierMeasureResult {
456    /// The size this modifier will occupy.
457    pub size: Size,
458    /// The offset at which to place the wrapped content relative to
459    /// the top-left corner of this modifier's bounds.
460    /// For example, PaddingNode returns (padding.left, padding.top) here
461    /// to offset the child by the padding amount.
462    pub placement_offset_x: f32,
463    /// The vertical offset of the wrapped content, as `placement_offset_x`.
464    pub placement_offset_y: f32,
465    /// Explicit alignment lines. Unspecified lines are inherited from wrapped content.
466    pub alignment_lines: crate::AlignmentLines,
467}
468
469impl LayoutModifierMeasureResult {
470    /// A modifier of `size` that places its content at the offset.
471    pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
472        Self {
473            size,
474            placement_offset_x,
475            placement_offset_y,
476            alignment_lines: crate::AlignmentLines::default(),
477        }
478    }
479
480    /// Creates a result with zero placement offset (wrapped content placed at 0,0).
481    pub fn with_size(size: Size) -> Self {
482        Self {
483            size,
484            placement_offset_x: 0.0,
485            placement_offset_y: 0.0,
486            alignment_lines: crate::AlignmentLines::default(),
487        }
488    }
489
490    /// Overrides the wrapped content's alignment lines in this modifier's coordinates.
491    pub fn with_alignment_lines(mut self, alignment_lines: crate::AlignmentLines) -> Self {
492        self.alignment_lines = alignment_lines;
493        self
494    }
495}
496
497#[cfg(test)]
498#[path = "tests/core_tests.rs"]
499mod tests;