Skip to main content

inset_rendering/
stack.rs

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