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