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 cranpose_core::NodeId;
5use cranpose_ui_graphics::Size;
6use std::rc::Rc;
7
8/// Parent data for flex layouts (Row/Column weights and alignment).
9#[derive(Clone, Copy, Debug, Default)]
10pub struct FlexParentData {
11    /// Weight for distributing remaining space in the main axis.
12    /// If > 0.0, this child participates in weighted distribution.
13    pub weight: f32,
14
15    /// Whether to fill the allocated space when using weight.
16    /// If true, child gets tight constraints; if false, child gets loose constraints.
17    pub fill: bool,
18}
19
20impl FlexParentData {
21    pub fn new(weight: f32, fill: bool) -> Self {
22        Self { weight, fill }
23    }
24
25    pub fn has_weight(&self) -> bool {
26        self.weight > 0.0
27    }
28}
29
30/// Object capable of measuring a layout child and exposing intrinsic sizes.
31pub trait Measurable {
32    /// Measures the child with the provided constraints, returning a [`Placeable`].
33    fn measure(&self, constraints: Constraints) -> Placeable;
34
35    /// Returns the minimum width achievable for the given height.
36    fn min_intrinsic_width(&self, height: f32) -> f32;
37
38    /// Returns the maximum width achievable for the given height.
39    fn max_intrinsic_width(&self, height: f32) -> f32;
40
41    /// Returns the minimum height achievable for the given width.
42    fn min_intrinsic_height(&self, width: f32) -> f32;
43
44    /// Returns the maximum height achievable for the given width.
45    fn max_intrinsic_height(&self, width: f32) -> f32;
46
47    /// Returns flex parent data if this measurable has weight/fill properties.
48    /// Default implementation returns None (no weight).
49    fn flex_parent_data(&self) -> Option<FlexParentData> {
50        None
51    }
52}
53
54/// Result of running a measurement pass for a single child.
55///
56/// Concrete struct replacing the former `dyn Placeable` trait object.
57/// This avoids a heap allocation per node per measure pass — the hot
58/// coordinator path (16-byte value) now lives entirely on the stack.
59pub struct Placeable {
60    width: f32,
61    height: f32,
62    node_id: NodeId,
63    content_offset_x: f32,
64    content_offset_y: f32,
65    /// Optional side-effect executed by `place()`.  `None` for pure-value
66    /// placeables (coordinators, subcompose).
67    place_fn: Option<Rc<dyn Fn(f32, f32)>>,
68}
69
70impl Placeable {
71    /// Creates a pure-value placeable with no side effects on `place()`.
72    pub fn value(width: f32, height: f32, node_id: NodeId) -> Self {
73        Self {
74            width,
75            height,
76            node_id,
77            content_offset_x: 0.0,
78            content_offset_y: 0.0,
79            place_fn: None,
80        }
81    }
82
83    /// Creates a pure-value placeable with a content offset.
84    pub fn value_with_offset(
85        width: f32,
86        height: f32,
87        node_id: NodeId,
88        content_offset: (f32, f32),
89    ) -> Self {
90        Self {
91            width,
92            height,
93            node_id,
94            content_offset_x: content_offset.0,
95            content_offset_y: content_offset.1,
96            place_fn: None,
97        }
98    }
99
100    /// Creates a node-backed placeable whose `place()` triggers a side effect.
101    pub fn with_place_fn(
102        width: f32,
103        height: f32,
104        node_id: NodeId,
105        place_fn: Rc<dyn Fn(f32, f32)>,
106    ) -> Self {
107        Self {
108            width,
109            height,
110            node_id,
111            content_offset_x: 0.0,
112            content_offset_y: 0.0,
113            place_fn: Some(place_fn),
114        }
115    }
116
117    /// Places the child at the provided coordinates relative to its parent.
118    pub fn place(&self, x: f32, y: f32) {
119        if let Some(f) = &self.place_fn {
120            f(x, y);
121        }
122    }
123
124    /// Returns the measured width of the child.
125    pub fn width(&self) -> f32 {
126        self.width
127    }
128
129    /// Returns the measured height of the child.
130    pub fn height(&self) -> f32 {
131        self.height
132    }
133
134    /// Returns the identifier for the underlying layout node.
135    pub fn node_id(&self) -> NodeId {
136        self.node_id
137    }
138
139    /// Returns the accumulated content offset from the coordinator chain.
140    pub fn content_offset(&self) -> (f32, f32) {
141        (self.content_offset_x, self.content_offset_y)
142    }
143}
144
145/// Scope for measurement operations.
146pub trait MeasureScope {
147    /// Returns the current density for converting Dp to pixels.
148    fn density(&self) -> f32 {
149        1.0
150    }
151
152    /// Returns the current font scale for converting Sp to pixels.
153    fn font_scale(&self) -> f32 {
154        1.0
155    }
156}
157
158/// Policy responsible for measuring and placing children.
159pub trait MeasurePolicy {
160    /// Runs the measurement pass with the provided children and constraints.
161    fn measure(
162        &self,
163        measurables: &[Box<dyn Measurable>],
164        constraints: Constraints,
165    ) -> MeasureResult;
166
167    /// Computes the minimum intrinsic width of this policy.
168    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
169
170    /// Computes the maximum intrinsic width of this policy.
171    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
172
173    /// Computes the minimum intrinsic height of this policy.
174    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
175
176    /// Computes the maximum intrinsic height of this policy.
177    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
178}
179
180/// Result of a measurement operation.
181#[derive(Clone, Debug)]
182pub struct MeasureResult {
183    pub size: Size,
184    pub placements: Vec<Placement>,
185}
186
187impl MeasureResult {
188    pub fn new(size: Size, placements: Vec<Placement>) -> Self {
189        Self { size, placements }
190    }
191}
192
193/// Placement information for a measured child.
194#[derive(Clone, Copy, Debug)]
195pub struct Placement {
196    pub node_id: NodeId,
197    pub x: f32,
198    pub y: f32,
199    pub z_index: i32,
200}
201
202impl Placement {
203    pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
204        Self {
205            node_id,
206            x,
207            y,
208            z_index,
209        }
210    }
211}
212
213/// Result of a layout modifier measurement operation.
214///
215/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
216/// this type is specifically for layout modifiers which wrap a single piece
217/// of content and need to specify where that wrapped content should be placed.
218#[derive(Clone, Copy, Debug)]
219pub struct LayoutModifierMeasureResult {
220    /// The size this modifier will occupy.
221    pub size: Size,
222    /// The offset at which to place the wrapped content relative to
223    /// the top-left corner of this modifier's bounds.
224    /// For example, PaddingNode returns (padding.left, padding.top) here
225    /// to offset the child by the padding amount.
226    pub placement_offset_x: f32,
227    pub placement_offset_y: f32,
228}
229
230impl LayoutModifierMeasureResult {
231    pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
232        Self {
233            size,
234            placement_offset_x,
235            placement_offset_y,
236        }
237    }
238
239    /// Creates a result with zero placement offset (wrapped content placed at 0,0).
240    pub fn with_size(size: Size) -> Self {
241        Self {
242            size,
243            placement_offset_x: 0.0,
244            placement_offset_y: 0.0,
245        }
246    }
247}