Skip to main content

inset_rendering/
stack.rs

1//! Flutter counterpart: `rendering/stack.dart` (`RelativeRect`, `StackParentData`, `StackFit`,
2//! `RenderStack`, `RenderIndexedStack`).
3//!
4//! Baselines wait; see `PORTING.md`.
5
6use std::any::{Any, TypeId};
7use std::fmt::{self, Debug};
8
9use inset_embedder::{Clip, Offset, Rect, Size, TextDirection, lerp_double};
10use inset_foundation::{App, Handle};
11use inset_painting::{Alignment, AlignmentGeometry};
12
13use crate::box_::{
14    AnyRenderBox, BoxConstraints, BoxHitTestResult, BoxParentData, ContainerBoxParentData,
15    RenderBox, RenderBoxContainerDefaultsMixin, RenderBoxData,
16};
17use crate::layer::{ClipRectLayer, LayerHandle};
18use crate::layout_helper::ChildLayoutHelper;
19use crate::object::{
20    AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectData,
21    ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, RenderObjectData,
22};
23use crate::painting_context::PaintingContext;
24use crate::pipeline_owner::PipelineOwner;
25
26/// An immutable 2D, axis-aligned, floating-point rectangle whose coordinates are given relative
27/// to another rectangle's edges, known as the container.
28///
29/// Since the dimensions of the rectangle are relative to those of the container, this type has no
30/// width and height members. To determine the width or height of the rectangle, convert it to a
31/// [`Rect`] using [`to_rect`](Self::to_rect) (passing the container's own [`Rect`]), and then
32/// examine that object.
33#[derive(Clone, Copy, PartialEq)]
34pub struct RelativeRect {
35    /// Distance from the left side of the container to the left side of this rectangle.
36    ///
37    /// May be negative if the left side of the rectangle is outside of the container.
38    pub left: f64,
39
40    /// Distance from the top side of the container to the top side of this rectangle.
41    ///
42    /// May be negative if the top side of the rectangle is outside of the container.
43    pub top: f64,
44
45    /// Distance from the right side of the container to the right side of this rectangle.
46    ///
47    /// May be positive if the right side of the rectangle is outside of the container.
48    pub right: f64,
49
50    /// Distance from the bottom side of the container to the bottom side of this rectangle.
51    ///
52    /// May be positive if the bottom side of the rectangle is outside of the container.
53    pub bottom: f64,
54}
55
56impl RelativeRect {
57    /// Creates a [`RelativeRect`] with the given values.
58    pub const fn from_ltrb(left: f64, top: f64, right: f64, bottom: f64) -> RelativeRect {
59        RelativeRect {
60            left,
61            top,
62            right,
63            bottom,
64        }
65    }
66
67    /// Creates a [`RelativeRect`] from a [`Rect`] and a [`Size`].
68    ///
69    /// The rect and the [`RelativeRect`] (the output) are in the coordinate space of the
70    /// rectangle described by the size, with 0,0 being at the top left.
71    pub fn from_size(rect: Rect, container: Size) -> RelativeRect {
72        RelativeRect {
73            left: rect.left,
74            top: rect.top,
75            right: container.width() - rect.right,
76            bottom: container.height() - rect.bottom,
77        }
78    }
79
80    /// Creates a [`RelativeRect`] from two [`Rect`]s.
81    ///
82    /// `container` provides the container; `rect` provides the rectangle, in the same coordinate
83    /// space, that is to be converted to a [`RelativeRect`]. The output will be in the
84    /// container's coordinate space.
85    ///
86    /// For example, if the top left of the rect is at 0,0, and the top left of the container is
87    /// at 100,100, then the top left of the output will be at -100,-100.
88    ///
89    /// If `rect` is actually in the container's coordinate space, then use
90    /// [`from_size`](Self::from_size) and pass the container's size as the second argument
91    /// instead.
92    pub fn from_rect(rect: Rect, container: Rect) -> RelativeRect {
93        RelativeRect {
94            left: rect.left - container.left,
95            top: rect.top - container.top,
96            right: container.right - rect.right,
97            bottom: container.bottom - rect.bottom,
98        }
99    }
100
101    /// Creates a [`RelativeRect`] from horizontal position using `start` and `end` rather than
102    /// `left` and `right`.
103    ///
104    /// If `text_direction` is [`TextDirection::Rtl`], then `start` is used for the
105    /// [`right`](Self::right) property and `end` for the [`left`](Self::left) property.
106    /// Otherwise, `start` is used for [`left`](Self::left) and `end` for
107    /// [`right`](Self::right).
108    pub fn from_directional(
109        text_direction: TextDirection,
110        start: f64,
111        top: f64,
112        end: f64,
113        bottom: f64,
114    ) -> RelativeRect {
115        let (left, right) = match text_direction {
116            TextDirection::Rtl => (end, start),
117            TextDirection::Ltr => (start, end),
118        };
119        RelativeRect::from_ltrb(left, top, right, bottom)
120    }
121
122    /// A rect that covers the entire container.
123    pub const FILL: RelativeRect = RelativeRect::from_ltrb(0.0, 0.0, 0.0, 0.0);
124
125    /// Returns whether any of the values are greater than zero.
126    ///
127    /// This corresponds to one of the sides ([`left`](Self::left), [`top`](Self::top),
128    /// [`right`](Self::right), or [`bottom`](Self::bottom)) having some positive inset towards
129    /// the center.
130    pub fn has_insets(&self) -> bool {
131        self.left > 0.0 || self.top > 0.0 || self.right > 0.0 || self.bottom > 0.0
132    }
133
134    /// Returns a new rectangle object translated by the given offset.
135    pub fn shift(&self, offset: Offset) -> RelativeRect {
136        RelativeRect::from_ltrb(
137            self.left + offset.dx(),
138            self.top + offset.dy(),
139            self.right - offset.dx(),
140            self.bottom - offset.dy(),
141        )
142    }
143
144    /// Returns a new rectangle with edges moved outwards by the given delta.
145    pub fn inflate(&self, delta: f64) -> RelativeRect {
146        RelativeRect::from_ltrb(
147            self.left - delta,
148            self.top - delta,
149            self.right - delta,
150            self.bottom - delta,
151        )
152    }
153
154    /// Returns a new rectangle with edges moved inwards by the given delta.
155    pub fn deflate(&self, delta: f64) -> RelativeRect {
156        self.inflate(-delta)
157    }
158
159    /// Returns a new rectangle that is the intersection of the given rectangle and this one.
160    pub fn intersect(&self, other: RelativeRect) -> RelativeRect {
161        RelativeRect::from_ltrb(
162            self.left.max(other.left),
163            self.top.max(other.top),
164            self.right.max(other.right),
165            self.bottom.max(other.bottom),
166        )
167    }
168
169    /// Convert this [`RelativeRect`] to a [`Rect`], in the coordinate space of the container.
170    ///
171    /// See also:
172    ///
173    ///  * [`to_size`](Self::to_size), which returns the size part of the rect, based on the size
174    ///    of the container.
175    pub fn to_rect(&self, container: Rect) -> Rect {
176        Rect::from_ltrb(
177            self.left,
178            self.top,
179            container.width() - self.right,
180            container.height() - self.bottom,
181        )
182    }
183
184    /// Convert this [`RelativeRect`] to a [`Size`], assuming a container with the given size.
185    ///
186    /// See also:
187    ///
188    ///  * [`to_rect`](Self::to_rect), which also computes the position relative to the container.
189    pub fn to_size(&self, container: Size) -> Size {
190        Size::new(
191            container.width() - self.left - self.right,
192            container.height() - self.top - self.bottom,
193        )
194    }
195
196    /// Linearly interpolate between two [`RelativeRect`]s.
197    ///
198    /// If either rect is `None`, this function interpolates from [`FILL`](Self::FILL).
199    pub fn lerp(a: Option<RelativeRect>, b: Option<RelativeRect>, t: f64) -> Option<RelativeRect> {
200        match (a, b) {
201            (None, None) => None,
202            (None, Some(b)) => Some(RelativeRect::from_ltrb(
203                b.left * t,
204                b.top * t,
205                b.right * t,
206                b.bottom * t,
207            )),
208            (Some(a), None) => {
209                let k = 1.0 - t;
210                Some(RelativeRect::from_ltrb(
211                    a.left * k,
212                    a.top * k,
213                    a.right * k,
214                    a.bottom * k,
215                ))
216            }
217            (Some(a), Some(b)) => Some(RelativeRect::from_ltrb(
218                lerp_double(Some(a.left), Some(b.left), t).unwrap(),
219                lerp_double(Some(a.top), Some(b.top), t).unwrap(),
220                lerp_double(Some(a.right), Some(b.right), t).unwrap(),
221                lerp_double(Some(a.bottom), Some(b.bottom), t).unwrap(),
222            )),
223        }
224    }
225}
226
227impl Debug for RelativeRect {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        write!(
230            f,
231            "RelativeRect::from_ltrb({:.1}, {:.1}, {:.1}, {:.1})",
232            self.left, self.top, self.right, self.bottom
233        )
234    }
235}
236
237/// Parent data for use with [`RenderStack`].
238#[derive(Debug)]
239pub struct StackParentData {
240    box_parent_data: BoxParentData,
241    container_parent_data: ContainerParentData<AnyRenderBox>,
242
243    /// The distance by which the child's top edge is inset from the top of the stack.
244    pub top: Option<f64>,
245
246    /// The distance by which the child's right edge is inset from the right of the stack.
247    pub right: Option<f64>,
248
249    /// The distance by which the child's bottom edge is inset from the bottom of the stack.
250    pub bottom: Option<f64>,
251
252    /// The distance by which the child's left edge is inset from the left of the stack.
253    pub left: Option<f64>,
254
255    /// The child's width.
256    ///
257    /// Ignored if both [`left`](Self::left) and [`right`](Self::right) are non-null.
258    pub width: Option<f64>,
259
260    /// The child's height.
261    ///
262    /// Ignored if both [`top`](Self::top) and [`bottom`](Self::bottom) are non-null.
263    pub height: Option<f64>,
264}
265
266impl StackParentData {
267    /// Creates stack parent data for a non-positioned child.
268    pub const fn new() -> StackParentData {
269        StackParentData {
270            box_parent_data: BoxParentData::new(),
271            container_parent_data: ContainerParentData::new(),
272            top: None,
273            right: None,
274            bottom: None,
275            left: None,
276            width: None,
277            height: None,
278        }
279    }
280
281    /// The current values in terms of a [`RelativeRect`].
282    ///
283    /// # Panics
284    ///
285    /// If any of the four edges is unset.
286    pub fn rect(&self) -> RelativeRect {
287        RelativeRect::from_ltrb(
288            self.left.expect("left is set"),
289            self.top.expect("top is set"),
290            self.right.expect("right is set"),
291            self.bottom.expect("bottom is set"),
292        )
293    }
294
295    /// Sets [`rect`](Self::rect).
296    pub fn set_rect(&mut self, value: RelativeRect) {
297        self.top = Some(value.top);
298        self.right = Some(value.right);
299        self.bottom = Some(value.bottom);
300        self.left = Some(value.left);
301    }
302
303    /// Whether this child is considered positioned.
304    ///
305    /// A child is positioned if any of the top, right, bottom, or left properties are non-null.
306    /// Positioned children do not factor into determining the size of the stack but are instead
307    /// placed relative to the non-positioned children in the stack.
308    pub fn is_positioned(&self) -> bool {
309        self.top.is_some()
310            || self.right.is_some()
311            || self.bottom.is_some()
312            || self.left.is_some()
313            || self.width.is_some()
314            || self.height.is_some()
315    }
316
317    /// Computes the [`BoxConstraints`] the stack layout algorithm would give to this child,
318    /// given the [`Size`] of the stack.
319    ///
320    /// This method should only be called when [`is_positioned`](Self::is_positioned) is true for
321    /// the child.
322    pub fn positioned_child_constraints(&self, stack_size: Size) -> BoxConstraints {
323        debug_assert!(self.is_positioned());
324        let width = match (self.left, self.right) {
325            (Some(left), Some(right)) => Some(stack_size.width() - right - left),
326            _ => self.width,
327        };
328        let height = match (self.top, self.bottom) {
329            (Some(top), Some(bottom)) => Some(stack_size.height() - bottom - top),
330            _ => self.height,
331        };
332        debug_assert!(height.is_none_or(|height| !height.is_nan()));
333        debug_assert!(width.is_none_or(|width| !width.is_nan()));
334        BoxConstraints::tight_for(
335            width.map(|width| width.max(0.0)),
336            height.map(|height| height.max(0.0)),
337        )
338    }
339}
340
341impl Default for StackParentData {
342    fn default() -> StackParentData {
343        StackParentData::new()
344    }
345}
346
347impl ParentData for StackParentData {
348    fn detach(&mut self) {
349        ContainerParentDataMixin::detach(self);
350    }
351
352    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
353        if id == TypeId::of::<StackParentData>() {
354            return Some(self);
355        }
356        self.box_parent_data.provide(id)
357    }
358
359    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
360        if id == TypeId::of::<StackParentData>() {
361            return Some(self);
362        }
363        self.box_parent_data.provide_mut(id)
364    }
365}
366
367impl ContainerParentDataMixin for StackParentData {
368    type ChildType = AnyRenderBox;
369
370    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
371        &self.container_parent_data
372    }
373
374    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
375        &mut self.container_parent_data
376    }
377}
378
379impl ContainerBoxParentData for StackParentData {
380    fn box_parent_data(&self) -> &BoxParentData {
381        &self.box_parent_data
382    }
383
384    fn box_parent_data_mut(&mut self) -> &mut BoxParentData {
385        &mut self.box_parent_data
386    }
387}
388
389impl fmt::Display for StackParentData {
390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391        let mut values = Vec::new();
392        for (name, value) in [
393            ("top", self.top),
394            ("right", self.right),
395            ("bottom", self.bottom),
396            ("left", self.left),
397            ("width", self.width),
398            ("height", self.height),
399        ] {
400            if let Some(value) = value {
401                values.push(format!("{name}={value}"));
402            }
403        }
404        if values.is_empty() {
405            values.push("not positioned".to_string());
406        }
407        values.push(self.box_parent_data.to_string());
408        f.write_str(&values.join("; "))
409    }
410}
411
412/// How to size the non-positioned children of a [`RenderStack`].
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414pub enum StackFit {
415    /// The constraints passed to the stack from its parent are loosened.
416    ///
417    /// For example, if the stack has constraints that force it to 350x600, then this would allow
418    /// the non-positioned children of the stack to have any width from zero to 350 and any
419    /// height from zero to 600.
420    Loose,
421
422    /// The constraints passed to the stack from its parent are tightened to the biggest size
423    /// allowed.
424    ///
425    /// For example, if the stack has loose constraints with a width in the range 10 to 100 and a
426    /// height in the range 0 to 600, then the non-positioned children of the stack would all be
427    /// sized as 100 pixels wide and 600 high.
428    Expand,
429
430    /// The constraints passed to the stack from its parent are passed unmodified to the
431    /// non-positioned children.
432    Passthrough,
433}
434
435/// Flutter's `RenderStack` fields.
436pub struct RenderStackData {
437    has_visual_overflow: bool,
438    alignment: AlignmentGeometry,
439    text_direction: Option<TextDirection>,
440    fit: StackFit,
441    clip_behavior: Clip,
442    resolved_alignment_cache: Option<Alignment>,
443    clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
444}
445
446impl RenderStackData {
447    /// The stack defaults: children aligned by their top start corners, loosely fit, clipped.
448    pub fn new() -> RenderStackData {
449        RenderStackData {
450            has_visual_overflow: false,
451            alignment: AlignmentGeometry::TOP_START,
452            text_direction: None,
453            fit: StackFit::Loose,
454            clip_behavior: Clip::HardEdge,
455            resolved_alignment_cache: None,
456            clip_rect_layer: LayerHandle::new(),
457        }
458    }
459}
460
461impl Default for RenderStackData {
462    fn default() -> RenderStackData {
463        RenderStackData::new()
464    }
465}
466
467/// Helper function for calculating the intrinsic metrics of a stack.
468///
469/// Flutter's `RenderStack.getIntrinsicDimension`.
470pub fn get_intrinsic_dimension(
471    app: &mut App,
472    first_child: Option<AnyRenderBox>,
473    mut main_child_size_getter: impl FnMut(&mut App, AnyRenderBox) -> f64,
474) -> f64 {
475    let mut extent: f64 = 0.0;
476    let mut child = first_child;
477    while let Some(current) = child {
478        let child_parent_data = current.as_object().parent_data_of::<StackParentData>(app);
479        let (is_positioned, next_sibling) = (
480            child_parent_data.is_positioned(),
481            child_parent_data.next_sibling(),
482        );
483        if !is_positioned {
484            extent = extent.max(main_child_size_getter(app, current));
485        }
486        child = next_sibling;
487    }
488    extent
489}
490
491/// Lays out the positioned `child` according to `alignment` within a stack of `size`.
492///
493/// Returns true when the child has visual overflow. Flutter's `RenderStack.layoutPositionedChild`;
494/// it also takes the child's [`StackParentData`], which is read from the child here.
495pub fn layout_positioned_child(
496    app: &mut App,
497    child: AnyRenderBox,
498    size: Size,
499    alignment: Alignment,
500) -> bool {
501    let child_constraints = {
502        let child_parent_data = child.as_object().parent_data_of::<StackParentData>(app);
503        debug_assert!(child_parent_data.is_positioned());
504        child_parent_data.positioned_child_constraints(size)
505    };
506    child.layout(app, child_constraints, true);
507
508    let child_size = child.size(app);
509    let child_parent_data = child.as_object().parent_data_of::<StackParentData>(app);
510    let x = match (child_parent_data.left, child_parent_data.right) {
511        (Some(left), _) => left,
512        (None, Some(right)) => size.width() - right - child_size.width(),
513        (None, None) => alignment.along_offset(size - child_size).dx(),
514    };
515    let y = match (child_parent_data.top, child_parent_data.bottom) {
516        (Some(top), _) => top,
517        (None, Some(bottom)) => size.height() - bottom - child_size.height(),
518        (None, None) => alignment.along_offset(size - child_size).dy(),
519    };
520
521    child
522        .parent_data_of_mut::<StackParentData>(app)
523        .set_offset(Offset::new(x, y));
524    x < 0.0
525        || x + child_size.width() > size.width()
526        || y < 0.0
527        || y + child_size.height() > size.height()
528}
529
530/// Implements the stack layout algorithm.
531///
532/// In a stack layout, the children are positioned on top of each other in the order in which they
533/// appear in the child list. First, the non-positioned children (those with null values for top,
534/// right, bottom, and left) are laid out and initially placed in the upper-left corner of the
535/// stack. The stack is then sized to enclose all of the non-positioned children. If there are no
536/// non-positioned children, the stack becomes as large as possible.
537///
538/// The final location of non-positioned children is determined by the alignment parameter. The
539/// left of each non-positioned child becomes the difference between the stack's width and the
540/// child's width multiplied by (alignment.x + 1.0) / 2.0. The top of each non-positioned child is
541/// computed similarly from the stack's height, the child's height, and `alignment.y`.
542///
543/// Next, the positioned children are laid out. If a child has top and bottom values that are both
544/// non-null, the child is given a fixed height determined by subtracting the sum of the top and
545/// bottom values from the height of the stack. Similarly, if the child has right and left values
546/// that are both non-null, the child is given a fixed width derived from the stack's width.
547/// Otherwise, the child is given unbounded constraints in the non-fixed dimensions.
548///
549/// Once the child is laid out, the stack positions the child according to the top, right, bottom,
550/// and left properties of their [`StackParentData`]. If the child extends beyond the bounds of the
551/// stack, the stack will clip the child's painting to the bounds of the stack.
552///
553/// Flutter's `RenderStack` bodies. [`RenderStack`] is the plain leaf; [`RenderIndexedStack`]
554/// implements this trait too and overrides [`paint_stack`](Self::paint_stack).
555pub trait RenderStackBase:
556    RenderBoxContainerDefaultsMixin<ParentDataType = StackParentData>
557{
558    /// Mixin field access.
559    fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData;
560
561    /// See [`stack_data`](Self::stack_data).
562    fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData;
563
564    /// Installs [`StackParentData`] unless the child already has it.
565    ///
566    /// The body of Flutter's `setupParentData` override; call it from
567    /// [`RenderBox::setup_parent_data`].
568    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
569        if !child.parent_data_is::<StackParentData>(app) {
570            child.set_parent_data(app, StackParentData::new());
571        }
572    }
573
574    /// The [`alignment`](Self::alignment) resolved against
575    /// [`text_direction`](Self::text_direction), cached until either changes.
576    fn resolved_alignment(self: RenderHandle<Self>, app: &mut App) -> Alignment {
577        if let Some(cached) = self.stack_data(app).resolved_alignment_cache {
578            return cached;
579        }
580        let resolved = self.alignment(app).resolve(self.text_direction(app));
581        self.stack_data_mut(app).resolved_alignment_cache = Some(resolved);
582        resolved
583    }
584
585    /// Drops the resolved alignment and marks layout dirty.
586    fn mark_need_resolution(self: RenderHandle<Self>, app: &mut App) {
587        self.stack_data_mut(app).resolved_alignment_cache = None;
588        self.mark_needs_layout(app);
589    }
590
591    /// How to align the non-positioned or partially-positioned children in the stack.
592    ///
593    /// The non-positioned children are placed relative to each other such that the points
594    /// determined by the alignment are co-located. For example, if the alignment is
595    /// `AlignmentGeometry::TOP_LEFT`, then the top left corner of each non-positioned child will
596    /// be located at the same global coordinate.
597    ///
598    /// Partially-positioned children, those that do not specify an alignment in a particular axis
599    /// (e.g. that have neither `top` nor `bottom` set), use the alignment to determine how they
600    /// should be positioned in that under-specified axis.
601    ///
602    /// If this is set to a directional alignment, then [`text_direction`](Self::text_direction)
603    /// must not be `None`.
604    fn alignment(self: RenderHandle<Self>, app: &App) -> AlignmentGeometry {
605        self.stack_data(app).alignment
606    }
607
608    /// Sets [`alignment`](Self::alignment).
609    fn set_alignment(self: RenderHandle<Self>, app: &mut App, value: AlignmentGeometry) {
610        if self.stack_data(app).alignment == value {
611            return;
612        }
613        self.stack_data_mut(app).alignment = value;
614        self.mark_need_resolution(app);
615    }
616
617    /// The text direction with which to resolve [`alignment`](Self::alignment).
618    ///
619    /// This may be changed to `None`, but only after the alignment has been changed to a value
620    /// that does not depend on the direction.
621    fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
622        self.stack_data(app).text_direction
623    }
624
625    /// Sets [`text_direction`](Self::text_direction).
626    fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: Option<TextDirection>) {
627        if self.stack_data(app).text_direction == value {
628            return;
629        }
630        self.stack_data_mut(app).text_direction = value;
631        self.mark_need_resolution(app);
632    }
633
634    /// How to size the non-positioned children in the stack.
635    ///
636    /// The constraints passed into the stack from its parent are either loosened
637    /// ([`StackFit::Loose`]) or tightened to their biggest size ([`StackFit::Expand`]).
638    fn fit(self: RenderHandle<Self>, app: &App) -> StackFit {
639        self.stack_data(app).fit
640    }
641
642    /// Sets [`fit`](Self::fit).
643    fn set_fit(self: RenderHandle<Self>, app: &mut App, value: StackFit) {
644        if self.stack_data(app).fit != value {
645            self.stack_data_mut(app).fit = value;
646            self.mark_needs_layout(app);
647        }
648    }
649
650    /// How to clip the children that overflow the stack.
651    ///
652    /// Stacks only clip children whose geometry overflows the stack. A child that paints outside
653    /// its bounds (e.g. a box with a shadow) will not be clipped, regardless of the value of this
654    /// property. Similarly, a child that itself has a descendant that overflows the stack will
655    /// not be clipped, as only the geometry of the stack's direct children are considered.
656    ///
657    /// Defaults to [`Clip::HardEdge`].
658    fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
659        self.stack_data(app).clip_behavior
660    }
661
662    /// Sets [`clip_behavior`](Self::clip_behavior).
663    fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
664        if value != self.stack_data(app).clip_behavior {
665            self.stack_data_mut(app).clip_behavior = value;
666            self.mark_needs_paint(app);
667        }
668    }
669
670    /// The stack's minimum intrinsic width: the widest non-positioned child's.
671    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
672        let first_child = self.first_child(app);
673        get_intrinsic_dimension(app, first_child, |app, child| {
674            child.get_min_intrinsic_width(app, height)
675        })
676    }
677
678    /// The stack's maximum intrinsic width: the widest non-positioned child's.
679    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
680        let first_child = self.first_child(app);
681        get_intrinsic_dimension(app, first_child, |app, child| {
682            child.get_max_intrinsic_width(app, height)
683        })
684    }
685
686    /// The stack's minimum intrinsic height: the tallest non-positioned child's.
687    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
688        let first_child = self.first_child(app);
689        get_intrinsic_dimension(app, first_child, |app, child| {
690            child.get_min_intrinsic_height(app, width)
691        })
692    }
693
694    /// The stack's maximum intrinsic height: the tallest non-positioned child's.
695    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
696        let first_child = self.first_child(app);
697        get_intrinsic_dimension(app, first_child, |app, child| {
698            child.get_max_intrinsic_height(app, width)
699        })
700    }
701
702    /// The size the stack would take, measuring its non-positioned children dry.
703    fn compute_dry_layout(
704        self: RenderHandle<Self>,
705        app: &mut App,
706        constraints: BoxConstraints,
707    ) -> Size {
708        self.compute_size(app, constraints, ChildLayoutHelper::dry_layout_child)
709    }
710
711    /// Flutter's `_computeSize`: measures the non-positioned children with `layout_child` and
712    /// sizes the stack.
713    fn compute_size(
714        self: RenderHandle<Self>,
715        app: &mut App,
716        constraints: BoxConstraints,
717        mut layout_child: impl FnMut(&mut App, AnyRenderBox, BoxConstraints) -> Size,
718    ) -> Size {
719        let mut has_non_positioned_children = false;
720        if self.child_count(app) == 0 {
721            return if constraints.biggest().is_finite() {
722                constraints.biggest()
723            } else {
724                constraints.smallest()
725            };
726        }
727
728        let mut width = constraints.min_width;
729        let mut height = constraints.min_height;
730
731        let non_positioned_constraints = match self.fit(app) {
732            StackFit::Loose => constraints.loosen(),
733            StackFit::Expand => BoxConstraints::tight(constraints.biggest()),
734            StackFit::Passthrough => constraints,
735        };
736
737        let mut child = self.first_child(app);
738        while let Some(current) = child {
739            let is_positioned = current
740                .as_object()
741                .parent_data_of::<StackParentData>(app)
742                .is_positioned();
743
744            if !is_positioned {
745                has_non_positioned_children = true;
746
747                let child_size = layout_child(app, current, non_positioned_constraints);
748
749                width = width.max(child_size.width());
750                height = height.max(child_size.height());
751            }
752
753            child = self.child_after(app, current);
754        }
755
756        let size = if has_non_positioned_children {
757            let size = Size::new(width, height);
758            debug_assert_eq!(size.width(), constraints.constrain_width(width));
759            debug_assert_eq!(size.height(), constraints.constrain_height(height));
760            size
761        } else {
762            constraints.biggest()
763        };
764
765        debug_assert!(
766            size.is_finite(),
767            "A Stack requires bounded constraints from its parent. This error commonly occurs \
768             when a Stack is placed inside a widget like Column, ListView, or other widgets that \
769             do not constrain their children. To fix this, wrap the Stack in a widget that \
770             provides finite height and width constraints, such as a SizedBox or ConstrainedBox. \
771             Use Expanded only if the parent is a Flex widget like Row or Column."
772        );
773        size
774    }
775
776    /// The body of Flutter's `performLayout` override.
777    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
778        let constraints = self.constraints(app);
779        self.stack_data_mut(app).has_visual_overflow = false;
780
781        let size = self.compute_size(app, constraints, ChildLayoutHelper::layout_child);
782        self.set_size(app, size);
783
784        let resolved_alignment = self.resolved_alignment(app);
785        let mut child = self.first_child(app);
786        while let Some(current) = child {
787            let is_positioned = current
788                .as_object()
789                .parent_data_of::<StackParentData>(app)
790                .is_positioned();
791
792            if !is_positioned {
793                let offset = resolved_alignment.along_offset(size - current.size(app));
794                current
795                    .parent_data_of_mut::<StackParentData>(app)
796                    .set_offset(offset);
797            } else {
798                let overflowed = layout_positioned_child(app, current, size, resolved_alignment);
799                self.stack_data_mut(app).has_visual_overflow |= overflowed;
800            }
801
802            child = self.child_after(app, current);
803        }
804    }
805
806    /// Override in subclasses to customize how the stack paints.
807    ///
808    /// By default, the stack uses [`default_paint`](RenderBoxContainerDefaultsMixin::default_paint).
809    /// This function is called by [`paint`](Self::paint) after potentially applying a clip to
810    /// contain visual overflow.
811    fn paint_stack(
812        self: RenderHandle<Self>,
813        app: &mut App,
814        context: &mut PaintingContext,
815        offset: Offset,
816    ) {
817        self.default_paint(app, context, offset);
818    }
819
820    /// The body of Flutter's `paint` override.
821    fn paint(
822        self: RenderHandle<Self>,
823        app: &mut App,
824        context: &mut PaintingContext,
825        offset: Offset,
826    ) {
827        let clip_behavior = self.clip_behavior(app);
828        if clip_behavior != Clip::None && self.stack_data(app).has_visual_overflow {
829            let size = self.size(app);
830            let old = self.stack_data(app).clip_rect_layer.layer();
831            let layer = context.push_clip_rect(
832                app,
833                self.as_object().needs_compositing(app),
834                offset,
835                Offset::ZERO & size,
836                |app, context, offset| self.paint_stack(app, context, offset),
837                clip_behavior,
838                old,
839            );
840            LayerHandle::set_layer(
841                app,
842                |app| &mut self.stack_data_mut(app).clip_rect_layer,
843                layer,
844            );
845        } else {
846            LayerHandle::set_layer(
847                app,
848                |app| &mut self.stack_data_mut(app).clip_rect_layer,
849                None,
850            );
851            self.paint_stack(app, context, offset);
852        }
853    }
854
855    /// Flutter's `dispose`: drop the clip layer handle, then `super.dispose()`.
856    fn dispose(self: RenderHandle<Self>, app: &mut App) {
857        LayerHandle::set_layer(
858            app,
859            |app| &mut self.stack_data_mut(app).clip_rect_layer,
860            None,
861        );
862        crate::object::RenderObjectBase::dispose(self, app);
863    }
864}
865
866/// The plain [`RenderStackBase`]: paints every child in order.
867pub struct RenderStack {
868    render_object: RenderObjectData,
869    render_box: RenderBoxData,
870    container: ContainerRenderObjectData<AnyRenderBox>,
871    stack: RenderStackData,
872}
873
874impl RenderStack {
875    /// Creates a stack render object.
876    ///
877    /// By default, the non-positioned children of the stack are aligned by their top start
878    /// corners. Add children with [`add`](ContainerRenderObjectMixin::add) or
879    /// [`add_all`](ContainerRenderObjectMixin::add_all); Dart's constructor takes them.
880    pub fn new(app: &mut App) -> RenderHandle<RenderStack> {
881        RenderHandle::new_box(
882            app,
883            RenderStack {
884                render_object: RenderObjectData::new(),
885                render_box: RenderBoxData::new(),
886                container: ContainerRenderObjectData::new(),
887                stack: RenderStackData::new(),
888            },
889        )
890    }
891}
892
893impl ContainerRenderObjectMixin for RenderStack {
894    type ChildType = AnyRenderBox;
895    type ParentDataType = StackParentData;
896
897    fn container_data(
898        self: RenderHandle<Self>,
899        app: &App,
900    ) -> &ContainerRenderObjectData<AnyRenderBox> {
901        &self.get(app).container
902    }
903
904    fn container_data_mut(
905        self: RenderHandle<Self>,
906        app: &mut App,
907    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
908        &mut self.get_mut(app).container
909    }
910}
911
912impl RenderBoxContainerDefaultsMixin for RenderStack {}
913
914impl RenderStackBase for RenderStack {
915    fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData {
916        &self.get(app).stack
917    }
918
919    fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData {
920        &mut self.get_mut(app).stack
921    }
922}
923
924impl RenderObject for RenderStack {
925    crate::render_object_accessors!();
926
927    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
928        RenderStackBase::perform_layout(self, app)
929    }
930
931    fn visit_children(
932        self: RenderHandle<Self>,
933        app: &App,
934        visitor: &mut dyn FnMut(AnyRenderObject),
935    ) {
936        ContainerRenderObjectMixin::visit_children(self, app, visitor)
937    }
938
939    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
940        ContainerRenderObjectMixin::did_attach(self, app, owner)
941    }
942
943    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
944        ContainerRenderObjectMixin::did_detach(self, app)
945    }
946
947    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
948        ContainerRenderObjectMixin::redepth_children(self, app)
949    }
950
951    fn paint(
952        self: RenderHandle<Self>,
953        app: &mut App,
954        context: &mut PaintingContext,
955        offset: Offset,
956    ) {
957        RenderStackBase::paint(self, app, context, offset)
958    }
959
960    fn dispose(self: RenderHandle<Self>, app: &mut App) {
961        RenderStackBase::dispose(self, app)
962    }
963}
964
965impl RenderBox for RenderStack {
966    crate::render_box_accessors!();
967
968    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
969        RenderStackBase::setup_parent_data(self, app, child)
970    }
971
972    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
973        RenderStackBase::compute_min_intrinsic_width(self, app, height)
974    }
975
976    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
977        RenderStackBase::compute_max_intrinsic_width(self, app, height)
978    }
979
980    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
981        RenderStackBase::compute_min_intrinsic_height(self, app, width)
982    }
983
984    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
985        RenderStackBase::compute_max_intrinsic_height(self, app, width)
986    }
987
988    fn compute_dry_layout(
989        self: RenderHandle<Self>,
990        app: &mut App,
991        constraints: BoxConstraints,
992    ) -> Size {
993        RenderStackBase::compute_dry_layout(self, app, constraints)
994    }
995
996    fn hit_test_children(
997        self: RenderHandle<Self>,
998        app: &mut App,
999        result: &mut BoxHitTestResult<'_>,
1000        position: Offset,
1001    ) -> bool {
1002        self.default_hit_test_children(app, result, position)
1003    }
1004}
1005
1006/// Implements the same layout algorithm as [`RenderStack`] but only paints the child specified by
1007/// [`index`](Self::index).
1008///
1009/// Although only one child is displayed, the cost of the layout algorithm is still O(N), like an
1010/// ordinary stack.
1011pub struct RenderIndexedStack {
1012    render_object: RenderObjectData,
1013    render_box: RenderBoxData,
1014    container: ContainerRenderObjectData<AnyRenderBox>,
1015    stack: RenderStackData,
1016    index: Option<i32>,
1017}
1018
1019impl RenderIndexedStack {
1020    /// Creates a stack render object that paints a single child.
1021    ///
1022    /// If [`index`](Self::index) is set to `None`, nothing is displayed.
1023    pub fn new(app: &mut App) -> RenderHandle<RenderIndexedStack> {
1024        RenderHandle::new_box(
1025            app,
1026            RenderIndexedStack {
1027                render_object: RenderObjectData::new(),
1028                render_box: RenderBoxData::new(),
1029                container: ContainerRenderObjectData::new(),
1030                stack: RenderStackData::new(),
1031                index: Some(0),
1032            },
1033        )
1034    }
1035
1036    /// The index of the child to show, `None` if nothing is to be displayed.
1037    pub fn index(self: RenderHandle<Self>, app: &App) -> Option<i32> {
1038        self.get(app).index
1039    }
1040
1041    /// Sets [`index`](Self::index).
1042    pub fn set_index(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
1043        if self.get(app).index != value {
1044            self.get_mut(app).index = value;
1045            self.mark_needs_layout(app);
1046        }
1047    }
1048
1049    fn child_at_index(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
1050        let index = self.index(app)?;
1051        let mut child = self.first_child(app);
1052        let mut i = 0;
1053        while i < index
1054            && let Some(current) = child
1055        {
1056            child = self.child_after(app, current);
1057            i += 1;
1058        }
1059        debug_assert!(self.first_child(app).is_none() || child.is_some());
1060        child
1061    }
1062}
1063
1064impl ContainerRenderObjectMixin for RenderIndexedStack {
1065    type ChildType = AnyRenderBox;
1066    type ParentDataType = StackParentData;
1067
1068    fn container_data(
1069        self: RenderHandle<Self>,
1070        app: &App,
1071    ) -> &ContainerRenderObjectData<AnyRenderBox> {
1072        &self.get(app).container
1073    }
1074
1075    fn container_data_mut(
1076        self: RenderHandle<Self>,
1077        app: &mut App,
1078    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
1079        &mut self.get_mut(app).container
1080    }
1081}
1082
1083impl RenderBoxContainerDefaultsMixin for RenderIndexedStack {}
1084
1085impl RenderStackBase for RenderIndexedStack {
1086    fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData {
1087        &self.get(app).stack
1088    }
1089
1090    fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData {
1091        &mut self.get_mut(app).stack
1092    }
1093
1094    fn paint_stack(
1095        self: RenderHandle<Self>,
1096        app: &mut App,
1097        context: &mut PaintingContext,
1098        offset: Offset,
1099    ) {
1100        let Some(displayed_child) = self.child_at_index(app) else {
1101            return;
1102        };
1103        let child_offset = displayed_child
1104            .as_object()
1105            .parent_data_of::<StackParentData>(app)
1106            .offset();
1107        context.paint_child(app, displayed_child.as_object(), child_offset + offset);
1108    }
1109}
1110
1111impl RenderObject for RenderIndexedStack {
1112    crate::render_object_accessors!();
1113
1114    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1115        RenderStackBase::perform_layout(self, app)
1116    }
1117
1118    fn visit_children(
1119        self: RenderHandle<Self>,
1120        app: &App,
1121        visitor: &mut dyn FnMut(AnyRenderObject),
1122    ) {
1123        ContainerRenderObjectMixin::visit_children(self, app, visitor)
1124    }
1125
1126    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
1127        ContainerRenderObjectMixin::did_attach(self, app, owner)
1128    }
1129
1130    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
1131        ContainerRenderObjectMixin::did_detach(self, app)
1132    }
1133
1134    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
1135        ContainerRenderObjectMixin::redepth_children(self, app)
1136    }
1137
1138    fn paint(
1139        self: RenderHandle<Self>,
1140        app: &mut App,
1141        context: &mut PaintingContext,
1142        offset: Offset,
1143    ) {
1144        RenderStackBase::paint(self, app, context, offset)
1145    }
1146
1147    fn dispose(self: RenderHandle<Self>, app: &mut App) {
1148        RenderStackBase::dispose(self, app)
1149    }
1150}
1151
1152impl RenderBox for RenderIndexedStack {
1153    crate::render_box_accessors!();
1154
1155    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
1156        RenderStackBase::setup_parent_data(self, app, child)
1157    }
1158
1159    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1160        RenderStackBase::compute_min_intrinsic_width(self, app, height)
1161    }
1162
1163    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
1164        RenderStackBase::compute_max_intrinsic_width(self, app, height)
1165    }
1166
1167    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1168        RenderStackBase::compute_min_intrinsic_height(self, app, width)
1169    }
1170
1171    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
1172        RenderStackBase::compute_max_intrinsic_height(self, app, width)
1173    }
1174
1175    fn compute_dry_layout(
1176        self: RenderHandle<Self>,
1177        app: &mut App,
1178        constraints: BoxConstraints,
1179    ) -> Size {
1180        RenderStackBase::compute_dry_layout(self, app, constraints)
1181    }
1182
1183    fn hit_test_children(
1184        self: RenderHandle<Self>,
1185        app: &mut App,
1186        result: &mut BoxHitTestResult<'_>,
1187        position: Offset,
1188    ) -> bool {
1189        let Some(displayed_child) = self.child_at_index(app) else {
1190            return false;
1191        };
1192        let child_offset = displayed_child
1193            .as_object()
1194            .parent_data_of::<StackParentData>(app)
1195            .offset();
1196        result.add_with_paint_offset(Some(child_offset), position, |result, transformed| {
1197            debug_assert_eq!(transformed, position - child_offset);
1198            displayed_child.hit_test(app, result, transformed)
1199        })
1200    }
1201}
1202
1203#[cfg(test)]
1204mod tests {
1205    use inset_foundation::AppCell;
1206    use std::cell::Cell;
1207    use std::rc::Rc;
1208
1209    use super::*;
1210    use crate::flex::{MainAxisSize, RenderFlex};
1211    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer, PictureLayer};
1212    use crate::pipeline_owner::PipelineOwner;
1213    use crate::proxy_box::{RenderConstrainedBox, RenderRepaintBoundary};
1214    use crate::shifted_box::RenderPadding;
1215    use inset_embedder::valo::Op;
1216    use inset_painting::EdgeInsetsGeometry;
1217
1218    /// A leaf that takes its preferred size where the constraints allow, and counts its paints.
1219    struct SizedTestBox {
1220        render_object: RenderObjectData,
1221        render_box: RenderBoxData,
1222        preferred_size: Size,
1223        paints: Rc<Cell<u32>>,
1224    }
1225
1226    impl RenderObject for SizedTestBox {
1227        crate::render_object_accessors!();
1228
1229        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
1230            let preferred_size = self.get(app).preferred_size;
1231            let size = self.constraints(app).constrain(preferred_size);
1232            self.set_size(app, size);
1233        }
1234
1235        fn paint(
1236            self: RenderHandle<Self>,
1237            app: &mut App,
1238            _context: &mut PaintingContext,
1239            _offset: Offset,
1240        ) {
1241            let paints = self.get(app).paints.clone();
1242            paints.set(paints.get() + 1);
1243        }
1244    }
1245
1246    impl RenderBox for SizedTestBox {
1247        crate::render_box_accessors!();
1248    }
1249
1250    fn sized_box(app: &mut App, size: Size) -> (AnyRenderBox, Rc<Cell<u32>>) {
1251        let paints = Rc::new(Cell::new(0));
1252        let box_ = RenderHandle::new_box(
1253            app,
1254            SizedTestBox {
1255                render_object: RenderObjectData::new(),
1256                render_box: RenderBoxData::new(),
1257                preferred_size: size,
1258                paints: paints.clone(),
1259            },
1260        )
1261        .as_box();
1262        (box_, paints)
1263    }
1264
1265    fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
1266        let root = OffsetLayer::new(app, Offset::ZERO);
1267        root.as_layer().attach(app, node.id());
1268        node.schedule_initial_paint(app, root.as_container_layer());
1269    }
1270
1271    /// The test binding's first frame: attach, lay the root out, schedule and flush paint.
1272    fn first_frame(
1273        app: &mut App,
1274        root: AnyRenderBox,
1275        constraints: BoxConstraints,
1276    ) -> Handle<PipelineOwner> {
1277        let owner = PipelineOwner::new(app, None);
1278        owner.set_root_node(app, Some(root.as_object()));
1279        root.layout(app, constraints, false);
1280        schedule_root_paint(app, root.as_object());
1281        owner.flush_compositing_bits(app);
1282        owner.flush_paint(app);
1283        owner
1284    }
1285
1286    fn picture_has_clip(app: &App, boundary: AnyRenderObject) -> bool {
1287        let layer = boundary.debug_layer(app).expect("painted");
1288        layer
1289            .depth_first_iterate_children(app)
1290            .into_iter()
1291            .filter_map(|child| {
1292                app.handle::<PictureLayer>(child.id())
1293                    .and_then(|picture| picture.picture(app).map(|p| p.ops().to_vec()))
1294            })
1295            .flatten()
1296            .any(|op| matches!(op, Op::ClipPath { .. }))
1297    }
1298
1299    /// `stack_test.dart`: a positioned child is sized and placed by its [`RelativeRect`], and does
1300    /// not take part in sizing the stack.
1301    /// `stack_test.dart`: a stack's intrinsics are the largest of its non-positioned children's,
1302    /// and a positioned child does not count.
1303    #[test]
1304    fn stack_intrinsics_are_the_largest_non_positioned_child() {
1305        let cell = AppCell::new();
1306        let mut app = cell.borrow_mut();
1307        let first =
1308            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
1309        let second =
1310            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(50.0, 20.0)), None);
1311        let positioned = RenderConstrainedBox::new(
1312            &mut app,
1313            BoxConstraints::tight(Size::new(200.0, 200.0)),
1314            None,
1315        );
1316        let stack = RenderStack::new(&mut app);
1317        stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1318        stack.add_all(
1319            &mut app,
1320            Some(vec![first.as_box(), second.as_box(), positioned.as_box()]),
1321        );
1322        positioned
1323            .as_box()
1324            .parent_data_of_mut::<StackParentData>(&mut app)
1325            .left = Some(0.0);
1326
1327        let box_ = stack.as_box();
1328        assert_eq!(box_.get_min_intrinsic_width(&mut app, 0.0), 50.0);
1329        assert_eq!(box_.get_max_intrinsic_width(&mut app, 0.0), 50.0);
1330        assert_eq!(box_.get_min_intrinsic_height(&mut app, 0.0), 40.0);
1331        assert_eq!(box_.get_max_intrinsic_height(&mut app, 0.0), 40.0);
1332    }
1333
1334    /// The dry layout of a tree of a padding inside a row inside a stack is the size that tree
1335    /// lays out to.
1336    #[test]
1337    fn dry_layout_matches_layout_for_a_padding_in_a_flex_in_a_stack() {
1338        let cell = AppCell::new();
1339        let mut app = cell.borrow_mut();
1340        let leaf =
1341            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(40.0, 20.0)), None);
1342        let padding = RenderPadding::new(
1343            &mut app,
1344            EdgeInsetsGeometry::all(5.0),
1345            None,
1346            Some(leaf.as_box()),
1347        );
1348        let row = RenderFlex::new(&mut app);
1349        row.set_text_direction(&mut app, Some(TextDirection::Ltr));
1350        row.set_main_axis_size(&mut app, MainAxisSize::Min);
1351        row.add_all(&mut app, Some(vec![padding.as_box()]));
1352        let stack = RenderStack::new(&mut app);
1353        stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1354        stack.add_all(&mut app, Some(vec![row.as_box()]));
1355
1356        let constraints = BoxConstraints::new().max_width(300.0).max_height(300.0);
1357        let dry = stack.as_box().get_dry_layout(&mut app, constraints);
1358        stack.layout(&mut app, constraints, false);
1359        assert_eq!(dry, Size::new(50.0, 30.0));
1360        assert_eq!(dry, stack.size(&app));
1361        assert_eq!(row.size(&app), Size::new(50.0, 30.0));
1362    }
1363
1364    #[test]
1365    fn positioned_child_is_laid_out_by_its_rect() {
1366        let cell = AppCell::new();
1367        let mut app = cell.borrow_mut();
1368        let (child, _) = sized_box(&mut app, Size::new(10.0, 10.0));
1369        let stack = RenderStack::new(&mut app);
1370        stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1371        stack.add(&mut app, child);
1372        child
1373            .parent_data_of_mut::<StackParentData>(&mut app)
1374            .set_rect(RelativeRect::from_ltrb(10.0, 20.0, 30.0, 40.0));
1375
1376        stack.layout(
1377            &mut app,
1378            BoxConstraints::tight(Size::new(100.0, 100.0)),
1379            false,
1380        );
1381
1382        // No non-positioned child, so the stack is as large as possible.
1383        assert_eq!(stack.size(&app), Size::new(100.0, 100.0));
1384        assert_eq!(child.size(&app), Size::new(60.0, 40.0));
1385        assert_eq!(child.box_parent_data(&app).offset, Offset::new(10.0, 20.0));
1386    }
1387
1388    /// `stack_test.dart`: `Stack fit`. The fit decides the constraints the non-positioned
1389    /// children get, and the stack sizes itself to them.
1390    #[test]
1391    fn non_positioned_children_are_sized_by_the_fit() {
1392        let cases = [
1393            (
1394                StackFit::Loose,
1395                Size::new(20.0, 20.0),
1396                Size::new(50.0, 50.0),
1397            ),
1398            (
1399                StackFit::Expand,
1400                Size::new(100.0, 100.0),
1401                Size::new(100.0, 100.0),
1402            ),
1403            (
1404                StackFit::Passthrough,
1405                Size::new(50.0, 50.0),
1406                Size::new(50.0, 50.0),
1407            ),
1408        ];
1409        for (fit, child_size, stack_size) in cases {
1410            let cell = AppCell::new();
1411            let mut app = cell.borrow_mut();
1412            let (child, _) = sized_box(&mut app, Size::new(20.0, 20.0));
1413            let stack = RenderStack::new(&mut app);
1414            stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1415            stack.set_fit(&mut app, fit);
1416            stack.add(&mut app, child);
1417
1418            stack.layout(
1419                &mut app,
1420                BoxConstraints {
1421                    min_width: 50.0,
1422                    max_width: 100.0,
1423                    min_height: 50.0,
1424                    max_height: 100.0,
1425                },
1426                false,
1427            );
1428
1429            assert_eq!(child.size(&app), child_size, "{fit:?}");
1430            assert_eq!(stack.size(&app), stack_size, "{fit:?}");
1431        }
1432    }
1433
1434    /// `stack_test.dart`: `RenderIndexedStack` lays every child out but paints only the one at
1435    /// [`RenderIndexedStack::index`].
1436    #[test]
1437    fn indexed_stack_paints_one_child() {
1438        let cell = AppCell::new();
1439        let mut app = cell.borrow_mut();
1440        let (first, first_paints) = sized_box(&mut app, Size::new(20.0, 20.0));
1441        let (second, second_paints) = sized_box(&mut app, Size::new(30.0, 30.0));
1442        let stack = RenderIndexedStack::new(&mut app);
1443        stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1444        stack.add_all(&mut app, Some(vec![first, second]));
1445        stack.set_index(&mut app, Some(1));
1446        let root = RenderRepaintBoundary::new(&mut app, Some(stack.as_box()));
1447
1448        let owner = first_frame(
1449            &mut app,
1450            root.as_box(),
1451            BoxConstraints::tight(Size::new(100.0, 100.0)),
1452        );
1453
1454        // Both children are laid out; only the one at the index is painted.
1455        assert_eq!(first.size(&app), Size::new(20.0, 20.0));
1456        assert_eq!(second.size(&app), Size::new(30.0, 30.0));
1457        assert_eq!(first_paints.get(), 0);
1458        assert_eq!(second_paints.get(), 1);
1459
1460        stack.set_index(&mut app, Some(0));
1461        owner.flush_layout(&mut app);
1462        owner.flush_compositing_bits(&mut app);
1463        owner.flush_paint(&mut app);
1464        assert_eq!(first_paints.get(), 1);
1465        assert_eq!(second_paints.get(), 1);
1466    }
1467
1468    /// `stack_test.dart`: a child that overflows the stack is clipped.
1469    #[test]
1470    fn overflowing_positioned_child_is_clipped() {
1471        for (clip_behavior, clips) in [(Clip::None, false), (Clip::HardEdge, true)] {
1472            let cell = AppCell::new();
1473            let mut app = cell.borrow_mut();
1474            let (child, _) = sized_box(&mut app, Size::new(10.0, 10.0));
1475            let stack = RenderStack::new(&mut app);
1476            stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
1477            stack.set_clip_behavior(&mut app, clip_behavior);
1478            stack.add(&mut app, child);
1479            child.parent_data_of_mut::<StackParentData>(&mut app).left = Some(-10.0);
1480            let root = RenderRepaintBoundary::new(&mut app, Some(stack.as_box()));
1481
1482            let owner = first_frame(
1483                &mut app,
1484                root.as_box(),
1485                BoxConstraints::tight(Size::new(100.0, 100.0)),
1486            );
1487
1488            let clipped = picture_has_clip(&app, root.as_object());
1489            assert_eq!(clipped, clips, "{clip_behavior:?}");
1490            let _ = owner;
1491        }
1492    }
1493}