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 /// Runs measurement into caller-owned placement storage.
168 ///
169 /// The default preserves the public [`MeasurePolicy::measure`] contract for custom
170 /// policies. Built-in policies override this to avoid allocating a fresh placement
171 /// vector on every measure pass.
172 fn measure_into(
173 &self,
174 measurables: &[Box<dyn Measurable>],
175 constraints: Constraints,
176 placements: &mut Vec<Placement>,
177 ) -> Size {
178 let result = self.measure(measurables, constraints);
179 placements.clear();
180 placements.extend(result.placements);
181 result.size
182 }
183
184 /// Computes the minimum intrinsic width of this policy.
185 fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
186
187 /// Computes the maximum intrinsic width of this policy.
188 fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32;
189
190 /// Computes the minimum intrinsic height of this policy.
191 fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
192
193 /// Computes the maximum intrinsic height of this policy.
194 fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32;
195}
196
197/// Result of a measurement operation.
198#[derive(Clone, Debug)]
199pub struct MeasureResult {
200 pub size: Size,
201 pub placements: Vec<Placement>,
202}
203
204impl MeasureResult {
205 pub fn new(size: Size, placements: Vec<Placement>) -> Self {
206 Self { size, placements }
207 }
208}
209
210/// Placement information for a measured child.
211#[derive(Clone, Copy, Debug)]
212pub struct Placement {
213 pub node_id: NodeId,
214 pub x: f32,
215 pub y: f32,
216 pub z_index: i32,
217}
218
219impl Placement {
220 pub fn new(node_id: NodeId, x: f32, y: f32, z_index: i32) -> Self {
221 Self {
222 node_id,
223 x,
224 y,
225 z_index,
226 }
227 }
228}
229
230/// Result of a layout modifier measurement operation.
231///
232/// Unlike `MeasureResult` which is for `MeasurePolicy` (multiple children),
233/// this type is specifically for layout modifiers which wrap a single piece
234/// of content and need to specify where that wrapped content should be placed.
235#[derive(Clone, Copy, Debug)]
236pub struct LayoutModifierMeasureResult {
237 /// The size this modifier will occupy.
238 pub size: Size,
239 /// The offset at which to place the wrapped content relative to
240 /// the top-left corner of this modifier's bounds.
241 /// For example, PaddingNode returns (padding.left, padding.top) here
242 /// to offset the child by the padding amount.
243 pub placement_offset_x: f32,
244 pub placement_offset_y: f32,
245}
246
247impl LayoutModifierMeasureResult {
248 pub fn new(size: Size, placement_offset_x: f32, placement_offset_y: f32) -> Self {
249 Self {
250 size,
251 placement_offset_x,
252 placement_offset_y,
253 }
254 }
255
256 /// Creates a result with zero placement offset (wrapped content placed at 0,0).
257 pub fn with_size(size: Size) -> Self {
258 Self {
259 size,
260 placement_offset_x: 0.0,
261 placement_offset_y: 0.0,
262 }
263 }
264}