Skip to main content

cranpose_ui/layout/
policies.rs

1use cranpose_ui_layout::{
2    Axis, Constraints, MeasurePolicy, MeasureResult, MeasureScope, ParentData, Placement,
3};
4use smallvec::SmallVec;
5
6use crate::layout::core::{
7    Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
8};
9
10/// MeasurePolicy for Box layout - overlays children according to alignment.
11#[derive(Clone, Debug, PartialEq)]
12pub struct BoxMeasurePolicy {
13    pub content_alignment: Alignment,
14    pub propagate_min_constraints: bool,
15}
16
17impl BoxMeasurePolicy {
18    pub fn new(content_alignment: Alignment, propagate_min_constraints: bool) -> Self {
19        Self {
20            content_alignment,
21            propagate_min_constraints,
22        }
23    }
24}
25
26impl MeasurePolicy for BoxMeasurePolicy {
27    fn measure(
28        &self,
29        scope: &dyn MeasureScope,
30        measurables: &[Box<dyn Measurable>],
31        constraints: Constraints,
32    ) -> MeasureResult {
33        let mut placements = Vec::new();
34        let size = self.measure_into(scope, measurables, constraints, &mut placements);
35        MeasureResult::new(size, placements)
36    }
37
38    fn measure_into(
39        &self,
40        _scope: &dyn MeasureScope,
41        measurables: &[Box<dyn Measurable>],
42        constraints: Constraints,
43        placements: &mut Vec<Placement>,
44    ) -> crate::modifier::Size {
45        placements.clear();
46        let child_constraints = if self.propagate_min_constraints {
47            constraints
48        } else {
49            Constraints {
50                min_width: 0.0,
51                max_width: constraints.max_width,
52                min_height: 0.0,
53                max_height: constraints.max_height,
54            }
55        };
56
57        let mut max_width = 0.0_f32;
58        let mut max_height = 0.0_f32;
59        let mut placeables: SmallVec<[(cranpose_ui_layout::Placeable, Alignment); 8]> =
60            SmallVec::new();
61
62        for measurable in measurables {
63            let placeable = measurable.measure(child_constraints);
64            max_width = max_width.max(placeable.width());
65            max_height = max_height.max(placeable.height());
66            let alignment = measurable
67                .parent_data()
68                .box_alignment
69                .unwrap_or(self.content_alignment);
70            placeables.push((placeable, alignment));
71        }
72
73        let width = max_width.clamp(constraints.min_width, constraints.max_width);
74        let height = max_height.clamp(constraints.min_height, constraints.max_height);
75
76        placements.reserve(placeables.len());
77        for (placeable, alignment) in placeables {
78            let child_width = placeable.width();
79            let child_height = placeable.height();
80
81            let x = match alignment.horizontal {
82                HorizontalAlignment::Start => 0.0,
83                HorizontalAlignment::CenterHorizontally => ((width - child_width) / 2.0).max(0.0),
84                HorizontalAlignment::End => (width - child_width).max(0.0),
85            };
86
87            let y = match alignment.vertical {
88                VerticalAlignment::Top => 0.0,
89                VerticalAlignment::CenterVertically => ((height - child_height) / 2.0).max(0.0),
90                VerticalAlignment::Bottom => (height - child_height).max(0.0),
91            };
92
93            placeable.place(x, y);
94            placements.push(Placement::new(placeable.node_id(), x, y, 0));
95        }
96
97        crate::modifier::Size { width, height }
98    }
99
100    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
101        measurables
102            .iter()
103            .map(|m| m.min_intrinsic_width(height))
104            .fold(0.0, f32::max)
105    }
106
107    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
108        measurables
109            .iter()
110            .map(|m| m.max_intrinsic_width(height))
111            .fold(0.0, f32::max)
112    }
113
114    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
115        measurables
116            .iter()
117            .map(|m| m.min_intrinsic_height(width))
118            .fold(0.0, f32::max)
119    }
120
121    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
122        measurables
123            .iter()
124            .map(|m| m.max_intrinsic_height(width))
125            .fold(0.0, f32::max)
126    }
127}
128
129/// Unified Flex layout policy that powers both Row and Column.
130///
131/// This policy implements Jetpack Compose's flex layout semantics:
132/// - Measures children with proper loose constraints (min = 0 on both axes)
133/// - Supports weighted distribution of remaining space
134/// - Handles bounded/unbounded main axis correctly
135/// - Implements correct intrinsics for both axes
136///
137/// ## Overflow Behavior
138///
139/// Like Jetpack Compose, this policy **allows children to overflow** their container bounds:
140/// - Children can be positioned outside the parent's measured size
141/// - Overflowing content is rendered (unless clipped by a modifier)
142/// - When content overflows, distribution arrangements switch to `Start` to avoid negative spacing
143/// - `SpacedBy` keeps its fixed inter-child spacing even when content overflows
144///
145/// Example: A Row with 300px of content in a 200px container will:
146/// 1. Measure children at their natural sizes
147/// 2. Detect overflow (300px > 200px)
148/// 3. Switch to Start arrangement (pack children at the start)
149/// 4. Position last children beyond the 200px boundary
150///
151/// To prevent overflow:
152/// - Use weights for flexible sizing: `.weight(1.0, true)`
153/// - Use `fillMaxWidth()`/`fillMaxHeight()` modifiers
154/// - Design UI to fit within available space
155/// - Add a clip modifier to hide overflowing content
156///
157/// ## Weighted Children
158///
159/// When the main axis is bounded and children have weights:
160/// 1. Fixed children (no weight) are measured first
161/// 2. Remaining space is distributed proportionally to weights
162/// 3. Each weighted child gets: `remaining * (weight / total_weight)`
163/// 4. If `fill=true`, child gets tight constraints; if `fill=false`, loose constraints
164///
165/// When the main axis is unbounded, weights are ignored (all children wrap content).
166#[derive(Clone, Debug, PartialEq)]
167pub struct FlexMeasurePolicy {
168    /// Main axis direction (Horizontal for Row, Vertical for Column)
169    pub axis: Axis,
170    /// Arrangement along the main axis
171    pub main_axis_arrangement: LinearArrangement,
172    /// Alignment along the cross axis (used as default for children without explicit alignment)
173    pub cross_axis_alignment: CrossAxisAlignment,
174}
175
176/// Cross-axis alignment for flex layouts.
177/// This is axis-agnostic and gets interpreted based on the flex axis.
178#[derive(Clone, Copy, Debug, PartialEq)]
179pub enum CrossAxisAlignment {
180    /// Align to the start of the cross axis (Top for Row, Start for Column)
181    Start,
182    /// Align to the center of the cross axis
183    Center,
184    /// Align to the end of the cross axis (Bottom for Row, End for Column)
185    End,
186}
187
188impl CrossAxisAlignment {
189    fn align(&self, available: f32, child: f32) -> f32 {
190        match self {
191            CrossAxisAlignment::Start => 0.0,
192            CrossAxisAlignment::Center => ((available - child) / 2.0).max(0.0),
193            CrossAxisAlignment::End => (available - child).max(0.0),
194        }
195    }
196}
197
198impl From<HorizontalAlignment> for CrossAxisAlignment {
199    fn from(alignment: HorizontalAlignment) -> Self {
200        match alignment {
201            HorizontalAlignment::Start => CrossAxisAlignment::Start,
202            HorizontalAlignment::CenterHorizontally => CrossAxisAlignment::Center,
203            HorizontalAlignment::End => CrossAxisAlignment::End,
204        }
205    }
206}
207
208impl From<VerticalAlignment> for CrossAxisAlignment {
209    fn from(alignment: VerticalAlignment) -> Self {
210        match alignment {
211            VerticalAlignment::Top => CrossAxisAlignment::Start,
212            VerticalAlignment::CenterVertically => CrossAxisAlignment::Center,
213            VerticalAlignment::Bottom => CrossAxisAlignment::End,
214        }
215    }
216}
217
218impl FlexMeasurePolicy {
219    pub fn new(
220        axis: Axis,
221        main_axis_arrangement: LinearArrangement,
222        cross_axis_alignment: CrossAxisAlignment,
223    ) -> Self {
224        Self {
225            axis,
226            main_axis_arrangement,
227            cross_axis_alignment,
228        }
229    }
230
231    /// Creates a FlexMeasurePolicy for Row (horizontal main axis).
232    pub fn row(
233        horizontal_arrangement: LinearArrangement,
234        vertical_alignment: VerticalAlignment,
235    ) -> Self {
236        Self::new(
237            Axis::Horizontal,
238            horizontal_arrangement,
239            vertical_alignment.into(),
240        )
241    }
242
243    /// Creates a FlexMeasurePolicy for Column (vertical main axis).
244    pub fn column(
245        vertical_arrangement: LinearArrangement,
246        horizontal_alignment: HorizontalAlignment,
247    ) -> Self {
248        Self::new(
249            Axis::Vertical,
250            vertical_arrangement,
251            horizontal_alignment.into(),
252        )
253    }
254
255    fn get_axis_constraints(&self, constraints: Constraints) -> (f32, f32, f32, f32) {
256        match self.axis {
257            Axis::Horizontal => (
258                constraints.min_width,
259                constraints.max_width,
260                constraints.min_height,
261                constraints.max_height,
262            ),
263            Axis::Vertical => (
264                constraints.min_height,
265                constraints.max_height,
266                constraints.min_width,
267                constraints.max_width,
268            ),
269        }
270    }
271
272    fn make_constraints(
273        &self,
274        min_main: f32,
275        max_main: f32,
276        min_cross: f32,
277        max_cross: f32,
278    ) -> Constraints {
279        match self.axis {
280            Axis::Horizontal => Constraints {
281                min_width: min_main,
282                max_width: max_main,
283                min_height: min_cross,
284                max_height: max_cross,
285            },
286            Axis::Vertical => Constraints {
287                min_width: min_cross,
288                max_width: max_cross,
289                min_height: min_main,
290                max_height: max_main,
291            },
292        }
293    }
294
295    fn get_main_axis_size(&self, width: f32, height: f32) -> f32 {
296        match self.axis {
297            Axis::Horizontal => width,
298            Axis::Vertical => height,
299        }
300    }
301
302    fn get_cross_axis_size(&self, width: f32, height: f32) -> f32 {
303        match self.axis {
304            Axis::Horizontal => height,
305            Axis::Vertical => width,
306        }
307    }
308
309    fn get_spacing(&self) -> f32 {
310        match self.main_axis_arrangement {
311            LinearArrangement::SpacedBy(value) => value.max(0.0),
312            _ => 0.0,
313        }
314    }
315}
316
317impl MeasurePolicy for FlexMeasurePolicy {
318    fn measure(
319        &self,
320        scope: &dyn MeasureScope,
321        measurables: &[Box<dyn Measurable>],
322        constraints: Constraints,
323    ) -> MeasureResult {
324        let mut placements = Vec::new();
325        let size = self.measure_into(scope, measurables, constraints, &mut placements);
326        MeasureResult::new(size, placements)
327    }
328
329    fn measure_into(
330        &self,
331        _scope: &dyn MeasureScope,
332        measurables: &[Box<dyn Measurable>],
333        constraints: Constraints,
334        placements: &mut Vec<Placement>,
335    ) -> crate::modifier::Size {
336        placements.clear();
337        if measurables.is_empty() {
338            let (width, height) = constraints.constrain(0.0, 0.0);
339            return crate::modifier::Size { width, height };
340        }
341
342        let (min_main, max_main, min_cross, max_cross) = self.get_axis_constraints(constraints);
343        let main_axis_bounded = max_main.is_finite();
344        let spacing = self.get_spacing();
345
346        let mut fixed_children: SmallVec<[usize; 8]> = SmallVec::new();
347        let parent_data: SmallVec<[ParentData; 8]> = measurables
348            .iter()
349            .map(|child| child.parent_data())
350            .collect();
351        let mut weighted_children: SmallVec<[(usize, ParentData); 8]> = SmallVec::new();
352
353        for (idx, data) in parent_data.iter().copied().enumerate() {
354            if data.has_weight() {
355                weighted_children.push((idx, data));
356            } else {
357                fixed_children.push(idx);
358            }
359        }
360
361        let child_constraints = self.make_constraints(0.0, max_main, 0.0, max_cross);
362
363        let mut placeables: SmallVec<[Option<cranpose_ui_layout::Placeable>; 8]> = SmallVec::new();
364        placeables.resize_with(measurables.len(), || None);
365        let mut fixed_main_size = 0.0_f32;
366        let mut max_cross_size = 0.0_f32;
367
368        for &idx in &fixed_children {
369            let measurable = &measurables[idx];
370            let placeable = measurable.measure(child_constraints);
371            let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
372            let cross_size = self.get_cross_axis_size(placeable.width(), placeable.height());
373
374            fixed_main_size += main_size;
375            max_cross_size = max_cross_size.max(cross_size);
376            placeables[idx] = Some(placeable);
377        }
378
379        let num_children = measurables.len();
380        let total_spacing = if num_children > 1 {
381            spacing * (num_children - 1) as f32
382        } else {
383            0.0
384        };
385
386        if !weighted_children.is_empty() {
387            if main_axis_bounded {
388                let used_main = fixed_main_size + total_spacing;
389                let remaining_main = (max_main - used_main).max(0.0);
390
391                let total_weight: f32 = weighted_children.iter().map(|(_, data)| data.weight).sum();
392
393                for &(idx, parent_data) in &weighted_children {
394                    let measurable = &measurables[idx];
395                    let allocated = if total_weight > 0.0 {
396                        remaining_main * (parent_data.weight / total_weight)
397                    } else {
398                        0.0
399                    };
400
401                    let weighted_constraints = if parent_data.fill {
402                        self.make_constraints(allocated, allocated, 0.0, max_cross)
403                    } else {
404                        self.make_constraints(0.0, allocated, 0.0, max_cross)
405                    };
406
407                    let placeable = measurable.measure(weighted_constraints);
408                    let cross_size =
409                        self.get_cross_axis_size(placeable.width(), placeable.height());
410                    max_cross_size = max_cross_size.max(cross_size);
411                    placeables[idx] = Some(placeable);
412                }
413            } else {
414                for &(idx, _) in &weighted_children {
415                    let measurable = &measurables[idx];
416                    let placeable = measurable.measure(child_constraints);
417                    let cross_size =
418                        self.get_cross_axis_size(placeable.width(), placeable.height());
419                    max_cross_size = max_cross_size.max(cross_size);
420                    placeables[idx] = Some(placeable);
421                }
422            }
423        }
424
425        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = placeables
426            .into_iter()
427            .enumerate()
428            .map(|(idx, placeable)| {
429                placeable.unwrap_or_else(|| measurables[idx].measure(child_constraints))
430            })
431            .collect();
432
433        let total_main: f32 = placeables
434            .iter()
435            .map(|p| self.get_main_axis_size(p.width(), p.height()))
436            .sum::<f32>()
437            + total_spacing;
438
439        let container_main = total_main.clamp(min_main, max_main);
440        let container_cross = max_cross_size.clamp(min_cross, max_cross);
441
442        let child_main_sizes: SmallVec<[f32; 8]> = placeables
443            .iter()
444            .map(|p| self.get_main_axis_size(p.width(), p.height()))
445            .collect();
446
447        let mut main_positions: SmallVec<[f32; 8]> =
448            SmallVec::with_capacity(child_main_sizes.len());
449        main_positions.resize(child_main_sizes.len(), 0.0);
450
451        let arrangement = if total_main > container_main
452            && !matches!(self.main_axis_arrangement, LinearArrangement::SpacedBy(_))
453        {
454            LinearArrangement::Start
455        } else {
456            self.main_axis_arrangement
457        };
458        arrangement.arrange(container_main, &child_main_sizes, &mut main_positions);
459
460        placements.reserve(placeables.len());
461        for (idx, (placeable, main_pos)) in placeables.into_iter().zip(main_positions).enumerate() {
462            let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
463            let cross_axis_alignment = match self.axis {
464                Axis::Horizontal => parent_data[idx]
465                    .row_alignment
466                    .map_or(self.cross_axis_alignment, Into::into),
467                Axis::Vertical => parent_data[idx]
468                    .column_alignment
469                    .map_or(self.cross_axis_alignment, Into::into),
470            };
471            let cross_pos = cross_axis_alignment.align(container_cross, child_cross);
472
473            let (x, y) = match self.axis {
474                Axis::Horizontal => (main_pos, cross_pos),
475                Axis::Vertical => (cross_pos, main_pos),
476            };
477
478            placeable.place(x, y);
479            placements.push(Placement::new(placeable.node_id(), x, y, 0));
480        }
481
482        let (width, height) = match self.axis {
483            Axis::Horizontal => (container_main, container_cross),
484            Axis::Vertical => (container_cross, container_main),
485        };
486
487        crate::modifier::Size { width, height }
488    }
489
490    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
491        let spacing = self.get_spacing();
492        let total_spacing = if measurables.len() > 1 {
493            spacing * (measurables.len() - 1) as f32
494        } else {
495            0.0
496        };
497
498        match self.axis {
499            Axis::Horizontal => {
500                measurables
501                    .iter()
502                    .map(|m| m.min_intrinsic_width(height))
503                    .sum::<f32>()
504                    + total_spacing
505            }
506            Axis::Vertical => measurables
507                .iter()
508                .map(|m| m.min_intrinsic_width(height))
509                .fold(0.0, f32::max),
510        }
511    }
512
513    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
514        let spacing = self.get_spacing();
515        let total_spacing = if measurables.len() > 1 {
516            spacing * (measurables.len() - 1) as f32
517        } else {
518            0.0
519        };
520
521        match self.axis {
522            Axis::Horizontal => {
523                measurables
524                    .iter()
525                    .map(|m| m.max_intrinsic_width(height))
526                    .sum::<f32>()
527                    + total_spacing
528            }
529            Axis::Vertical => measurables
530                .iter()
531                .map(|m| m.max_intrinsic_width(height))
532                .fold(0.0, f32::max),
533        }
534    }
535
536    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
537        let spacing = self.get_spacing();
538        let total_spacing = if measurables.len() > 1 {
539            spacing * (measurables.len() - 1) as f32
540        } else {
541            0.0
542        };
543
544        match self.axis {
545            Axis::Horizontal => measurables
546                .iter()
547                .map(|m| m.min_intrinsic_height(width))
548                .fold(0.0, f32::max),
549            Axis::Vertical => {
550                measurables
551                    .iter()
552                    .map(|m| m.min_intrinsic_height(width))
553                    .sum::<f32>()
554                    + total_spacing
555            }
556        }
557    }
558
559    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
560        let spacing = self.get_spacing();
561        let total_spacing = if measurables.len() > 1 {
562            spacing * (measurables.len() - 1) as f32
563        } else {
564            0.0
565        };
566
567        match self.axis {
568            Axis::Horizontal => measurables
569                .iter()
570                .map(|m| m.max_intrinsic_height(width))
571                .fold(0.0, f32::max),
572            Axis::Vertical => {
573                measurables
574                    .iter()
575                    .map(|m| m.max_intrinsic_height(width))
576                    .sum::<f32>()
577                    + total_spacing
578            }
579        }
580    }
581}
582
583/// MeasurePolicy for FlowRow: children flow horizontally and wrap onto the
584/// next line when the available width runs out (Jetpack Compose `FlowRow`).
585///
586/// - Children are measured with loose constraints (min = 0) capped at the
587///   incoming max width/height, then packed left-to-right.
588/// - A child that no longer fits on the current line starts a new line; a
589///   child wider than the whole line gets a line of its own (and may
590///   overflow, like Compose).
591/// - `main_axis_spacing` separates children on the same line and
592///   `cross_axis_spacing` separates lines; children are top-aligned within
593///   their line.
594/// - With an unbounded max width everything stays on one line.
595#[derive(Clone, Debug, PartialEq)]
596pub struct FlowRowMeasurePolicy {
597    /// Horizontal gap between adjacent children on the same line, in dp.
598    pub main_axis_spacing: f32,
599    /// Vertical gap between consecutive lines, in dp.
600    pub cross_axis_spacing: f32,
601}
602
603impl FlowRowMeasurePolicy {
604    pub fn new(main_axis_spacing: f32, cross_axis_spacing: f32) -> Self {
605        Self {
606            main_axis_spacing: main_axis_spacing.max(0.0),
607            cross_axis_spacing: cross_axis_spacing.max(0.0),
608        }
609    }
610
611    fn wrapped_intrinsic_height(
612        &self,
613        measurables: &[Box<dyn Measurable>],
614        available_width: f32,
615        use_min_height: bool,
616    ) -> f32 {
617        let mut cursor_x = 0.0_f32;
618        let mut line_top = 0.0_f32;
619        let mut line_height = 0.0_f32;
620
621        for measurable in measurables {
622            let child_width = measurable.max_intrinsic_width(f32::INFINITY);
623            let child_height = if use_min_height {
624                measurable.min_intrinsic_height(child_width)
625            } else {
626                measurable.max_intrinsic_height(child_width)
627            };
628
629            if cursor_x > 0.0 && cursor_x + self.main_axis_spacing + child_width > available_width {
630                line_top += line_height + self.cross_axis_spacing;
631                cursor_x = 0.0;
632                line_height = 0.0;
633            }
634            cursor_x += if cursor_x > 0.0 {
635                self.main_axis_spacing + child_width
636            } else {
637                child_width
638            };
639            line_height = line_height.max(child_height);
640        }
641
642        line_top + line_height
643    }
644}
645
646impl MeasurePolicy for FlowRowMeasurePolicy {
647    fn measure(
648        &self,
649        scope: &dyn MeasureScope,
650        measurables: &[Box<dyn Measurable>],
651        constraints: Constraints,
652    ) -> MeasureResult {
653        let mut placements = Vec::new();
654        let size = self.measure_into(scope, measurables, constraints, &mut placements);
655        MeasureResult::new(size, placements)
656    }
657
658    fn measure_into(
659        &self,
660        _scope: &dyn MeasureScope,
661        measurables: &[Box<dyn Measurable>],
662        constraints: Constraints,
663        placements: &mut Vec<Placement>,
664    ) -> crate::modifier::Size {
665        placements.clear();
666        if measurables.is_empty() {
667            let (width, height) = constraints.constrain(0.0, 0.0);
668            return crate::modifier::Size { width, height };
669        }
670
671        let child_constraints = Constraints {
672            min_width: 0.0,
673            max_width: constraints.max_width,
674            min_height: 0.0,
675            max_height: constraints.max_height,
676        };
677
678        let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = measurables
679            .iter()
680            .map(|measurable| measurable.measure(child_constraints))
681            .collect();
682
683        let mut cursor_x = 0.0_f32;
684        let mut line_top = 0.0_f32;
685        let mut line_height = 0.0_f32;
686        let mut max_line_width = 0.0_f32;
687
688        placements.reserve(placeables.len());
689        for placeable in placeables {
690            let child_width = placeable.width();
691            let child_height = placeable.height();
692
693            if cursor_x > 0.0
694                && cursor_x + self.main_axis_spacing + child_width > constraints.max_width
695            {
696                max_line_width = max_line_width.max(cursor_x);
697                line_top += line_height + self.cross_axis_spacing;
698                cursor_x = 0.0;
699                line_height = 0.0;
700            }
701
702            let x = if cursor_x > 0.0 {
703                cursor_x + self.main_axis_spacing
704            } else {
705                0.0
706            };
707            placeable.place(x, line_top);
708            placements.push(Placement::new(placeable.node_id(), x, line_top, 0));
709
710            cursor_x = x + child_width;
711            line_height = line_height.max(child_height);
712        }
713        max_line_width = max_line_width.max(cursor_x);
714
715        let width = max_line_width.clamp(constraints.min_width, constraints.max_width);
716        let height = (line_top + line_height).clamp(constraints.min_height, constraints.max_height);
717        crate::modifier::Size { width, height }
718    }
719
720    fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
721        measurables
722            .iter()
723            .map(|m| m.min_intrinsic_width(height))
724            .fold(0.0, f32::max)
725    }
726
727    fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
728        let total_spacing = if measurables.len() > 1 {
729            self.main_axis_spacing * (measurables.len() - 1) as f32
730        } else {
731            0.0
732        };
733        measurables
734            .iter()
735            .map(|m| m.max_intrinsic_width(height))
736            .sum::<f32>()
737            + total_spacing
738    }
739
740    fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
741        self.wrapped_intrinsic_height(measurables, width, true)
742    }
743
744    fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
745        self.wrapped_intrinsic_height(measurables, width, false)
746    }
747}
748
749/// MeasurePolicy for leaf nodes with fixed intrinsic size (like Spacer).
750/// This policy respects the provided constraints but has a preferred intrinsic size.
751#[derive(Clone, Debug, PartialEq)]
752pub struct LeafMeasurePolicy {
753    pub intrinsic_size: crate::modifier::Size,
754}
755
756impl LeafMeasurePolicy {
757    pub fn new(intrinsic_size: crate::modifier::Size) -> Self {
758        Self { intrinsic_size }
759    }
760}
761
762impl MeasurePolicy for LeafMeasurePolicy {
763    fn measure(
764        &self,
765        scope: &dyn MeasureScope,
766        _measurables: &[Box<dyn Measurable>],
767        constraints: Constraints,
768    ) -> MeasureResult {
769        let mut placements = Vec::new();
770        let size = self.measure_into(scope, &[], constraints, &mut placements);
771        MeasureResult::new(size, placements)
772    }
773
774    fn measure_into(
775        &self,
776        _scope: &dyn MeasureScope,
777        _measurables: &[Box<dyn Measurable>],
778        constraints: Constraints,
779        placements: &mut Vec<Placement>,
780    ) -> crate::modifier::Size {
781        placements.clear();
782        let (width, height) =
783            constraints.constrain(self.intrinsic_size.width, self.intrinsic_size.height);
784
785        crate::modifier::Size { width, height }
786    }
787
788    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
789        self.intrinsic_size.width
790    }
791
792    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
793        self.intrinsic_size.width
794    }
795
796    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
797        self.intrinsic_size.height
798    }
799
800    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
801        self.intrinsic_size.height
802    }
803}
804
805/// EmptyMeasurePolicy that delegates all measurement to modifier nodes.
806///
807/// This is used when a Layout has no child layout logic - all measurement
808/// is handled by modifier nodes (e.g., TextModifierNode for Text widgets).
809/// Matches Jetpack Compose's EmptyMeasurePolicy pattern used in BasicText.
810#[derive(Clone, Debug, PartialEq)]
811pub struct EmptyMeasurePolicy;
812
813impl EmptyMeasurePolicy {
814    pub fn new() -> Self {
815        Self
816    }
817}
818
819impl Default for EmptyMeasurePolicy {
820    fn default() -> Self {
821        Self::new()
822    }
823}
824
825impl MeasurePolicy for EmptyMeasurePolicy {
826    fn measure(
827        &self,
828        scope: &dyn MeasureScope,
829        _measurables: &[Box<dyn Measurable>],
830        constraints: Constraints,
831    ) -> MeasureResult {
832        let mut placements = Vec::new();
833        let size = self.measure_into(scope, &[], constraints, &mut placements);
834        MeasureResult::new(size, placements)
835    }
836
837    fn measure_into(
838        &self,
839        _scope: &dyn MeasureScope,
840        _measurables: &[Box<dyn Measurable>],
841        constraints: Constraints,
842        placements: &mut Vec<Placement>,
843    ) -> crate::modifier::Size {
844        placements.clear();
845        let (width, height) = constraints.constrain(0.0, 0.0);
846
847        crate::modifier::Size { width, height }
848    }
849
850    fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
851        0.0
852    }
853
854    fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
855        0.0
856    }
857
858    fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
859        0.0
860    }
861
862    fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
863        0.0
864    }
865}
866
867#[cfg(test)]
868#[path = "tests/policies_tests.rs"]
869mod tests;