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