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    /// Optional side-effect executed by `place()`.  `None` for pure-value
105    /// placeables (coordinators, subcompose).
106    place_fn: Option<Rc<dyn Fn(f32, f32)>>,
107}
108
109impl Placeable {
110    /// Creates a pure-value placeable with no side effects on `place()`.
111    pub fn value(width: f32, height: f32, node_id: NodeId) -> Self {
112        Self {
113            width,
114            height,
115            node_id,
116            content_offset_x: 0.0,
117            content_offset_y: 0.0,
118            place_fn: None,
119        }
120    }
121
122    /// Creates a pure-value placeable with a content offset.
123    pub fn value_with_offset(
124        width: f32,
125        height: f32,
126        node_id: NodeId,
127        content_offset: (f32, f32),
128    ) -> Self {
129        Self {
130            width,
131            height,
132            node_id,
133            content_offset_x: content_offset.0,
134            content_offset_y: content_offset.1,
135            place_fn: None,
136        }
137    }
138
139    /// Creates a node-backed placeable whose `place()` triggers a side effect.
140    pub fn with_place_fn(
141        width: f32,
142        height: f32,
143        node_id: NodeId,
144        place_fn: Rc<dyn Fn(f32, f32)>,
145    ) -> Self {
146        Self {
147            width,
148            height,
149            node_id,
150            content_offset_x: 0.0,
151            content_offset_y: 0.0,
152            place_fn: Some(place_fn),
153        }
154    }
155
156    /// Places the child at the provided coordinates relative to its parent.
157    pub fn place(&self, x: f32, y: f32) {
158        if let Some(f) = &self.place_fn {
159            f(x, y);
160        }
161    }
162
163    /// Returns the measured width of the child.
164    pub fn width(&self) -> f32 {
165        self.width
166    }
167
168    /// Returns the measured height of the child.
169    pub fn height(&self) -> f32 {
170        self.height
171    }
172
173    /// Returns the identifier for the underlying layout node.
174    pub fn node_id(&self) -> NodeId {
175        self.node_id
176    }
177
178    /// Returns the accumulated content offset from the coordinator chain.
179    pub fn content_offset(&self) -> (f32, f32) {
180        (self.content_offset_x, self.content_offset_y)
181    }
182}
183
184/// Scope for measurement operations.
185///
186/// This is Compose's `MeasureScope` -- the receiver `MeasurePolicy.measure` runs
187/// on, which is what lets a measure pass see the density of the subtree it is
188/// measuring rather than some process-wide default. There is no sensible
189/// fallback value for either method: a policy that reads density must be given
190/// the real grid, so both are required rather than defaulted.
191pub trait MeasureScope {
192    /// Returns the current density for converting Dp to pixels.
193    fn density(&self) -> f32;
194
195    /// Returns the current font scale for converting Sp to pixels.
196    fn font_scale(&self) -> f32;
197}
198
199/// Policy responsible for measuring and placing children.
200pub trait MeasurePolicy {
201    /// Runs the measurement pass with the provided children and constraints.
202    fn measure(
203        &self,
204        scope: &dyn MeasureScope,
205        measurables: &[Box<dyn Measurable>],
206        constraints: Constraints,
207    ) -> MeasureResult;
208
209    /// Runs measurement into caller-owned placement storage.
210    ///
211    /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
212    /// policies. Built-in policies override this to avoid allocating a fresh placement
213    /// vector on every measure pass.
214    fn measure_into(
215        &self,
216        scope: &dyn MeasureScope,
217        measurables: &[Box<dyn Measurable>],
218        constraints: Constraints,
219        placements: &mut Vec<Placement>,
220    ) -> Size {
221        let result = self.measure(scope, measurables, constraints);
222        placements.clear();
223        placements.extend(result.placements);
224        result.size
225    }
226
227    /// Computes the minimum intrinsic width of this policy.
228    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
229
230    /// Computes the maximum intrinsic width of this policy.
231    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
232
233    /// Computes the minimum intrinsic height of this policy.
234    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
235
236    /// Computes the maximum intrinsic height of this policy.
237    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
238}
239
240/// Result of a measurement operation.
241#[derive(Clone, Debug)]
242pub struct MeasureResult {
243    pub size: Size,
244    pub placements: Vec<Placement>,
245}
246
247impl MeasureResult {
248    pub fn new(size: Size, placements: Vec<Placement>) -> Self {
249        Self { size, placements }
250    }
251}
252
253/// Placement information for a measured child.
254#[derive(Clone, Copy, Debug)]
255pub struct Placement {
256    pub node_id: NodeId,
257    pub x: f32,
258    pub y: f32,
259    pub z_index: i32,
260}
261
262impl Placement {
263    pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
264        Self {
265            node_id,
266            x,
267            y,
268            z_index,
269        }
270    }
271}
272
273/// Result of a layout modifier measurement operation.
274///
275/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
276/// this type is specifically for layout modifiers which wrap a single piece
277/// of content and need to specify where that wrapped content should be placed.
278#[derive(Clone, Copy, Debug)]
279pub struct LayoutModifierMeasureResult {
280    /// The size this modifier will occupy.
281    pub size: Size,
282    /// The offset at which to place the wrapped content relative to
283    /// the top-left corner of this modifier's bounds.
284    /// For example, PaddingNode returns (padding.left, padding.top) here
285    /// to offset the child by the padding amount.
286    pub placement_offset_x: f32,
287    pub placement_offset_y: f32,
288}
289
290impl LayoutModifierMeasureResult {
291    pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
292        Self {
293            size,
294            placement_offset_x,
295            placement_offset_y,
296        }
297    }
298
299    /// Creates a result with zero placement offset (wrapped content placed at 0,0).
300    pub fn with_size(size: Size) -> Self {
301        Self {
302            size,
303            placement_offset_x: 0.0,
304            placement_offset_y: 0.0,
305        }
306    }
307}