Skip to main content

cranpose_ui_layout/
core.rs

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