Skip to main content

inset_rendering/
custom_layout.rs

1//! Flutter counterpart: `rendering/custom_layout.dart` (`MultiChildLayoutParentData`,
2//! `MultiChildLayoutDelegate`, `RenderCustomMultiChildLayoutBox`).
3//!
4//! `SingleChildLayoutDelegate` and `RenderCustomSingleChildLayoutBox` live in
5//! `shifted_box.dart`.
6
7use std::any::{Any, TypeId};
8use std::collections::HashMap;
9use std::fmt::{self, Debug};
10use std::rc::Rc;
11
12use inset_embedder::{Offset, Size};
13use inset_foundation::{App, Handle, Key, Listenable, Listener};
14
15use crate::box_::{
16    AnyRenderBox, BoxConstraints, BoxHitTestResult, BoxParentData, ContainerBoxParentData,
17    RenderBox, RenderBoxContainerDefaultsMixin, RenderBoxData,
18};
19use crate::object::{
20    AnyRenderObject, Constraints, ContainerParentData, ContainerParentDataMixin,
21    ContainerRenderObjectData, ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject,
22    RenderObjectData,
23};
24use crate::painting_context::PaintingContext;
25use crate::pipeline_owner::PipelineOwner;
26
27/// An object representing the identity of a child of a [`RenderCustomMultiChildLayoutBox`].
28///
29/// Dart's `Object` id, which `LayoutId` also wraps in a `ValueKey` to key the widget.
30pub type ChildLayoutId = Rc<dyn Key>;
31
32/// [`ParentData`] used by [`RenderCustomMultiChildLayoutBox`].
33#[derive(Debug)]
34pub struct MultiChildLayoutParentData {
35    box_parent_data: BoxParentData,
36    container_parent_data: ContainerParentData<AnyRenderBox>,
37
38    /// An object representing the identity of this child.
39    pub id: Option<ChildLayoutId>,
40}
41
42impl MultiChildLayoutParentData {
43    /// Creates parent data for a child that has no id yet.
44    pub const fn new() -> MultiChildLayoutParentData {
45        MultiChildLayoutParentData {
46            box_parent_data: BoxParentData::new(),
47            container_parent_data: ContainerParentData::new(),
48            id: None,
49        }
50    }
51}
52
53impl Default for MultiChildLayoutParentData {
54    fn default() -> MultiChildLayoutParentData {
55        MultiChildLayoutParentData::new()
56    }
57}
58
59impl ParentData for MultiChildLayoutParentData {
60    fn detach(&mut self) {
61        ContainerParentDataMixin::detach(self);
62    }
63
64    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
65        if id == TypeId::of::<MultiChildLayoutParentData>() {
66            return Some(self);
67        }
68        self.box_parent_data.provide(id)
69    }
70
71    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
72        if id == TypeId::of::<MultiChildLayoutParentData>() {
73            return Some(self);
74        }
75        self.box_parent_data.provide_mut(id)
76    }
77}
78
79impl ContainerParentDataMixin for MultiChildLayoutParentData {
80    type ChildType = AnyRenderBox;
81
82    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
83        &self.container_parent_data
84    }
85
86    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
87        &mut self.container_parent_data
88    }
89}
90
91impl ContainerBoxParentData for MultiChildLayoutParentData {
92    fn box_parent_data(&self) -> &BoxParentData {
93        &self.box_parent_data
94    }
95
96    fn box_parent_data_mut(&mut self) -> &mut BoxParentData {
97        &mut self.box_parent_data
98    }
99}
100
101impl fmt::Display for MultiChildLayoutParentData {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "{}; id={:?}", self.box_parent_data, self.id)
104    }
105}
106
107/// The children a [`MultiChildLayoutDelegate`] lays out, keyed by their
108/// [`MultiChildLayoutParentData::id`].
109///
110/// Dart's `MultiChildLayoutDelegate._idToChild` and, in debug builds, its
111/// `_debugChildrenNeedingLayout`. [`RenderCustomMultiChildLayoutBox`] builds one from its child
112/// list and hands it to [`MultiChildLayoutDelegate::perform_layout`].
113pub struct MultiChildLayoutChildren {
114    id_to_child: HashMap<ChildLayoutId, AnyRenderBox>,
115    #[cfg(debug_assertions)]
116    children_needing_layout: std::collections::HashSet<AnyRenderBox>,
117}
118
119impl MultiChildLayoutChildren {
120    /// True if a non-`None` child was provided for the specified id.
121    ///
122    /// Call this from [`MultiChildLayoutDelegate::perform_layout`] to determine which children
123    /// are available, if the child list might vary.
124    ///
125    /// This cannot be called from [`MultiChildLayoutDelegate::get_size`] as the size is not
126    /// allowed to depend on the children.
127    pub fn has_child(&self, child_id: &ChildLayoutId) -> bool {
128        self.id_to_child.contains_key(child_id)
129    }
130
131    /// Ask the child to update its layout within the limits specified by the constraints
132    /// parameter. The child's size is returned.
133    ///
134    /// Call this from [`MultiChildLayoutDelegate::perform_layout`] to lay out each child. Every
135    /// child must be laid out using this function exactly once each time
136    /// [`MultiChildLayoutDelegate::perform_layout`] is called.
137    ///
138    /// # Panics
139    ///
140    /// If there is no child with `child_id`, or, in a debug build, if that child was already
141    /// laid out during this call.
142    pub fn layout_child(
143        &mut self,
144        app: &mut App,
145        child_id: &ChildLayoutId,
146        constraints: BoxConstraints,
147    ) -> Size {
148        let child = *self.id_to_child.get(child_id).unwrap_or_else(|| {
149            panic!(
150                "The custom multichild layout delegate tried to lay out a non-existent child. \
151                 There is no child with the id {child_id:?}."
152            )
153        });
154        #[cfg(debug_assertions)]
155        assert!(
156            self.children_needing_layout.remove(&child),
157            "The custom multichild layout delegate tried to lay out the child with id \
158             {child_id:?} more than once. Each child must be laid out exactly once."
159        );
160        debug_assert!(
161            constraints.debug_assert_is_valid(true),
162            "The custom multichild layout delegate provided invalid box constraints for the \
163             child with id {child_id:?}. The minimum width and height must be greater than or \
164             equal to zero. The maximum width must be greater than or equal to the minimum \
165             width. The maximum height must be greater than or equal to the minimum height."
166        );
167        child.layout(app, constraints, true);
168        child.size(app)
169    }
170
171    /// Specify the child's origin relative to this origin.
172    ///
173    /// Call this from [`MultiChildLayoutDelegate::perform_layout`] to position each child. If
174    /// you do not call this for a child, its position will remain unchanged. Children initially
175    /// have their position set to (0,0), i.e. the top left of the
176    /// [`RenderCustomMultiChildLayoutBox`].
177    ///
178    /// # Panics
179    ///
180    /// If there is no child with `child_id`.
181    pub fn position_child(&self, app: &mut App, child_id: &ChildLayoutId, offset: Offset) {
182        let child = *self.id_to_child.get(child_id).unwrap_or_else(|| {
183            panic!(
184                "The custom multichild layout delegate tried to position out a non-existent \
185                 child: there is no child with the id {child_id:?}."
186            )
187        });
188        child
189            .parent_data_of_mut::<MultiChildLayoutParentData>(app)
190            .set_offset(offset);
191    }
192}
193
194/// A delegate that controls the layout of multiple children.
195///
196/// Used with `CustomMultiChildLayout` (in the widgets library) and
197/// [`RenderCustomMultiChildLayoutBox`] (in the rendering library).
198///
199/// Delegates must be idempotent. Specifically, if two delegates are equal, then they must
200/// produce the same layout. To change the layout, replace the delegate with a different
201/// instance whose [`should_relayout`](Self::should_relayout) returns true when given the
202/// previous instance.
203///
204/// Override [`get_size`](Self::get_size) to control the overall size of the layout. The size of
205/// the layout cannot depend on layout properties of the children. This was a design decision to
206/// simplify the delegate implementations: this way, the delegate implementations do not have to
207/// also handle various intrinsic sizing functions if the parent's size depended on the children.
208///
209/// Override [`perform_layout`](Self::perform_layout) to size and position the children. An
210/// implementation of [`perform_layout`](Self::perform_layout) must call
211/// [`MultiChildLayoutChildren::layout_child`] exactly once for each child, but it may lay out
212/// children in an arbitrary order.
213///
214/// Override [`should_relayout`](Self::should_relayout) to determine when the layout of the
215/// children needs to be recomputed when the delegate changes.
216///
217/// The most efficient way to trigger a relayout is to answer a [`relayout`](Self::relayout)
218/// listenable. The custom layout listens to that value and relays out whenever it notifies its
219/// listeners, such as when an animation ticks. This allows the custom layout to avoid the build
220/// phase of the pipeline.
221///
222/// Each child must be wrapped in a `LayoutId` widget to assign the id that identifies it to the
223/// delegate. The id needs to be unique among the children that the `CustomMultiChildLayout`
224/// manages.
225pub trait MultiChildLayoutDelegate: Debug + 'static {
226    /// The [`Listenable`] the layout will update on, Dart's `relayout` constructor argument.
227    ///
228    /// Defaults to `None`.
229    fn relayout(&self) -> Option<&Rc<dyn Listenable>> {
230        None
231    }
232
233    /// Override this method to return the size of this object given the incoming constraints.
234    ///
235    /// The size cannot reflect the sizes of the children. If this layout has a fixed width or
236    /// height the returned size can reflect that; the size will be constrained to the given
237    /// constraints.
238    ///
239    /// By default, attempts to size the box to the biggest size possible given the constraints.
240    fn get_size(&self, constraints: BoxConstraints) -> Size {
241        constraints.biggest()
242    }
243
244    /// Override this method to lay out and position all children given this widget's size.
245    ///
246    /// This method must call [`MultiChildLayoutChildren::layout_child`] for each child. It
247    /// should also specify the final position of each child with
248    /// [`MultiChildLayoutChildren::position_child`].
249    fn perform_layout(&self, app: &mut App, children: &mut MultiChildLayoutChildren, size: Size);
250
251    /// Override this method to return true when the children need to be laid out.
252    ///
253    /// This should compare the fields of the current delegate and the given `old_delegate` and
254    /// return true if the fields are such that the layout would be different. `old_delegate` is
255    /// of the same concrete type, Dart's `covariant`: narrow it with
256    /// [`as_any`](Self::as_any).
257    fn should_relayout(&self, old_delegate: &dyn MultiChildLayoutDelegate) -> bool;
258
259    /// The concrete delegate, for Dart's `runtimeType` comparison and its `covariant`
260    /// narrowing of [`should_relayout`](Self::should_relayout)'s argument.
261    fn as_any(&self) -> &dyn Any;
262}
263
264/// Defers the layout of multiple children to a delegate.
265///
266/// The delegate can determine the layout constraints for each child and can decide where to
267/// position each child. The delegate can also determine the size of the parent, but the size of
268/// the parent cannot depend on the sizes of the children.
269pub struct RenderCustomMultiChildLayoutBox {
270    render_object: RenderObjectData,
271    render_box: RenderBoxData,
272    container: ContainerRenderObjectData<AnyRenderBox>,
273    delegate: Rc<dyn MultiChildLayoutDelegate>,
274}
275
276impl RenderCustomMultiChildLayoutBox {
277    /// Creates a render object that customizes the layout of multiple children.
278    ///
279    /// Add children with [`add`](ContainerRenderObjectMixin::add) or
280    /// [`add_all`](ContainerRenderObjectMixin::add_all); Dart's constructor takes them.
281    pub fn new(
282        app: &mut App,
283        delegate: Rc<dyn MultiChildLayoutDelegate>,
284    ) -> RenderHandle<RenderCustomMultiChildLayoutBox> {
285        RenderHandle::new_box(
286            app,
287            RenderCustomMultiChildLayoutBox {
288                render_object: RenderObjectData::new(),
289                render_box: RenderBoxData::new(),
290                container: ContainerRenderObjectData::new(),
291                delegate,
292            },
293        )
294    }
295
296    /// The delegate that controls the layout of the children.
297    pub fn delegate(self: RenderHandle<Self>, app: &App) -> Rc<dyn MultiChildLayoutDelegate> {
298        Rc::clone(&self.get(app).delegate)
299    }
300
301    /// Sets [`delegate`](Self::delegate).
302    pub fn set_delegate(
303        self: RenderHandle<Self>,
304        app: &mut App,
305        new_delegate: Rc<dyn MultiChildLayoutDelegate>,
306    ) {
307        let old_delegate = Rc::clone(&self.get(app).delegate);
308        if Rc::ptr_eq(&old_delegate, &new_delegate) {
309            return;
310        }
311        if new_delegate.as_any().type_id() != old_delegate.as_any().type_id()
312            || new_delegate.should_relayout(&*old_delegate)
313        {
314            self.mark_needs_layout(app);
315        }
316        self.get_mut(app).delegate = Rc::clone(&new_delegate);
317        if self.attached(app) {
318            if let Some(relayout) = old_delegate.relayout() {
319                relayout.remove_listener(app, &self.relayout_listener());
320            }
321            if let Some(relayout) = new_delegate.relayout() {
322                relayout.add_listener(app, self.relayout_listener());
323            }
324        }
325    }
326
327    /// The `markNeedsLayout` tear-off, equal to itself across registrations.
328    fn relayout_listener(self: RenderHandle<Self>) -> Listener {
329        Listener::handle_method(self.handle(), mark_needs_layout)
330    }
331
332    /// Dart's `_getSize`.
333    fn get_size(self: RenderHandle<Self>, app: &App, constraints: BoxConstraints) -> Size {
334        debug_assert!(constraints.debug_assert_is_valid(false));
335        constraints.constrain(self.get(app).delegate.get_size(constraints))
336    }
337
338    /// Dart's `MultiChildLayoutDelegate._callPerformLayout`.
339    ///
340    /// Dart saves and restores the delegate's `_idToChild` because a delegate can be used by
341    /// both a parent and a child; here the map belongs to the call, so there is nothing to
342    /// restore.
343    fn call_perform_layout(self: RenderHandle<Self>, app: &mut App, size: Size) {
344        let mut children = MultiChildLayoutChildren {
345            id_to_child: HashMap::new(),
346            #[cfg(debug_assertions)]
347            children_needing_layout: std::collections::HashSet::new(),
348        };
349        let mut child = self.first_child(app);
350        while let Some(current) = child {
351            let child_parent_data = current
352                .as_object()
353                .parent_data_of::<MultiChildLayoutParentData>(app);
354            let (id, next_sibling) = (
355                child_parent_data.id.clone(),
356                child_parent_data.next_sibling(),
357            );
358            let id = id.unwrap_or_else(|| {
359                panic!(
360                    "Every child of a RenderCustomMultiChildLayoutBox must have an ID in its \
361                     parent data. The following child has no ID: {current:?}"
362                )
363            });
364            children.id_to_child.insert(id, current);
365            #[cfg(debug_assertions)]
366            children.children_needing_layout.insert(current);
367            child = next_sibling;
368        }
369
370        let delegate = Rc::clone(&self.get(app).delegate);
371        delegate.perform_layout(app, &mut children, size);
372
373        #[cfg(debug_assertions)]
374        assert!(
375            children.children_needing_layout.is_empty(),
376            "Each child must be laid out exactly once. The custom multichild layout delegate \
377             forgot to lay out {:?}",
378            children.children_needing_layout
379        );
380    }
381}
382
383fn mark_needs_layout(this: Handle<RenderCustomMultiChildLayoutBox>, app: &mut App) {
384    RenderHandle::from_handle(this).mark_needs_layout(app);
385}
386
387impl ContainerRenderObjectMixin for RenderCustomMultiChildLayoutBox {
388    type ChildType = AnyRenderBox;
389    type ParentDataType = MultiChildLayoutParentData;
390
391    fn container_data(
392        self: RenderHandle<Self>,
393        app: &App,
394    ) -> &ContainerRenderObjectData<AnyRenderBox> {
395        &self.get(app).container
396    }
397
398    fn container_data_mut(
399        self: RenderHandle<Self>,
400        app: &mut App,
401    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
402        &mut self.get_mut(app).container
403    }
404}
405
406impl RenderBoxContainerDefaultsMixin for RenderCustomMultiChildLayoutBox {}
407
408impl RenderObject for RenderCustomMultiChildLayoutBox {
409    crate::render_object_accessors!();
410
411    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
412        let constraints = self.constraints(app);
413        let size = self.get_size(app, constraints);
414        self.set_size(app, size);
415        self.call_perform_layout(app, size);
416    }
417
418    fn visit_children(
419        self: RenderHandle<Self>,
420        app: &App,
421        visitor: &mut dyn FnMut(AnyRenderObject),
422    ) {
423        ContainerRenderObjectMixin::visit_children(self, app, visitor)
424    }
425
426    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
427        ContainerRenderObjectMixin::did_attach(self, app, owner);
428        if let Some(relayout) = self.delegate(app).relayout() {
429            relayout.add_listener(app, self.relayout_listener());
430        }
431    }
432
433    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
434        if let Some(relayout) = self.delegate(app).relayout() {
435            relayout.remove_listener(app, &self.relayout_listener());
436        }
437        ContainerRenderObjectMixin::did_detach(self, app)
438    }
439
440    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
441        ContainerRenderObjectMixin::redepth_children(self, app)
442    }
443
444    fn paint(
445        self: RenderHandle<Self>,
446        app: &mut App,
447        context: &mut PaintingContext,
448        offset: Offset,
449    ) {
450        self.default_paint(app, context, offset);
451    }
452}
453
454impl RenderBox for RenderCustomMultiChildLayoutBox {
455    crate::render_box_accessors!();
456
457    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
458        if !child.parent_data_is::<MultiChildLayoutParentData>(app) {
459            child.set_parent_data(app, MultiChildLayoutParentData::new());
460        }
461    }
462
463    // Dart's TODO stands: using the delegate's `getSize` for the intrinsic dimensions is a bit
464    // dubious.
465
466    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
467        let width = self
468            .get_size(app, BoxConstraints::tight_for_finite(f64::INFINITY, height))
469            .width();
470        if width.is_finite() { width } else { 0.0 }
471    }
472
473    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
474        let width = self
475            .get_size(app, BoxConstraints::tight_for_finite(f64::INFINITY, height))
476            .width();
477        if width.is_finite() { width } else { 0.0 }
478    }
479
480    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
481        let height = self
482            .get_size(app, BoxConstraints::tight_for_finite(width, f64::INFINITY))
483            .height();
484        if height.is_finite() { height } else { 0.0 }
485    }
486
487    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
488        let height = self
489            .get_size(app, BoxConstraints::tight_for_finite(width, f64::INFINITY))
490            .height();
491        if height.is_finite() { height } else { 0.0 }
492    }
493
494    fn compute_dry_layout(
495        self: RenderHandle<Self>,
496        app: &mut App,
497        constraints: BoxConstraints,
498    ) -> Size {
499        self.get_size(app, constraints)
500    }
501
502    fn hit_test_children(
503        self: RenderHandle<Self>,
504        app: &mut App,
505        result: &mut BoxHitTestResult<'_>,
506        position: Offset,
507    ) -> bool {
508        self.default_hit_test_children(app, result, position)
509    }
510}