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.
184pub trait MeasureScope {
185    /// Returns the current density for converting Dp to pixels.
186    fn density(&self) -> f32 {
187        1.0
188    }
189
190    /// Returns the current font scale for converting Sp to pixels.
191    fn font_scale(&self) -> f32 {
192        1.0
193    }
194}
195
196/// Policy responsible for measuring and placing children.
197pub trait MeasurePolicy {
198    /// Runs the measurement pass with the provided children and constraints.
199    fn measure(
200        &self,
201        measurables: &[Box<dyn Measurable>],
202        constraints: Constraints,
203    ) -> MeasureResult;
204
205    /// Runs measurement into caller-owned placement storage.
206    ///
207    /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
208    /// policies. Built-in policies override this to avoid allocating a fresh placement
209    /// vector on every measure pass.
210    fn measure_into(
211        &self,
212        measurables: &[Box<dyn Measurable>],
213        constraints: Constraints,
214        placements: &mut Vec<Placement>,
215    ) -> Size {
216        let result = self.measure(measurables, constraints);
217        placements.clear();
218        placements.extend(result.placements);
219        result.size
220    }
221
222    /// Computes the minimum intrinsic width of this policy.
223    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
224
225    /// Computes the maximum intrinsic width of this policy.
226    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
227
228    /// Computes the minimum intrinsic height of this policy.
229    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
230
231    /// Computes the maximum intrinsic height of this policy.
232    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
233}
234
235/// Result of a measurement operation.
236#[derive(Clone, Debug)]
237pub struct MeasureResult {
238    pub size: Size,
239    pub placements: Vec<Placement>,
240}
241
242impl MeasureResult {
243    pub fn new(size: Size, placements: Vec<Placement>) -> Self {
244        Self { size, placements }
245    }
246}
247
248/// Placement information for a measured child.
249#[derive(Clone, Copy, Debug)]
250pub struct Placement {
251    pub node_id: NodeId,
252    pub x: f32,
253    pub y: f32,
254    pub z_index: i32,
255}
256
257impl Placement {
258    pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
259        Self {
260            node_id,
261            x,
262            y,
263            z_index,
264        }
265    }
266}
267
268/// Result of a layout modifier measurement operation.
269///
270/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
271/// this type is specifically for layout modifiers which wrap a single piece
272/// of content and need to specify where that wrapped content should be placed.
273#[derive(Clone, Copy, Debug)]
274pub struct LayoutModifierMeasureResult {
275    /// The size this modifier will occupy.
276    pub size: Size,
277    /// The offset at which to place the wrapped content relative to
278    /// the top-left corner of this modifier's bounds.
279    /// For example, PaddingNode returns (padding.left, padding.top) here
280    /// to offset the child by the padding amount.
281    pub placement_offset_x: f32,
282    pub placement_offset_y: f32,
283}
284
285impl LayoutModifierMeasureResult {
286    pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
287        Self {
288            size,
289            placement_offset_x,
290            placement_offset_y,
291        }
292    }
293
294    /// Creates a result with zero placement offset (wrapped content placed at 0,0).
295    pub fn with_size(size: Size) -> Self {
296        Self {
297            size,
298            placement_offset_x: 0.0,
299            placement_offset_y: 0.0,
300        }
301    }
302}