Skip to main content

inset_rendering/
wrap.rs

1//! Flutter counterpart: `rendering/wrap.dart`.
2
3use std::any::{Any, TypeId};
4use std::fmt;
5use std::ops::{Add, Sub};
6
7use inset_embedder::{Clip, Offset, Size, TextBaseline, TextDirection};
8use inset_foundation::{App, Handle, PRECISION_ERROR_TOLERANCE};
9use inset_painting::{Axis, VerticalDirection};
10
11use crate::box_::{
12    AnyRenderBox, BaselineOffset, BoxConstraints, BoxHitTestResult, BoxParentData,
13    ContainerBoxParentData, RenderBox, RenderBoxContainerDefaultsMixin, RenderBoxData,
14};
15use crate::layer::{ClipRectLayer, LayerHandle};
16use crate::layout_helper::{ChildLayoutHelper, ChildLayouter};
17use crate::object::{
18    AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectData,
19    ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, RenderObjectData,
20};
21use crate::painting_context::PaintingContext;
22use crate::pipeline_owner::PipelineOwner;
23
24/// A 2D vector that uses a [`RenderWrap`]'s main axis and cross axis as its first and second
25/// coordinate axes. It represents the same vector as `(main_axis_extent, cross_axis_extent)`.
26#[derive(Clone, Copy)]
27struct AxisSize(Size);
28
29impl AxisSize {
30    const EMPTY: AxisSize = AxisSize(Size::ZERO);
31
32    fn new(main_axis_extent: f64, cross_axis_extent: f64) -> AxisSize {
33        AxisSize(Size::new(main_axis_extent, cross_axis_extent))
34    }
35
36    fn from_size(size: Size, direction: Axis) -> AxisSize {
37        AxisSize(AxisSize::convert(size, direction))
38    }
39
40    fn convert(size: Size, direction: Axis) -> Size {
41        match direction {
42            Axis::Horizontal => size,
43            Axis::Vertical => size.flipped(),
44        }
45    }
46
47    fn main_axis_extent(self) -> f64 {
48        self.0.width()
49    }
50
51    fn cross_axis_extent(self) -> f64 {
52        self.0.height()
53    }
54
55    fn to_size(self, direction: Axis) -> Size {
56        AxisSize::convert(self.0, direction)
57    }
58
59    fn apply_constraints(self, constraints: BoxConstraints, direction: Axis) -> AxisSize {
60        let effective_constraints = match direction {
61            Axis::Horizontal => constraints,
62            Axis::Vertical => constraints.flipped(),
63        };
64        AxisSize(effective_constraints.constrain(self.0))
65    }
66
67    fn flipped(self) -> AxisSize {
68        AxisSize(self.0.flipped())
69    }
70}
71
72impl Add for AxisSize {
73    type Output = AxisSize;
74
75    fn add(self, other: AxisSize) -> AxisSize {
76        AxisSize(Size::new(
77            self.0.width() + other.0.width(),
78            self.0.height().max(other.0.height()),
79        ))
80    }
81}
82
83impl Sub for AxisSize {
84    type Output = AxisSize;
85
86    fn sub(self, other: AxisSize) -> AxisSize {
87        AxisSize(Size::new(
88            self.0.width() - other.0.width(),
89            self.0.height() - other.0.height(),
90        ))
91    }
92}
93
94/// How [`RenderWrap`] should align objects.
95///
96/// Used both to align children within a run in the main axis as well as to align the runs
97/// themselves in the cross axis.
98#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
99pub enum WrapAlignment {
100    /// Place the objects as close to the start of the axis as possible.
101    ///
102    /// If this value is used in a horizontal direction, a [`TextDirection`] must be available
103    /// to determine if the start is the left or the right.
104    ///
105    /// If this value is used in a vertical direction, a [`VerticalDirection`] must be available
106    /// to determine if the start is the top or the bottom.
107    #[default]
108    Start,
109
110    /// Place the objects as close to the end of the axis as possible.
111    ///
112    /// If this value is used in a horizontal direction, a [`TextDirection`] must be available
113    /// to determine if the end is the left or the right.
114    ///
115    /// If this value is used in a vertical direction, a [`VerticalDirection`] must be available
116    /// to determine if the end is the top or the bottom.
117    End,
118
119    /// Place the objects as close to the middle of the axis as possible.
120    Center,
121
122    /// Place the free space evenly between the objects.
123    SpaceBetween,
124
125    /// Place the free space evenly between the objects as well as half of that space before and
126    /// after the first and last objects.
127    SpaceAround,
128
129    /// Place the free space evenly between the objects as well as before and after the first
130    /// and last objects.
131    SpaceEvenly,
132}
133
134impl WrapAlignment {
135    /// The space before the first object and between each two, for `item_count` objects.
136    fn distribute_space(
137        self,
138        free_space: f64,
139        item_spacing: f64,
140        item_count: usize,
141        flipped: bool,
142    ) -> (f64, f64) {
143        debug_assert!(item_count > 0);
144        let count = item_count as f64;
145        match self {
146            WrapAlignment::Start => (if flipped { free_space } else { 0.0 }, item_spacing),
147            WrapAlignment::End => WrapAlignment::Start.distribute_space(
148                free_space,
149                item_spacing,
150                item_count,
151                !flipped,
152            ),
153            WrapAlignment::SpaceBetween if item_count < 2 => {
154                WrapAlignment::Start.distribute_space(free_space, item_spacing, item_count, flipped)
155            }
156            WrapAlignment::Center => (free_space / 2.0, item_spacing),
157            WrapAlignment::SpaceBetween => (0.0, free_space / (count - 1.0) + item_spacing),
158            WrapAlignment::SpaceAround => {
159                (free_space / count / 2.0, free_space / count + item_spacing)
160            }
161            WrapAlignment::SpaceEvenly => (
162                free_space / (count + 1.0),
163                free_space / (count + 1.0) + item_spacing,
164            ),
165        }
166    }
167}
168
169/// Who [`RenderWrap`] should align children within a run in the cross axis.
170#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
171pub enum WrapCrossAlignment {
172    /// Place the children as close to the start of the run in the cross axis as possible.
173    ///
174    /// If this value is used in a horizontal direction, a [`TextDirection`] must be available
175    /// to determine if the start is the left or the right.
176    ///
177    /// If this value is used in a vertical direction, a [`VerticalDirection`] must be available
178    /// to determine if the start is the top or the bottom.
179    #[default]
180    Start,
181
182    /// Place the children as close to the end of the run in the cross axis as possible.
183    ///
184    /// If this value is used in a horizontal direction, a [`TextDirection`] must be available
185    /// to determine if the end is the left or the right.
186    ///
187    /// If this value is used in a vertical direction, a [`VerticalDirection`] must be available
188    /// to determine if the end is the top or the bottom.
189    End,
190
191    /// Place the children as close to the middle of the run in the cross axis as possible.
192    Center,
193    // TODO(ianh): baseline.
194}
195
196impl WrapCrossAlignment {
197    fn flipped(self) -> WrapCrossAlignment {
198        match self {
199            WrapCrossAlignment::Start => WrapCrossAlignment::End,
200            WrapCrossAlignment::End => WrapCrossAlignment::Start,
201            WrapCrossAlignment::Center => WrapCrossAlignment::Center,
202        }
203    }
204
205    fn alignment(self) -> f64 {
206        match self {
207            WrapCrossAlignment::Start => 0.0,
208            WrapCrossAlignment::End => 1.0,
209            WrapCrossAlignment::Center => 0.5,
210        }
211    }
212}
213
214/// A run's extent, its number of children, and the child it starts placing from.
215struct RunMetrics {
216    axis_size: AxisSize,
217    child_count: usize,
218    leading_child: AnyRenderBox,
219}
220
221impl RunMetrics {
222    fn new(leading_child: AnyRenderBox, axis_size: AxisSize) -> RunMetrics {
223        RunMetrics {
224            axis_size,
225            child_count: 1,
226            leading_child,
227        }
228    }
229
230    // Look ahead, creates a new run if incorporating the child would exceed the allowed line
231    // width.
232    fn try_adding_new_child(
233        &mut self,
234        child: AnyRenderBox,
235        child_size: AxisSize,
236        flip_main_axis: bool,
237        spacing: f64,
238        max_main_extent: f64,
239    ) -> Option<RunMetrics> {
240        let needs_new_run =
241            self.axis_size.main_axis_extent() + child_size.main_axis_extent() + spacing
242                - max_main_extent
243                > PRECISION_ERROR_TOLERANCE;
244        if needs_new_run {
245            return Some(RunMetrics::new(child, child_size));
246        }
247        self.axis_size = self.axis_size + child_size + AxisSize::new(spacing, 0.0);
248        self.child_count += 1;
249        if flip_main_axis {
250            self.leading_child = child;
251        }
252        None
253    }
254}
255
256/// Parent data for use with [`RenderWrap`].
257#[derive(Debug, Default)]
258pub struct WrapParentData {
259    box_parent_data: BoxParentData,
260    container_parent_data: ContainerParentData<AnyRenderBox>,
261}
262
263impl WrapParentData {
264    /// Creates wrap parent data at offset zero, with no siblings.
265    pub const fn new() -> WrapParentData {
266        WrapParentData {
267            box_parent_data: BoxParentData::new(),
268            container_parent_data: ContainerParentData::new(),
269        }
270    }
271}
272
273impl ParentData for WrapParentData {
274    fn detach(&mut self) {
275        ContainerParentDataMixin::detach(self);
276    }
277
278    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
279        if id == TypeId::of::<WrapParentData>() {
280            return Some(self);
281        }
282        self.box_parent_data.provide(id)
283    }
284
285    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
286        if id == TypeId::of::<WrapParentData>() {
287            return Some(self);
288        }
289        self.box_parent_data.provide_mut(id)
290    }
291}
292
293impl ContainerParentDataMixin for WrapParentData {
294    type ChildType = AnyRenderBox;
295
296    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
297        &self.container_parent_data
298    }
299
300    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
301        &mut self.container_parent_data
302    }
303}
304
305impl ContainerBoxParentData for WrapParentData {
306    fn box_parent_data(&self) -> &BoxParentData {
307        &self.box_parent_data
308    }
309
310    fn box_parent_data_mut(&mut self) -> &mut BoxParentData {
311        &mut self.box_parent_data
312    }
313}
314
315impl fmt::Display for WrapParentData {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        fmt::Display::fmt(&self.box_parent_data, f)
318    }
319}
320
321/// Displays its children in multiple horizontal or vertical runs.
322///
323/// A [`RenderWrap`] lays out each child and attempts to place the child adjacent to the previous
324/// child in the main axis, given by [`direction`](Self::direction), leaving
325/// [`spacing`](Self::spacing) space in between. If there is not enough space to fit the child,
326/// [`RenderWrap`] creates a new _run_ adjacent to the existing children in the cross axis.
327///
328/// After all the children have been allocated to runs, the children within the runs are
329/// positioned according to the [`alignment`](Self::alignment) in the main axis and according
330/// to the [`cross_axis_alignment`](Self::cross_axis_alignment) in the cross axis.
331///
332/// The runs themselves are then positioned in the cross axis according to the
333/// [`run_spacing`](Self::run_spacing) and [`run_alignment`](Self::run_alignment).
334pub struct RenderWrap {
335    render_object: RenderObjectData,
336    render_box: RenderBoxData,
337    container: ContainerRenderObjectData<AnyRenderBox>,
338    direction: Axis,
339    alignment: WrapAlignment,
340    spacing: f64,
341    run_alignment: WrapAlignment,
342    run_spacing: f64,
343    cross_axis_alignment: WrapCrossAlignment,
344    text_direction: Option<TextDirection>,
345    vertical_direction: VerticalDirection,
346    clip_behavior: Clip,
347    has_visual_overflow: bool,
348    clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
349}
350
351impl RenderWrap {
352    /// Creates a wrap render object.
353    ///
354    /// By default, the wrap layout is horizontal and both the children and the runs are aligned
355    /// to the start. Add children with [`add`](ContainerRenderObjectMixin::add) or
356    /// [`add_all`](ContainerRenderObjectMixin::add_all); Dart's constructor takes them, and its
357    /// other named arguments are the setters.
358    pub fn new(app: &mut App) -> RenderHandle<RenderWrap> {
359        RenderHandle::new_box(
360            app,
361            RenderWrap {
362                render_object: RenderObjectData::new(),
363                render_box: RenderBoxData::new(),
364                container: ContainerRenderObjectData::new(),
365                direction: Axis::Horizontal,
366                alignment: WrapAlignment::Start,
367                spacing: 0.0,
368                run_alignment: WrapAlignment::Start,
369                run_spacing: 0.0,
370                cross_axis_alignment: WrapCrossAlignment::Start,
371                text_direction: None,
372                vertical_direction: VerticalDirection::Down,
373                clip_behavior: Clip::None,
374                has_visual_overflow: false,
375                clip_rect_layer: LayerHandle::new(),
376            },
377        )
378    }
379
380    /// The direction to use as the main axis.
381    ///
382    /// For example, if [`direction`](Self::direction) is [`Axis::Horizontal`], the default, the
383    /// children are placed adjacent to one another in a horizontal run until the available
384    /// horizontal space is consumed, at which point a subsequent children are placed in a new
385    /// run vertically adjacent to the previous run.
386    pub fn direction(self: RenderHandle<Self>, app: &App) -> Axis {
387        self.get(app).direction
388    }
389
390    /// Sets [`direction`](Self::direction).
391    pub fn set_direction(self: RenderHandle<Self>, app: &mut App, value: Axis) {
392        if self.get(app).direction == value {
393            return;
394        }
395        self.get_mut(app).direction = value;
396        self.mark_needs_layout(app);
397    }
398
399    /// How the children within a run should be placed in the main axis.
400    ///
401    /// For example, if [`alignment`](Self::alignment) is [`WrapAlignment::Center`], the
402    /// children in each run are grouped together in the center of their run in the main axis.
403    ///
404    /// Defaults to [`WrapAlignment::Start`].
405    ///
406    /// See also:
407    ///
408    ///  * [`run_alignment`](Self::run_alignment), which controls how the runs are placed
409    ///    relative to each other in the cross axis.
410    ///  * [`cross_axis_alignment`](Self::cross_axis_alignment), which controls how the children
411    ///    within each run are placed relative to each other in the cross axis.
412    pub fn alignment(self: RenderHandle<Self>, app: &App) -> WrapAlignment {
413        self.get(app).alignment
414    }
415
416    /// Sets [`alignment`](Self::alignment).
417    pub fn set_alignment(self: RenderHandle<Self>, app: &mut App, value: WrapAlignment) {
418        if self.get(app).alignment == value {
419            return;
420        }
421        self.get_mut(app).alignment = value;
422        self.mark_needs_layout(app);
423    }
424
425    /// How much space to place between children in a run in the main axis.
426    ///
427    /// For example, if [`spacing`](Self::spacing) is 10.0, the children will be spaced at least
428    /// 10.0 logical pixels apart in the main axis.
429    ///
430    /// If there is additional free space in a run (e.g., because the wrap has a minimum size
431    /// that is not filled or because some runs are longer than others), the additional free
432    /// space will be allocated according to the [`alignment`](Self::alignment).
433    ///
434    /// Defaults to 0.0.
435    pub fn spacing(self: RenderHandle<Self>, app: &App) -> f64 {
436        self.get(app).spacing
437    }
438
439    /// Sets [`spacing`](Self::spacing).
440    pub fn set_spacing(self: RenderHandle<Self>, app: &mut App, value: f64) {
441        if self.get(app).spacing == value {
442            return;
443        }
444        self.get_mut(app).spacing = value;
445        self.mark_needs_layout(app);
446    }
447
448    /// How the runs themselves should be placed in the cross axis.
449    ///
450    /// For example, if [`run_alignment`](Self::run_alignment) is [`WrapAlignment::Center`], the
451    /// runs are grouped together in the center of the overall [`RenderWrap`] in the cross axis.
452    ///
453    /// Defaults to [`WrapAlignment::Start`].
454    ///
455    /// See also:
456    ///
457    ///  * [`alignment`](Self::alignment), which controls how the children within each run are
458    ///    placed relative to each other in the main axis.
459    ///  * [`cross_axis_alignment`](Self::cross_axis_alignment), which controls how the children
460    ///    within each run are placed relative to each other in the cross axis.
461    pub fn run_alignment(self: RenderHandle<Self>, app: &App) -> WrapAlignment {
462        self.get(app).run_alignment
463    }
464
465    /// Sets [`run_alignment`](Self::run_alignment).
466    pub fn set_run_alignment(self: RenderHandle<Self>, app: &mut App, value: WrapAlignment) {
467        if self.get(app).run_alignment == value {
468            return;
469        }
470        self.get_mut(app).run_alignment = value;
471        self.mark_needs_layout(app);
472    }
473
474    /// How much space to place between the runs themselves in the cross axis.
475    ///
476    /// For example, if [`run_spacing`](Self::run_spacing) is 10.0, the runs will be spaced at
477    /// least 10.0 logical pixels apart in the cross axis.
478    ///
479    /// If there is additional free space in the overall [`RenderWrap`] (e.g., because the wrap
480    /// has a minimum size that is not filled), the additional free space will be allocated
481    /// according to the [`run_alignment`](Self::run_alignment).
482    ///
483    /// Defaults to 0.0.
484    pub fn run_spacing(self: RenderHandle<Self>, app: &App) -> f64 {
485        self.get(app).run_spacing
486    }
487
488    /// Sets [`run_spacing`](Self::run_spacing).
489    pub fn set_run_spacing(self: RenderHandle<Self>, app: &mut App, value: f64) {
490        if self.get(app).run_spacing == value {
491            return;
492        }
493        self.get_mut(app).run_spacing = value;
494        self.mark_needs_layout(app);
495    }
496
497    /// How the children within a run should be aligned relative to each other in the cross
498    /// axis.
499    ///
500    /// For example, if this is set to [`WrapCrossAlignment::End`], and the
501    /// [`direction`](Self::direction) is [`Axis::Horizontal`], then the children within each
502    /// run will have their bottom edges aligned to the bottom edge of the run.
503    ///
504    /// Defaults to [`WrapCrossAlignment::Start`].
505    ///
506    /// See also:
507    ///
508    ///  * [`alignment`](Self::alignment), which controls how the children within each run are
509    ///    placed relative to each other in the main axis.
510    ///  * [`run_alignment`](Self::run_alignment), which controls how the runs are placed
511    ///    relative to each other in the cross axis.
512    pub fn cross_axis_alignment(self: RenderHandle<Self>, app: &App) -> WrapCrossAlignment {
513        self.get(app).cross_axis_alignment
514    }
515
516    /// Sets [`cross_axis_alignment`](Self::cross_axis_alignment).
517    pub fn set_cross_axis_alignment(
518        self: RenderHandle<Self>,
519        app: &mut App,
520        value: WrapCrossAlignment,
521    ) {
522        if self.get(app).cross_axis_alignment == value {
523            return;
524        }
525        self.get_mut(app).cross_axis_alignment = value;
526        self.mark_needs_layout(app);
527    }
528
529    /// Determines the order to lay children out horizontally and how to interpret `start` and
530    /// `end` in the horizontal direction.
531    ///
532    /// If the [`direction`](Self::direction) is [`Axis::Horizontal`], this controls the order in
533    /// which children are positioned (left-to-right or right-to-left), and the meaning of the
534    /// [`alignment`](Self::alignment) property's [`WrapAlignment::Start`] and
535    /// [`WrapAlignment::End`] values.
536    ///
537    /// If the [`direction`](Self::direction) is [`Axis::Horizontal`], and either the
538    /// [`alignment`](Self::alignment) is either [`WrapAlignment::Start`] or
539    /// [`WrapAlignment::End`], or there's more than one child, then the
540    /// [`text_direction`](Self::text_direction) must not be `None`.
541    ///
542    /// If the [`direction`](Self::direction) is [`Axis::Vertical`], this controls the order in
543    /// which runs are positioned, the meaning of the [`run_alignment`](Self::run_alignment)
544    /// property's [`WrapAlignment::Start`] and [`WrapAlignment::End`] values, as well as the
545    /// [`cross_axis_alignment`](Self::cross_axis_alignment) property's
546    /// [`WrapCrossAlignment::Start`] and [`WrapCrossAlignment::End`] values.
547    ///
548    /// If the [`direction`](Self::direction) is [`Axis::Vertical`], and either the
549    /// [`run_alignment`](Self::run_alignment) is either [`WrapAlignment::Start`] or
550    /// [`WrapAlignment::End`], the [`cross_axis_alignment`](Self::cross_axis_alignment) is
551    /// either [`WrapCrossAlignment::Start`] or [`WrapCrossAlignment::End`], or there's more
552    /// than one child, then the [`text_direction`](Self::text_direction) must not be `None`.
553    pub fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
554        self.get(app).text_direction
555    }
556
557    /// Sets [`text_direction`](Self::text_direction).
558    pub fn set_text_direction(
559        self: RenderHandle<Self>,
560        app: &mut App,
561        value: Option<TextDirection>,
562    ) {
563        if self.get(app).text_direction != value {
564            self.get_mut(app).text_direction = value;
565            self.mark_needs_layout(app);
566        }
567    }
568
569    /// Determines the order to lay children out vertically and how to interpret `start` and
570    /// `end` in the vertical direction.
571    ///
572    /// If the [`direction`](Self::direction) is [`Axis::Vertical`], this controls which order
573    /// children are painted in (down or up), the meaning of the [`alignment`](Self::alignment)
574    /// property's [`WrapAlignment::Start`] and [`WrapAlignment::End`] values.
575    ///
576    /// If the [`direction`](Self::direction) is [`Axis::Horizontal`], this controls the order
577    /// in which runs are positioned, the meaning of the [`run_alignment`](Self::run_alignment)
578    /// property's [`WrapAlignment::Start`] and [`WrapAlignment::End`] values, as well as the
579    /// [`cross_axis_alignment`](Self::cross_axis_alignment) property's
580    /// [`WrapCrossAlignment::Start`] and [`WrapCrossAlignment::End`] values.
581    pub fn vertical_direction(self: RenderHandle<Self>, app: &App) -> VerticalDirection {
582        self.get(app).vertical_direction
583    }
584
585    /// Sets [`vertical_direction`](Self::vertical_direction).
586    pub fn set_vertical_direction(
587        self: RenderHandle<Self>,
588        app: &mut App,
589        value: VerticalDirection,
590    ) {
591        if self.get(app).vertical_direction != value {
592            self.get_mut(app).vertical_direction = value;
593            self.mark_needs_layout(app);
594        }
595    }
596
597    /// The content will be clipped (or not) according to this option.
598    ///
599    /// Defaults to [`Clip::None`].
600    pub fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
601        self.get(app).clip_behavior
602    }
603
604    /// Sets [`clip_behavior`](Self::clip_behavior).
605    pub fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
606        if value != self.get(app).clip_behavior {
607            self.get_mut(app).clip_behavior = value;
608            self.mark_needs_paint(app);
609        }
610    }
611
612    /// Flutter's `_debugHasNecessaryDirections`: asserts the directions the current alignments
613    /// need are available. Always returns true.
614    fn debug_has_necessary_directions(self: RenderHandle<Self>, app: &App) -> bool {
615        let this = self.get(app);
616        let has_text_direction = this.text_direction.is_some();
617        if self.first_child(app).is_some() && self.last_child(app) != self.first_child(app) {
618            // i.e. there's more than one child
619            if this.direction == Axis::Horizontal {
620                debug_assert!(
621                    has_text_direction,
622                    "Horizontal RenderWrap with multiple children has a null textDirection, so \
623                     the layout order is undefined."
624                );
625            }
626        }
627        let alignment = this.alignment;
628        if matches!(alignment, WrapAlignment::Start | WrapAlignment::End)
629            && this.direction == Axis::Horizontal
630        {
631            debug_assert!(
632                has_text_direction,
633                "Horizontal RenderWrap with alignment {alignment:?} has a null textDirection, so \
634                 the alignment cannot be resolved."
635            );
636        }
637        let run_alignment = this.run_alignment;
638        if matches!(run_alignment, WrapAlignment::Start | WrapAlignment::End)
639            && this.direction == Axis::Vertical
640        {
641            debug_assert!(
642                has_text_direction,
643                "Vertical RenderWrap with runAlignment {run_alignment:?} has a null \
644                 textDirection, so the alignment cannot be resolved."
645            );
646        }
647        let cross_axis_alignment = this.cross_axis_alignment;
648        if matches!(
649            cross_axis_alignment,
650            WrapCrossAlignment::Start | WrapCrossAlignment::End
651        ) && this.direction == Axis::Vertical
652        {
653            debug_assert!(
654                has_text_direction,
655                "Vertical RenderWrap with crossAxisAlignment {cross_axis_alignment:?} has a \
656                 null textDirection, so the alignment cannot be resolved."
657            );
658        }
659        true
660    }
661
662    fn get_main_axis_extent(self: RenderHandle<Self>, app: &App, child_size: Size) -> f64 {
663        match self.direction(app) {
664            Axis::Horizontal => child_size.width(),
665            Axis::Vertical => child_size.height(),
666        }
667    }
668
669    fn get_cross_axis_extent(self: RenderHandle<Self>, app: &App, child_size: Size) -> f64 {
670        match self.direction(app) {
671            Axis::Horizontal => child_size.height(),
672            Axis::Vertical => child_size.width(),
673        }
674    }
675
676    fn get_offset(
677        self: RenderHandle<Self>,
678        app: &App,
679        main_axis_offset: f64,
680        cross_axis_offset: f64,
681    ) -> Offset {
682        match self.direction(app) {
683            Axis::Horizontal => Offset::new(main_axis_offset, cross_axis_offset),
684            Axis::Vertical => Offset::new(cross_axis_offset, main_axis_offset),
685        }
686    }
687
688    /// Whether the main axis and the cross axis run against their coordinates.
689    fn are_axes_flipped(self: RenderHandle<Self>, app: &App) -> (bool, bool) {
690        let this = self.get(app);
691        let flip_horizontal = match this.text_direction.unwrap_or(TextDirection::Ltr) {
692            TextDirection::Ltr => false,
693            TextDirection::Rtl => true,
694        };
695        let flip_vertical = match this.vertical_direction {
696            VerticalDirection::Down => false,
697            VerticalDirection::Up => true,
698        };
699        match this.direction {
700            Axis::Horizontal => (flip_horizontal, flip_vertical),
701            Axis::Vertical => (flip_vertical, flip_horizontal),
702        }
703    }
704
705    /// The constraints a child is laid out with, and the main axis extent a run fills.
706    fn child_constraints_and_main_axis_limit(
707        self: RenderHandle<Self>,
708        app: &App,
709        constraints: BoxConstraints,
710    ) -> (BoxConstraints, f64) {
711        match self.direction(app) {
712            Axis::Horizontal => (
713                BoxConstraints::new().max_width(constraints.max_width),
714                constraints.max_width,
715            ),
716            Axis::Vertical => (
717                BoxConstraints::new().max_height(constraints.max_height),
718                constraints.max_height,
719            ),
720        }
721    }
722
723    /// The size this wrap would take, measuring the children dry.
724    fn compute_dry_layout_size(
725        self: RenderHandle<Self>,
726        app: &mut App,
727        constraints: BoxConstraints,
728    ) -> Size {
729        let (child_constraints, main_axis_limit) =
730            self.child_constraints_and_main_axis_limit(app, constraints);
731        let (spacing, run_spacing) = (self.spacing(app), self.run_spacing(app));
732
733        let mut main_axis_extent: f64 = 0.0;
734        let mut cross_axis_extent = 0.0;
735        let mut run_main_axis_extent = 0.0;
736        let mut run_cross_axis_extent: f64 = 0.0;
737        let mut child_count = 0;
738        let mut child = self.first_child(app);
739        while let Some(current) = child {
740            let child_size = ChildLayoutHelper::dry_layout_child(app, current, child_constraints);
741            let child_main_axis_extent = self.get_main_axis_extent(app, child_size);
742            let child_cross_axis_extent = self.get_cross_axis_extent(app, child_size);
743            // There must be at least one child before we move on to the next run.
744            if child_count > 0
745                && run_main_axis_extent + child_main_axis_extent + spacing > main_axis_limit
746            {
747                main_axis_extent = main_axis_extent.max(run_main_axis_extent);
748                cross_axis_extent += run_cross_axis_extent + run_spacing;
749                run_main_axis_extent = 0.0;
750                run_cross_axis_extent = 0.0;
751                child_count = 0;
752            }
753            run_main_axis_extent += child_main_axis_extent;
754            run_cross_axis_extent = run_cross_axis_extent.max(child_cross_axis_extent);
755            if child_count > 0 {
756                run_main_axis_extent += spacing;
757            }
758            child_count += 1;
759            child = self.child_after(app, current);
760        }
761        cross_axis_extent += run_cross_axis_extent;
762        main_axis_extent = main_axis_extent.max(run_main_axis_extent);
763
764        constraints.constrain(match self.direction(app) {
765            Axis::Horizontal => Size::new(main_axis_extent, cross_axis_extent),
766            Axis::Vertical => Size::new(cross_axis_extent, main_axis_extent),
767        })
768    }
769
770    /// Lays out (or measures) the children with `layout_child` and splits them into runs:
771    /// the extent the runs take together, and each run.
772    fn compute_runs(
773        self: RenderHandle<Self>,
774        app: &mut App,
775        constraints: BoxConstraints,
776        layout_child: ChildLayouter,
777    ) -> (AxisSize, Vec<RunMetrics>) {
778        debug_assert!(self.first_child(app).is_some());
779        let (child_constraints, main_axis_limit) =
780            self.child_constraints_and_main_axis_limit(app, constraints);
781
782        let (flip_main_axis, _) = self.are_axes_flipped(app);
783        let direction = self.direction(app);
784        let spacing = self.spacing(app);
785        let mut run_metrics: Vec<RunMetrics> = Vec::new();
786
787        let mut children_axis_size = AxisSize::EMPTY;
788        let mut child = self.first_child(app);
789        while let Some(current) = child {
790            let child_size =
791                AxisSize::from_size(layout_child(app, current, child_constraints), direction);
792            let new_run = match run_metrics.last_mut() {
793                None => Some(RunMetrics::new(current, child_size)),
794                Some(current_run) => current_run.try_adding_new_child(
795                    current,
796                    child_size,
797                    flip_main_axis,
798                    spacing,
799                    main_axis_limit,
800                ),
801            };
802            if let Some(new_run) = new_run {
803                if let Some(current_run) = run_metrics.last() {
804                    children_axis_size = children_axis_size + current_run.axis_size.flipped();
805                }
806                run_metrics.push(new_run);
807            }
808            child = self.child_after(app, current);
809        }
810        debug_assert!(!run_metrics.is_empty());
811        let total_run_spacing = self.run_spacing(app) * (run_metrics.len() - 1) as f64;
812        let current_run = run_metrics.last().expect("at least one run");
813        children_axis_size = children_axis_size
814            + (AxisSize::new(total_run_spacing, 0.0) + current_run.axis_size.flipped());
815        (children_axis_size.flipped(), run_metrics)
816    }
817
818    /// Places each child of `run_metrics` within the container: `position_child` is told each
819    /// child's offset, and `get_child_size` gives each child's size.
820    fn position_children(
821        self: RenderHandle<Self>,
822        app: &mut App,
823        run_metrics: &[RunMetrics],
824        free_axis_size: AxisSize,
825        container_axis_size: AxisSize,
826        position_child: &mut dyn FnMut(&mut App, Offset, AnyRenderBox),
827        get_child_size: &mut dyn FnMut(&mut App, AnyRenderBox) -> Size,
828    ) {
829        debug_assert!(!run_metrics.is_empty());
830
831        let direction = self.direction(app);
832        let spacing = self.spacing(app);
833
834        let cross_axis_free_space = free_axis_size.cross_axis_extent().max(0.0);
835
836        let (flip_main_axis, flip_cross_axis) = self.are_axes_flipped(app);
837        let cross_axis_alignment = self.cross_axis_alignment(app);
838        let effective_cross_alignment = if flip_cross_axis {
839            cross_axis_alignment.flipped()
840        } else {
841            cross_axis_alignment
842        };
843        let (run_leading_space, run_between_space) = self.run_alignment(app).distribute_space(
844            cross_axis_free_space,
845            self.run_spacing(app),
846            run_metrics.len(),
847            flip_cross_axis,
848        );
849        let alignment = self.alignment(app);
850
851        let mut run_cross_axis_offset = run_leading_space;
852        let runs: Box<dyn Iterator<Item = &RunMetrics>> = if flip_cross_axis {
853            Box::new(run_metrics.iter().rev())
854        } else {
855            Box::new(run_metrics.iter())
856        };
857        for run in runs {
858            let run_cross_axis_extent = run.axis_size.cross_axis_extent();
859            let child_count = run.child_count;
860
861            let main_axis_free_space = (container_axis_size.main_axis_extent()
862                - run.axis_size.main_axis_extent())
863            .max(0.0);
864            let (child_leading_space, child_between_space) = alignment.distribute_space(
865                main_axis_free_space,
866                spacing,
867                child_count,
868                flip_main_axis,
869            );
870
871            let mut child_main_axis_offset = child_leading_space;
872
873            let mut remaining_child_count = run.child_count;
874            let mut child = Some(run.leading_child);
875            while let Some(current) = child
876                && remaining_child_count > 0
877            {
878                let child_axis_size = AxisSize::from_size(get_child_size(app, current), direction);
879                let child_cross_axis_offset = effective_cross_alignment.alignment()
880                    * (run_cross_axis_extent - child_axis_size.cross_axis_extent());
881                let offset = self.get_offset(
882                    app,
883                    child_main_axis_offset,
884                    run_cross_axis_offset + child_cross_axis_offset,
885                );
886                position_child(app, offset, current);
887                child_main_axis_offset += child_axis_size.main_axis_extent() + child_between_space;
888                child = if flip_main_axis {
889                    self.child_before(app, current)
890                } else {
891                    self.child_after(app, current)
892                };
893                remaining_child_count -= 1;
894            }
895            run_cross_axis_offset += run_cross_axis_extent + run_between_space;
896        }
897    }
898}
899
900impl ContainerRenderObjectMixin for RenderWrap {
901    type ChildType = AnyRenderBox;
902    type ParentDataType = WrapParentData;
903
904    fn container_data(
905        self: RenderHandle<Self>,
906        app: &App,
907    ) -> &ContainerRenderObjectData<AnyRenderBox> {
908        &self.get(app).container
909    }
910
911    fn container_data_mut(
912        self: RenderHandle<Self>,
913        app: &mut App,
914    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
915        &mut self.get_mut(app).container
916    }
917}
918
919impl RenderBoxContainerDefaultsMixin for RenderWrap {}
920
921impl RenderObject for RenderWrap {
922    crate::render_object_accessors!();
923
924    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
925        let constraints = self.constraints(app);
926        debug_assert!(self.debug_has_necessary_directions(app));
927        if self.first_child(app).is_none() {
928            self.set_size(app, constraints.smallest());
929            self.get_mut(app).has_visual_overflow = false;
930            return;
931        }
932
933        let direction = self.direction(app);
934        let (children_axis_size, run_metrics) =
935            self.compute_runs(app, constraints, ChildLayoutHelper::layout_child);
936        let container_axis_size = children_axis_size.apply_constraints(constraints, direction);
937        self.set_size(app, container_axis_size.to_size(direction));
938        let free_axis_size = container_axis_size - children_axis_size;
939        self.get_mut(app).has_visual_overflow =
940            free_axis_size.main_axis_extent() < 0.0 || free_axis_size.cross_axis_extent() < 0.0;
941        self.position_children(
942            app,
943            &run_metrics,
944            free_axis_size,
945            container_axis_size,
946            &mut |app, offset, child| {
947                child
948                    .parent_data_of_mut::<WrapParentData>(app)
949                    .set_offset(offset);
950            },
951            &mut |app, child| child.size(app),
952        );
953    }
954
955    fn visit_children(
956        self: RenderHandle<Self>,
957        app: &App,
958        visitor: &mut dyn FnMut(AnyRenderObject),
959    ) {
960        ContainerRenderObjectMixin::visit_children(self, app, visitor)
961    }
962
963    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
964        ContainerRenderObjectMixin::did_attach(self, app, owner)
965    }
966
967    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
968        ContainerRenderObjectMixin::did_detach(self, app)
969    }
970
971    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
972        ContainerRenderObjectMixin::redepth_children(self, app)
973    }
974
975    /// Paints the children, clipping them to this wrap when they overflow it and
976    /// [`clip_behavior`](RenderWrap::clip_behavior) asks for it.
977    fn paint(
978        self: RenderHandle<Self>,
979        app: &mut App,
980        context: &mut PaintingContext,
981        offset: Offset,
982    ) {
983        // TODO(ianh): move the debug flex overflow paint logic somewhere common so it can be
984        // reused here
985        let clip_behavior = self.clip_behavior(app);
986        if self.get(app).has_visual_overflow && clip_behavior != Clip::None {
987            let size = self.size(app);
988            let old = self.get(app).clip_rect_layer.layer();
989            let layer = context.push_clip_rect(
990                app,
991                self.as_object().needs_compositing(app),
992                offset,
993                Offset::ZERO & size,
994                |app, context, offset| self.default_paint(app, context, offset),
995                clip_behavior,
996                old,
997            );
998            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, layer);
999        } else {
1000            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
1001            self.default_paint(app, context, offset);
1002        }
1003    }
1004
1005    /// Flutter's `dispose`: drop the clip layer handle, then `super.dispose()`.
1006    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1007        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
1008        crate::object::RenderObjectBase::dispose(self, app);
1009    }
1010}
1011
1012impl RenderBox for RenderWrap {
1013    crate::render_box_accessors!();
1014
1015    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
1016        if !child.parent_data_is::<WrapParentData>(app) {
1017            child.set_parent_data(app, WrapParentData::new());
1018        }
1019    }
1020
1021    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1022        match self.direction(app) {
1023            Axis::Horizontal => {
1024                let mut width: f64 = 0.0;
1025                let mut child = self.first_child(app);
1026                while let Some(current) = child {
1027                    width = width.max(current.get_min_intrinsic_width(app, f64::INFINITY));
1028                    child = self.child_after(app, current);
1029                }
1030                width
1031            }
1032            Axis::Vertical => self
1033                .get_dry_layout(app, BoxConstraints::new().max_height(height))
1034                .width(),
1035        }
1036    }
1037
1038    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1039        match self.direction(app) {
1040            Axis::Horizontal => {
1041                let mut width = 0.0;
1042                let mut child = self.first_child(app);
1043                while let Some(current) = child {
1044                    width += current.get_max_intrinsic_width(app, f64::INFINITY);
1045                    child = self.child_after(app, current);
1046                }
1047                width
1048            }
1049            Axis::Vertical => self
1050                .get_dry_layout(app, BoxConstraints::new().max_height(height))
1051                .width(),
1052        }
1053    }
1054
1055    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1056        match self.direction(app) {
1057            Axis::Horizontal => self
1058                .get_dry_layout(app, BoxConstraints::new().max_width(width))
1059                .height(),
1060            Axis::Vertical => {
1061                let mut height: f64 = 0.0;
1062                let mut child = self.first_child(app);
1063                while let Some(current) = child {
1064                    height = height.max(current.get_min_intrinsic_height(app, f64::INFINITY));
1065                    child = self.child_after(app, current);
1066                }
1067                height
1068            }
1069        }
1070    }
1071
1072    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1073        match self.direction(app) {
1074            Axis::Horizontal => self
1075                .get_dry_layout(app, BoxConstraints::new().max_width(width))
1076                .height(),
1077            Axis::Vertical => {
1078                let mut height = 0.0;
1079                let mut child = self.first_child(app);
1080                while let Some(current) = child {
1081                    height += current.get_max_intrinsic_height(app, f64::INFINITY);
1082                    child = self.child_after(app, current);
1083                }
1084                height
1085            }
1086        }
1087    }
1088
1089    fn compute_distance_to_actual_baseline(
1090        self: RenderHandle<Self>,
1091        app: &mut App,
1092        baseline: TextBaseline,
1093    ) -> Option<f64> {
1094        self.default_compute_distance_to_highest_actual_baseline(app, baseline)
1095    }
1096
1097    fn compute_dry_baseline(
1098        self: RenderHandle<Self>,
1099        app: &mut App,
1100        constraints: BoxConstraints,
1101        baseline: TextBaseline,
1102    ) -> Option<f64> {
1103        self.first_child(app)?;
1104        let child_constraints = match self.direction(app) {
1105            Axis::Horizontal => BoxConstraints::new().max_width(constraints.max_width),
1106            Axis::Vertical => BoxConstraints::new().max_height(constraints.max_height),
1107        };
1108
1109        let (children_axis_size, run_metrics) =
1110            self.compute_runs(app, constraints, ChildLayoutHelper::dry_layout_child);
1111        let container_axis_size =
1112            children_axis_size.apply_constraints(constraints, self.direction(app));
1113
1114        let mut baseline_offset = BaselineOffset::NO_BASELINE;
1115        self.position_children(
1116            app,
1117            &run_metrics,
1118            children_axis_size,
1119            container_axis_size,
1120            &mut |app, offset, child| {
1121                baseline_offset = baseline_offset.min_of(
1122                    BaselineOffset(child.get_dry_baseline(app, child_constraints, baseline))
1123                        + offset.dy(),
1124                );
1125            },
1126            &mut |app, child| child.get_dry_layout(app, child_constraints),
1127        );
1128        baseline_offset.offset()
1129    }
1130
1131    fn compute_dry_layout(
1132        self: RenderHandle<Self>,
1133        app: &mut App,
1134        constraints: BoxConstraints,
1135    ) -> Size {
1136        self.compute_dry_layout_size(app, constraints)
1137    }
1138
1139    fn hit_test_children(
1140        self: RenderHandle<Self>,
1141        app: &mut App,
1142        result: &mut BoxHitTestResult<'_>,
1143        position: Offset,
1144    ) -> bool {
1145        self.default_hit_test_children(app, result, position)
1146    }
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151    use inset_embedder::valo::Op;
1152    use inset_foundation::AppCell;
1153
1154    use super::*;
1155    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer, PictureLayer};
1156    use crate::proxy_box::{RenderConstrainedBox, RenderRepaintBoundary};
1157
1158    /// A box at least 80 square, as `wrap_test.dart`'s children are.
1159    fn box_80(app: &mut App) -> AnyRenderBox {
1160        let at_least = BoxConstraints::new().min_width(80.0).min_height(80.0);
1161        RenderConstrainedBox::new(app, at_least, None).as_box()
1162    }
1163
1164    /// A left-to-right wrap of three 80 square boxes, `spacing` apart in both axes.
1165    fn wrap_of_three(app: &mut App, direction: Axis, spacing: f64) -> RenderHandle<RenderWrap> {
1166        let wrap = RenderWrap::new(app);
1167        let children = vec![box_80(app), box_80(app), box_80(app)];
1168        wrap.add_all(app, Some(children));
1169        wrap.set_spacing(app, spacing);
1170        wrap.set_run_spacing(app, spacing);
1171        wrap.set_direction(app, direction);
1172        wrap
1173    }
1174
1175    /// `wrap_test.dart`: "Compute intrinsic height test".
1176    #[test]
1177    fn a_horizontal_wrap_is_as_tall_as_its_runs() {
1178        let cell = AppCell::new();
1179        let mut app = cell.borrow_mut();
1180        let wrap = wrap_of_three(&mut app, Axis::Horizontal, 5.0);
1181        for (width, height) in [(245.0, 165.0), (250.0, 80.0), (80.0, 250.0), (79.0, 250.0)] {
1182            assert_eq!(wrap.compute_max_intrinsic_height(&mut app, width), height);
1183            assert_eq!(wrap.compute_min_intrinsic_height(&mut app, width), height);
1184        }
1185    }
1186
1187    /// `wrap_test.dart`: "Compute intrinsic width test".
1188    #[test]
1189    fn a_vertical_wrap_is_as_wide_as_its_runs() {
1190        let cell = AppCell::new();
1191        let mut app = cell.borrow_mut();
1192        let wrap = wrap_of_three(&mut app, Axis::Vertical, 5.0);
1193        for (height, width) in [(245.0, 165.0), (250.0, 80.0), (80.0, 250.0), (79.0, 250.0)] {
1194            assert_eq!(wrap.compute_max_intrinsic_width(&mut app, height), width);
1195            assert_eq!(wrap.compute_min_intrinsic_width(&mut app, height), width);
1196        }
1197    }
1198
1199    /// `wrap_test.dart`: "Compute intrinsic height for only one run".
1200    #[test]
1201    fn one_child_makes_one_run_whatever_the_width() {
1202        let cell = AppCell::new();
1203        let mut app = cell.borrow_mut();
1204        let wrap = RenderWrap::new(&mut app);
1205        let child = box_80(&mut app);
1206        wrap.add(&mut app, child);
1207        wrap.set_spacing(&mut app, 5.0);
1208        wrap.set_run_spacing(&mut app, 5.0);
1209        for width in [100.0, 79.0, 80.0] {
1210            assert_eq!(wrap.compute_max_intrinsic_height(&mut app, width), 80.0);
1211            assert_eq!(wrap.compute_min_intrinsic_height(&mut app, width), 80.0);
1212        }
1213    }
1214
1215    fn offsets(app: &App, wrap: RenderHandle<RenderWrap>) -> Vec<Offset> {
1216        let mut offsets = Vec::new();
1217        let mut child = wrap.first_child(app);
1218        while let Some(current) = child {
1219            offsets.push(current.box_parent_data(app).offset);
1220            child = wrap.child_after(app, current);
1221        }
1222        offsets
1223    }
1224
1225    #[test]
1226    fn children_that_overflow_a_run_start_the_next_one() {
1227        let cell = AppCell::new();
1228        let mut app = cell.borrow_mut();
1229        let wrap = wrap_of_three(&mut app, Axis::Horizontal, 10.0);
1230        wrap.set_text_direction(&mut app, Some(TextDirection::Ltr));
1231        wrap.as_box()
1232            .layout(&mut app, BoxConstraints::new().max_width(200.0), true);
1233        assert_eq!(wrap.size(&app), Size::new(170.0, 170.0));
1234        assert_eq!(
1235            offsets(&app, wrap),
1236            [
1237                Offset::new(0.0, 0.0),
1238                Offset::new(90.0, 0.0),
1239                Offset::new(0.0, 90.0)
1240            ]
1241        );
1242    }
1243
1244    #[test]
1245    fn a_right_to_left_wrap_places_its_runs_from_the_right() {
1246        let cell = AppCell::new();
1247        let mut app = cell.borrow_mut();
1248        let wrap = wrap_of_three(&mut app, Axis::Horizontal, 10.0);
1249        wrap.set_text_direction(&mut app, Some(TextDirection::Rtl));
1250        wrap.set_alignment(&mut app, WrapAlignment::Start);
1251        wrap.as_box().layout(
1252            &mut app,
1253            BoxConstraints::tight(Size::new(200.0, 200.0)),
1254            true,
1255        );
1256        // The first run's 170 sit against the right edge, 30 from the left.
1257        assert_eq!(
1258            offsets(&app, wrap),
1259            [
1260                Offset::new(120.0, 0.0),
1261                Offset::new(30.0, 0.0),
1262                Offset::new(120.0, 90.0)
1263            ]
1264        );
1265    }
1266
1267    #[test]
1268    fn alignments_spread_the_free_space() {
1269        let cell = AppCell::new();
1270        let mut app = cell.borrow_mut();
1271        let wrap = wrap_of_three(&mut app, Axis::Horizontal, 0.0);
1272        wrap.set_text_direction(&mut app, Some(TextDirection::Ltr));
1273        wrap.set_alignment(&mut app, WrapAlignment::SpaceBetween);
1274        wrap.set_run_alignment(&mut app, WrapAlignment::End);
1275        wrap.set_cross_axis_alignment(&mut app, WrapCrossAlignment::Center);
1276        wrap.as_box().layout(
1277            &mut app,
1278            BoxConstraints::tight(Size::new(300.0, 100.0)),
1279            true,
1280        );
1281        assert_eq!(
1282            offsets(&app, wrap),
1283            [
1284                Offset::new(0.0, 20.0),
1285                Offset::new(110.0, 20.0),
1286                Offset::new(220.0, 20.0)
1287            ]
1288        );
1289        assert_eq!(
1290            WrapAlignment::SpaceEvenly.distribute_space(40.0, 0.0, 3, false),
1291            (10.0, 10.0)
1292        );
1293        assert_eq!(
1294            WrapAlignment::SpaceAround.distribute_space(60.0, 5.0, 3, false),
1295            (10.0, 25.0)
1296        );
1297    }
1298
1299    #[test]
1300    fn a_wrap_measures_dry_as_it_lays_out() {
1301        let cell = AppCell::new();
1302        let mut app = cell.borrow_mut();
1303        let wrap = wrap_of_three(&mut app, Axis::Horizontal, 10.0);
1304        wrap.set_text_direction(&mut app, Some(TextDirection::Ltr));
1305        let constraints = BoxConstraints::new().max_width(200.0);
1306        let dry = wrap.get_dry_layout(&mut app, constraints);
1307        wrap.as_box().layout(&mut app, constraints, true);
1308        assert_eq!(dry, wrap.size(&app));
1309    }
1310
1311    fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
1312        let root = OffsetLayer::new(app, Offset::ZERO);
1313        root.as_layer().attach(app, node.id());
1314        node.schedule_initial_paint(app, root.as_container_layer());
1315    }
1316
1317    /// The test binding's first frame: attach, lay the root out, schedule and flush paint.
1318    fn first_frame(
1319        app: &mut App,
1320        root: AnyRenderBox,
1321        constraints: BoxConstraints,
1322    ) -> Handle<PipelineOwner> {
1323        let owner = PipelineOwner::new(app, None, None);
1324        owner.set_root_node(app, Some(root.as_object()));
1325        root.layout(app, constraints, false);
1326        schedule_root_paint(app, root.as_object());
1327        owner.flush_compositing_bits(app);
1328        owner.flush_paint(app);
1329        owner
1330    }
1331
1332    fn picture_has_clip(app: &App, boundary: AnyRenderObject) -> bool {
1333        let layer = boundary.debug_layer(app).expect("painted");
1334        layer
1335            .depth_first_iterate_children(app)
1336            .into_iter()
1337            .filter_map(|child| {
1338                app.handle::<PictureLayer>(child.id())
1339                    .and_then(|picture| picture.picture(app).map(|p| p.ops().to_vec()))
1340            })
1341            .flatten()
1342            .any(|op| matches!(op, Op::ClipPath { .. }))
1343    }
1344
1345    /// `wrap_test.dart`: "Wrap respects clipBehavior", which defaults to no clip.
1346    #[test]
1347    fn an_overflowing_wrap_clips_only_when_asked() {
1348        for (clip_behavior, clips) in [(None, false), (Some(Clip::HardEdge), true)] {
1349            let cell = AppCell::new();
1350            let mut app = cell.borrow_mut();
1351            let child = RenderConstrainedBox::new(
1352                &mut app,
1353                BoxConstraints::tight(Size::square(200.0)),
1354                None,
1355            );
1356            let wrap = RenderWrap::new(&mut app);
1357            wrap.set_text_direction(&mut app, Some(TextDirection::Ltr));
1358            if let Some(clip_behavior) = clip_behavior {
1359                wrap.set_clip_behavior(&mut app, clip_behavior);
1360            }
1361            wrap.add(&mut app, child.as_box());
1362            let root = RenderRepaintBoundary::new(&mut app, Some(wrap.as_box()));
1363
1364            let owner = first_frame(
1365                &mut app,
1366                root.as_box(),
1367                BoxConstraints::new().max_width(100.0).max_height(100.0),
1368            );
1369
1370            assert_eq!(
1371                picture_has_clip(&app, root.as_object()),
1372                clips,
1373                "{clip_behavior:?}"
1374            );
1375            let _ = owner;
1376        }
1377    }
1378}