Skip to main content

inset_rendering/
painting_context.rs

1//! Flutter counterpart: `rendering/object.dart` (`PaintingContext`).
2
3use std::sync::Arc;
4
5use inset_embedder::{Canvas, Clip, Matrix4, Offset, Path, PathBuilder, RRect, Rect};
6use inset_foundation::{App, Handle};
7use inset_painting::{ClipContext, inverse_transform_rect};
8
9use crate::layer::{
10    AnyContainerLayer, AnyLayer, ClipPathLayer, ClipRRectLayer, ClipRectLayer, CompositionCallback,
11    ContainerLayer, ErasedLayer, LayerHandle, OffsetLayerMixin, OpacityLayer, PictureLayer,
12    TransformLayer,
13};
14use crate::object::AnyRenderObject;
15
16/// A place to paint.
17///
18/// Rather than holding a canvas directly, [`crate::RenderObject`]s paint using a painting
19/// context. The painting context has a [`Canvas`](ClipContext::canvas), which receives the
20/// individual draw operations, and also has functions for painting child
21/// render objects.
22///
23/// When painting a child render object, the canvas held by the painting context
24/// can change because the draw operations issued before and after painting the
25/// child might be recorded in separate compositing layers. For this reason, do
26/// not hold a reference to the canvas across operations that might paint
27/// child render objects.
28///
29/// New [`PaintingContext`] objects are created automatically when using
30/// [`PaintingContext::repaint_composited_child`] and [`push_layer`](Self::push_layer).
31pub struct PaintingContext {
32    container_layer: AnyContainerLayer,
33    /// An estimate of the bounds within which the painting context's [`canvas`](ClipContext::canvas)
34    /// will record painting commands. This can be useful for debugging.
35    ///
36    /// The canvas will allow painting outside these bounds.
37    ///
38    /// The [`estimated_bounds`](Self::estimated_bounds) rectangle is in the canvas coordinate
39    /// system.
40    pub estimated_bounds: Rect,
41    canvas: Option<Canvas>,
42    is_complex_hint: bool,
43    will_change_hint: bool,
44}
45
46impl PaintingContext {
47    /// Creates a painting context.
48    ///
49    /// Typically only called by [`repaint_composited_child`](Self::repaint_composited_child)
50    /// and [`push_layer`](Self::push_layer).
51    pub fn new(container_layer: AnyContainerLayer, estimated_bounds: Rect) -> PaintingContext {
52        PaintingContext {
53            container_layer,
54            estimated_bounds,
55            canvas: None,
56            is_complex_hint: false,
57            will_change_hint: false,
58        }
59    }
60
61    /// Repaint the given render object.
62    ///
63    /// The render object must be attached to a [`crate::PipelineOwner`], must have a
64    /// composited layer, and must be in need of painting. The render object's
65    /// layer, if any, is re-used, along with any layers in the subtree that don't
66    /// need to be repainted.
67    pub fn repaint_composited_child(app: &mut App, child: AnyRenderObject) {
68        debug_assert!(child.needs_paint(app));
69        Self::repaint_composited_child_inner(app, child, false, None);
70    }
71
72    fn repaint_composited_child_inner(
73        app: &mut App,
74        child: AnyRenderObject,
75        debug_also_painted_parent: bool,
76        child_context: Option<PaintingContext>,
77    ) {
78        debug_assert!(child.is_repaint_boundary(app));
79        let child_layer = if let Some(child_layer) =
80            child.layer(app).and_then(|layer| layer.as_offset_layer())
81        {
82            debug_assert!(debug_also_painted_parent || child_layer.as_layer().attached(app));
83            let debug_old_offset = if cfg!(debug_assertions) {
84                Some(child_layer.offset(app))
85            } else {
86                None
87            };
88            child_layer.as_container_layer().remove_all_children(app);
89            let updated_layer = child.update_composited_layer(app, Some(child_layer));
90            debug_assert_eq!(
91                updated_layer, child_layer,
92                "{child:?} created a new layer instance {updated_layer:?} instead of reusing the \
93                 existing layer {child_layer:?}. See the documentation of RenderObject.updateCompositedLayer \
94                 for more information on how to correctly implement this method."
95            );
96            debug_assert_eq!(debug_old_offset, Some(updated_layer.offset(app)));
97            child_layer
98        } else {
99            debug_assert!(debug_also_painted_parent);
100            debug_assert!(child.layer(app).is_none());
101
102            // Not using the `layer` setter because the setter asserts that we not
103            // replace the layer for repaint boundaries. That assertion does not
104            // apply here because this is exactly the place designed to create a
105            // layer for repaint boundaries.
106            let layer = child.update_composited_layer(app, None);
107            LayerHandle::set_layer(
108                app,
109                |app| &mut child.data_mut(app).layer,
110                Some(layer.as_container_layer()),
111            );
112            layer
113        };
114        child.set_needs_composited_layer_update(app, false);
115        debug_assert_eq!(Some(child_layer.as_container_layer()), child.layer(app));
116        debug_assert!(
117            child
118                .layer(app)
119                .is_some_and(|layer| layer.as_offset_layer().is_some())
120        );
121
122        let mut child_context = child_context.unwrap_or_else(|| {
123            PaintingContext::new(child_layer.as_container_layer(), child.paint_bounds(app))
124        });
125        child.paint_with_context(app, &mut child_context, Offset::ZERO);
126
127        // Double-check that the paint method did not replace the layer (the first
128        // check is done in the [layer] setter itself).
129        debug_assert_eq!(Some(child_layer.as_container_layer()), child.layer(app));
130        child_context.stop_recording_if_needed(app);
131    }
132
133    /// Update the composited layer of `child` without repainting its children.
134    ///
135    /// The render object must be attached to a [`crate::PipelineOwner`], must have a
136    /// composited layer, and must be in need of a composited layer update but
137    /// not in need of painting. The render object's layer is re-used, and none
138    /// of its children are repaint or their layers updated.
139    pub fn update_layer_properties(app: &mut App, child: AnyRenderObject) {
140        debug_assert!(child.is_repaint_boundary(app) && child.was_repaint_boundary(app));
141        debug_assert!(!child.needs_paint(app));
142        debug_assert!(child.layer(app).is_some());
143
144        let child_layer = child
145            .layer(app)
146            .expect("checked")
147            .as_offset_layer()
148            .expect("a repaint boundary's layer is an OffsetLayer");
149        let debug_old_offset = if cfg!(debug_assertions) {
150            Some(child_layer.offset(app))
151        } else {
152            None
153        };
154        let updated_layer = child.update_composited_layer(app, Some(child_layer));
155        debug_assert_eq!(
156            updated_layer, child_layer,
157            "{child:?} created a new layer instance {updated_layer:?} instead of reusing the \
158             existing layer {child_layer:?}. See the documentation of RenderObject.updateCompositedLayer \
159             for more information on how to correctly implement this method."
160        );
161        debug_assert_eq!(debug_old_offset, Some(updated_layer.offset(app)));
162        child.set_needs_composited_layer_update(app, false);
163    }
164
165    /// Paint a child [`crate::RenderObject`].
166    ///
167    /// If the child has its own composited layer, the child will be composited
168    /// into the layer subtree associated with this painting context. Otherwise,
169    /// the child will be painted into the current PictureLayer for this context.
170    pub fn paint_child(&mut self, app: &mut App, child: AnyRenderObject, offset: Offset) {
171        if child.is_repaint_boundary(app) {
172            self.stop_recording_if_needed(app);
173            self.composite_child(app, child, offset);
174        } else if child.was_repaint_boundary(app) {
175            debug_assert!(
176                child
177                    .layer(app)
178                    .is_some_and(|layer| layer.as_offset_layer().is_some())
179            );
180            LayerHandle::set_layer(app, |app| &mut child.data_mut(app).layer, None);
181            child.paint_with_context(app, self, offset);
182        } else {
183            child.paint_with_context(app, self, offset);
184        }
185    }
186
187    fn composite_child(&mut self, app: &mut App, child: AnyRenderObject, offset: Offset) {
188        debug_assert!(!self.is_recording());
189        debug_assert!(child.is_repaint_boundary(app));
190
191        if child.needs_paint(app) || !child.was_repaint_boundary(app) {
192            Self::repaint_composited_child_inner(app, child, true, None);
193        } else if child.needs_composited_layer_update(app) {
194            Self::update_layer_properties(app, child);
195        }
196        debug_assert!(
197            child
198                .layer(app)
199                .is_some_and(|layer| layer.as_offset_layer().is_some())
200        );
201        let child_offset_layer = child
202            .layer(app)
203            .expect("checked")
204            .as_offset_layer()
205            .expect("a repaint boundary's layer is an OffsetLayer");
206        child_offset_layer.set_offset(app, offset);
207        self.append_layer(app, child_offset_layer.as_layer());
208    }
209
210    /// Adds a layer to the recording requiring that the recording is already
211    /// stopped.
212    ///
213    /// Do not call this function directly: call [`add_layer`](Self::add_layer) or
214    /// [`push_layer`](Self::push_layer) instead. This function is called internally when all
215    /// layers not generated from the canvas are added.
216    fn append_layer(&mut self, app: &mut App, layer: AnyLayer) {
217        debug_assert!(!self.is_recording());
218        layer.remove(app);
219        self.container_layer.append(app, layer);
220    }
221
222    fn is_recording(&self) -> bool {
223        self.canvas.is_some()
224    }
225
226    fn start_recording(&mut self) {
227        debug_assert!(!self.is_recording());
228        self.canvas = Some(Canvas::new());
229        self.is_complex_hint = false;
230        self.will_change_hint = false;
231    }
232
233    /// Adds a [`CompositionCallback`] for the current [`AnyContainerLayer`] used by this
234    /// context.
235    pub fn add_composition_callback(
236        &self,
237        app: &mut App,
238        callback: CompositionCallback,
239    ) -> Box<dyn FnOnce(&mut App)> {
240        self.container_layer
241            .as_layer()
242            .add_composition_callback(app, callback)
243    }
244
245    /// Stop recording to a canvas if recording has started.
246    ///
247    /// Do not call this function directly: functions in this class will call
248    /// this method as needed. This function is called internally to ensure that
249    /// recording is stopped before adding layers or finalizing the results of a
250    /// paint.
251    ///
252    /// Creates and appends the [`PictureLayer`] here rather than in
253    /// [`start_recording`](Self::start_recording): [`ClipContext::canvas`] has no `App`. See
254    /// `PORTING.md`.
255    pub fn stop_recording_if_needed(&mut self, app: &mut App) {
256        if !self.is_recording() {
257            return;
258        }
259        let canvas = self.canvas.take().expect("recording");
260        let picture = PictureLayer::new(app, self.estimated_bounds);
261        picture.set_picture(app, Some(Arc::new(canvas.build())));
262        picture.set_is_complex_hint(app, self.is_complex_hint);
263        picture.set_will_change_hint(app, self.will_change_hint);
264        self.is_complex_hint = false;
265        self.will_change_hint = false;
266        self.container_layer.append(app, picture.as_layer());
267    }
268
269    /// Hints that the painting in the current layer is complex and would benefit
270    /// from caching.
271    pub fn set_is_complex_hint(&mut self) {
272        if self.canvas.is_none() {
273            self.start_recording();
274        }
275        self.is_complex_hint = true;
276    }
277
278    /// Hints that the painting in the current layer is likely to change next frame.
279    pub fn set_will_change_hint(&mut self) {
280        if self.canvas.is_none() {
281            self.start_recording();
282        }
283        self.will_change_hint = true;
284    }
285
286    /// Adds a composited leaf layer to the recording.
287    ///
288    /// After calling this function, the [`canvas`](ClipContext::canvas) property will change to
289    /// refer to a new [`Canvas`] that draws on top of the given layer.
290    pub fn add_layer(&mut self, app: &mut App, layer: AnyLayer) {
291        self.stop_recording_if_needed(app);
292        self.append_layer(app, layer);
293    }
294
295    /// Appends the given layer to the recording, and calls the `painter` callback
296    /// with that layer, providing the `child_paint_bounds` as the estimated paint
297    /// bounds of the child. The `child_paint_bounds` can be used for debugging but
298    /// have no effect on painting.
299    ///
300    /// The given layer must be an unattached orphan. (Providing a newly created
301    /// object, rather than reusing an existing layer, satisfies that
302    /// requirement.)
303    ///
304    /// The `offset` is the offset to pass to the `painter`. In particular, it is
305    /// not an offset applied to the layer itself. Layers conceptually by default
306    /// have no position or size, though they can transform their contents. For
307    /// example, an [`crate::OffsetLayer`] applies an offset to its children.
308    ///
309    /// If the `child_paint_bounds` are not specified then the current layer's paint
310    /// bounds are used. This is appropriate if the child layer does not apply any
311    /// transformation or clipping to its contents. The `child_paint_bounds`, if
312    /// specified, must be in the coordinate system of the new layer (i.e. as seen
313    /// by its children after it applies whatever transform to its contents), and
314    /// should not go outside the current layer's paint bounds.
315    pub fn push_layer(
316        &mut self,
317        app: &mut App,
318        child_layer: AnyContainerLayer,
319        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
320        offset: Offset,
321        child_paint_bounds: Option<Rect>,
322    ) {
323        // If a layer is being reused, it may already contain children. We remove
324        // them so that `painter` can add children that are relevant for this frame.
325        if child_layer.has_children(app) {
326            child_layer.remove_all_children(app);
327        }
328        self.stop_recording_if_needed(app);
329        self.append_layer(app, child_layer.as_layer());
330        let mut child_context = self.create_child_context(
331            child_layer,
332            child_paint_bounds.unwrap_or(self.estimated_bounds),
333        );
334
335        painter(app, &mut child_context, offset);
336        child_context.stop_recording_if_needed(app);
337    }
338
339    /// Creates a painting context configured to paint into `child_layer`.
340    ///
341    /// The `bounds` are estimated paint bounds for debugging purposes.
342    fn create_child_context(
343        &self,
344        child_layer: AnyContainerLayer,
345        bounds: Rect,
346    ) -> PaintingContext {
347        PaintingContext::new(child_layer, bounds)
348    }
349
350    /// Clip further painting using a rectangle.
351    ///
352    /// The `needs_compositing` argument specifies whether the child needs
353    /// compositing. Typically this matches the value of
354    /// [`AnyRenderObject::needs_compositing`] for the caller. If false, this method
355    /// returns `None`, indicating that a layer is no longer necessary. If a render
356    /// object calling this method stores the `old_layer` in its
357    /// [`AnyRenderObject::layer`] field, it should set that field to `None`.
358    ///
359    /// When `needs_compositing` is false, this method will use a more efficient
360    /// way to apply the layer effect than actually creating a layer.
361    ///
362    /// The `offset` argument is the offset from the origin of the canvas'
363    /// coordinate system to the origin of the caller's coordinate system.
364    ///
365    /// The `clip_rect` is the rectangle (in the caller's coordinate system) to use
366    /// to clip the painting done by `painter`. It should not include the
367    /// `offset`.
368    #[allow(clippy::too_many_arguments)]
369    pub fn push_clip_rect(
370        &mut self,
371        app: &mut App,
372        needs_compositing: bool,
373        offset: Offset,
374        clip_rect: Rect,
375        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
376        clip_behavior: Clip,
377        old_layer: Option<Handle<ClipRectLayer>>,
378    ) -> Option<Handle<ClipRectLayer>> {
379        if clip_behavior == Clip::None {
380            painter(app, self, offset);
381            return None;
382        }
383        let offset_clip_rect = clip_rect.shift(offset);
384        if needs_compositing {
385            let layer = old_layer.unwrap_or_else(|| ClipRectLayer::new(app));
386            layer.set_clip_rect(app, Some(offset_clip_rect));
387            layer.set_clip_behavior(app, clip_behavior);
388            self.push_layer(
389                app,
390                layer.as_container_layer(),
391                painter,
392                offset,
393                Some(offset_clip_rect),
394            );
395            Some(layer)
396        } else {
397            self.clip_rect_and_paint(
398                offset_clip_rect,
399                clip_behavior,
400                offset_clip_rect,
401                |context| painter(app, context, offset),
402            );
403            None
404        }
405    }
406
407    /// Clip further painting using a rounded rectangle.
408    #[allow(clippy::too_many_arguments)]
409    pub fn push_clip_rrect(
410        &mut self,
411        app: &mut App,
412        needs_compositing: bool,
413        offset: Offset,
414        bounds: Rect,
415        clip_rrect: RRect,
416        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
417        clip_behavior: Clip,
418        old_layer: Option<Handle<ClipRRectLayer>>,
419    ) -> Option<Handle<ClipRRectLayer>> {
420        if clip_behavior == Clip::None {
421            painter(app, self, offset);
422            return None;
423        }
424        let offset_bounds = bounds.shift(offset);
425        let offset_clip_rrect = clip_rrect.shift(offset);
426        if needs_compositing {
427            let layer = old_layer.unwrap_or_else(|| ClipRRectLayer::new(app));
428            layer.set_clip_rrect(app, Some(offset_clip_rrect));
429            layer.set_clip_behavior(app, clip_behavior);
430            self.push_layer(
431                app,
432                layer.as_container_layer(),
433                painter,
434                offset,
435                Some(offset_bounds),
436            );
437            Some(layer)
438        } else {
439            self.clip_rrect_and_paint(offset_clip_rrect, clip_behavior, offset_bounds, |context| {
440                painter(app, context, offset)
441            });
442            None
443        }
444    }
445
446    /// Clip further painting using a path.
447    #[allow(clippy::too_many_arguments)]
448    pub fn push_clip_path(
449        &mut self,
450        app: &mut App,
451        needs_compositing: bool,
452        offset: Offset,
453        bounds: Rect,
454        clip_path: Arc<Path>,
455        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
456        clip_behavior: Clip,
457        old_layer: Option<Handle<ClipPathLayer>>,
458    ) -> Option<Handle<ClipPathLayer>> {
459        if clip_behavior == Clip::None {
460            painter(app, self, offset);
461            return None;
462        }
463        let offset_bounds = bounds.shift(offset);
464        let offset_clip_path = shift_path(&clip_path, offset);
465        if needs_compositing {
466            let layer = old_layer.unwrap_or_else(|| ClipPathLayer::new(app));
467            layer.set_clip_path(app, Some(offset_clip_path));
468            layer.set_clip_behavior(app, clip_behavior);
469            self.push_layer(
470                app,
471                layer.as_container_layer(),
472                painter,
473                offset,
474                Some(offset_bounds),
475            );
476            Some(layer)
477        } else {
478            self.clip_path_and_paint(&offset_clip_path, clip_behavior, offset_bounds, |context| {
479                painter(app, context, offset)
480            });
481            None
482        }
483    }
484
485    /// Transform further painting using a matrix.
486    ///
487    /// The `offset` argument is the offset to pass to `painter` and the offset to
488    /// the origin used by `transform`.
489    ///
490    /// The `transform` argument is the [`Matrix4`] with which to transform the
491    /// coordinate system while calling `painter`. It should not include `offset`.
492    /// It is applied effectively after applying `offset`.
493    pub fn push_transform(
494        &mut self,
495        app: &mut App,
496        needs_compositing: bool,
497        offset: Offset,
498        transform: Matrix4,
499        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
500        old_layer: Option<Handle<TransformLayer>>,
501    ) -> Option<Handle<TransformLayer>> {
502        let (dx, dy) = (offset.dx() as f32, offset.dy() as f32);
503        let effective_transform = Matrix4::translation(dx, dy)
504            .then(&transform)
505            .then(&Matrix4::translation(-dx, -dy));
506        if needs_compositing {
507            let layer = old_layer.unwrap_or_else(|| TransformLayer::new(app));
508            layer.set_transform(app, effective_transform);
509            let child_paint_bounds =
510                inverse_transform_rect(effective_transform, self.estimated_bounds);
511            self.push_layer(
512                app,
513                layer.as_container_layer(),
514                painter,
515                offset,
516                Some(child_paint_bounds),
517            );
518            Some(layer)
519        } else {
520            self.canvas().save();
521            self.canvas().concat(&effective_transform);
522            painter(app, self, offset);
523            self.canvas().restore();
524            None
525        }
526    }
527
528    /// Blend further painting with an alpha value.
529    ///
530    /// The `offset` argument indicates an offset to apply to all the children
531    /// (the rendering created by `painter`).
532    ///
533    /// The `alpha` argument is the alpha value to use when blending the painting
534    /// done by `painter`. An alpha value of 0 means the painting is fully
535    /// transparent and an alpha value of 255 means the painting is fully opaque.
536    pub fn push_opacity(
537        &mut self,
538        app: &mut App,
539        offset: Offset,
540        alpha: i32,
541        painter: impl FnOnce(&mut App, &mut PaintingContext, Offset),
542        old_layer: Option<Handle<OpacityLayer>>,
543    ) -> Handle<OpacityLayer> {
544        let layer = old_layer.unwrap_or_else(|| OpacityLayer::new(app));
545        layer.set_alpha(app, alpha);
546        layer.set_offset(app, offset);
547        self.push_layer(app, layer.as_container_layer(), painter, Offset::ZERO, None);
548        layer
549    }
550}
551
552fn shift_path(path: &Arc<Path>, offset: Offset) -> Arc<Path> {
553    if offset == Offset::ZERO {
554        return Arc::clone(path);
555    }
556    let mut builder = PathBuilder::new();
557    builder.append(
558        path,
559        &Matrix4::translation(offset.dx() as f32, offset.dy() as f32),
560    );
561    builder.build()
562}
563
564impl ClipContext for PaintingContext {
565    /// The canvas on which to paint.
566    ///
567    /// The current canvas can change whenever you paint a child using this
568    /// context, which means it's fragile to hold a reference to the canvas
569    /// returned by this getter.
570    fn canvas(&mut self) -> &mut Canvas {
571        if self.canvas.is_none() {
572            self.start_recording();
573        }
574        self.canvas.as_mut().expect("recording")
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use inset_foundation::AppCell;
581    use std::cell::Cell;
582    use std::rc::Rc;
583
584    use inset_embedder::{Color, Effect, EngineLayer, Layer, Paint, SceneBuilder, Size};
585
586    use super::*;
587    use crate::box_::{AnyRenderBox, BoxConstraints, RenderBox, RenderBoxData};
588    use crate::layer::{AnyOffsetLayer, ContainerLayer, ErasedLayer, OffsetLayer, OpacityLayer};
589    use crate::object::{RenderHandle, RenderObject, RenderObjectData};
590    use inset_foundation::Handle;
591
592    use crate::pipeline_owner::PipelineOwner;
593
594    /// Paints one rect and counts its paints. A repaint boundary when told to be, compositing
595    /// through an `OpacityLayer` with `alpha`.
596    struct Leaf {
597        render_object: RenderObjectData,
598        render_box: RenderBoxData,
599        repaint_boundary: bool,
600        alpha: i32,
601        paints: Rc<Cell<u32>>,
602    }
603
604    impl RenderObject for Leaf {
605        crate::render_object_accessors!();
606
607        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
608            let size = self.constraints(app).smallest();
609            self.set_size(app, size);
610        }
611
612        fn is_repaint_boundary(self: RenderHandle<Self>, app: &App) -> bool {
613            self.get(app).repaint_boundary
614        }
615
616        fn update_composited_layer(
617            self: RenderHandle<Self>,
618            app: &mut App,
619            old_layer: Option<AnyOffsetLayer>,
620        ) -> AnyOffsetLayer {
621            debug_assert!(self.is_repaint_boundary(app));
622            let layer = match old_layer {
623                Some(old) => app
624                    .handle::<OpacityLayer>(old.id())
625                    .expect("Leaf reuses an OpacityLayer"),
626                None => OpacityLayer::new(app),
627            };
628            layer.set_alpha(app, self.get(app).alpha);
629            layer.as_offset_layer()
630        }
631
632        fn paint(
633            self: RenderHandle<Self>,
634            app: &mut App,
635            context: &mut PaintingContext,
636            offset: Offset,
637        ) {
638            let paints = Rc::clone(&self.get(app).paints);
639            paints.set(paints.get() + 1);
640            let paint = Paint {
641                color: Color::from_argb(255, 0, 0, 0).into(),
642                ..Paint::default()
643            };
644            context.canvas().draw_rect(offset & self.size(app), &paint);
645        }
646    }
647
648    impl RenderBox for Leaf {
649        crate::render_box_accessors!();
650
651        /// Flutter's `TestRenderObject(allowPaintBounds: true)`: paint bounds before layout.
652        fn paint_bounds(self: RenderHandle<Self>, app: &App) -> Rect {
653            self.render_box_data(app)
654                .size
655                .map_or(Rect::ZERO, |size| Offset::ZERO & size)
656        }
657    }
658
659    /// A repaint boundary with one child, painted at the origin unless `paints_child` is off.
660    struct Parent {
661        render_object: RenderObjectData,
662        render_box: RenderBoxData,
663        child: Option<AnyRenderBox>,
664        paints_child: bool,
665        paints: Rc<Cell<u32>>,
666    }
667
668    impl RenderObject for Parent {
669        crate::render_object_accessors!();
670
671        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
672            if let Some(child) = self.get(app).child {
673                child.layout(app, BoxConstraints::tight(Size::new(50.0, 50.0)), false);
674            }
675            self.set_size(app, Size::new(100.0, 100.0));
676        }
677
678        fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
679            true
680        }
681
682        fn visit_children(
683            self: RenderHandle<Self>,
684            app: &App,
685            visitor: &mut dyn FnMut(AnyRenderObject),
686        ) {
687            if let Some(child) = self.get(app).child {
688                visitor(child.as_object());
689            }
690        }
691
692        fn paint(
693            self: RenderHandle<Self>,
694            app: &mut App,
695            context: &mut PaintingContext,
696            offset: Offset,
697        ) {
698            let paints = Rc::clone(&self.get(app).paints);
699            paints.set(paints.get() + 1);
700            if let Some(child) = self.get(app).child
701                && self.get(app).paints_child
702            {
703                context.paint_child(app, child.as_object(), offset);
704            }
705        }
706    }
707
708    impl RenderBox for Parent {
709        crate::render_box_accessors!();
710    }
711
712    struct Tree {
713        owner: Handle<PipelineOwner>,
714        parent: RenderHandle<Parent>,
715        leaf: RenderHandle<Leaf>,
716        parent_paints: Rc<Cell<u32>>,
717        leaf_paints: Rc<Cell<u32>>,
718    }
719
720    fn tree(app: &mut App, leaf_is_boundary: bool) -> Tree {
721        let owner = PipelineOwner::new(app, None, None);
722        let leaf_paints = Rc::new(Cell::new(0));
723        let parent_paints = Rc::new(Cell::new(0));
724        let leaf = RenderHandle::new_box(
725            app,
726            Leaf {
727                render_object: RenderObjectData::new(),
728                render_box: RenderBoxData::new(),
729                repaint_boundary: leaf_is_boundary,
730                alpha: 255,
731                paints: Rc::clone(&leaf_paints),
732            },
733        );
734        let parent = RenderHandle::new_box(
735            app,
736            Parent {
737                render_object: RenderObjectData::new(),
738                render_box: RenderBoxData::new(),
739                child: Some(leaf.as_box()),
740                paints_child: true,
741                paints: Rc::clone(&parent_paints),
742            },
743        );
744        parent.adopt_child(app, leaf.as_object());
745        owner.set_root_node(app, Some(parent.as_object()));
746        parent.schedule_initial_layout(app);
747        schedule_root_paint(app, parent.as_object());
748        Tree {
749            owner,
750            parent,
751            leaf,
752            parent_paints,
753            leaf_paints,
754        }
755    }
756
757    fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
758        let root = OffsetLayer::new(app, Offset::ZERO);
759        root.as_layer().attach(app, node.id());
760        node.schedule_initial_paint(app, root.as_container_layer());
761    }
762
763    fn frame(app: &mut App, owner: Handle<PipelineOwner>) {
764        owner.flush_layout(app);
765        owner.flush_compositing_bits(app);
766        owner.flush_paint(app);
767    }
768
769    fn paints(tree: &Tree) -> (u32, u32) {
770        (tree.parent_paints.get(), tree.leaf_paints.get())
771    }
772
773    fn leaf_layer_attached(app: &App, tree: &Tree) -> bool {
774        tree.leaf
775            .as_object()
776            .debug_layer(app)
777            .expect("leaf has a layer")
778            .as_layer()
779            .attached(app)
780    }
781
782    /// `object_test.dart`: `nodesNeedingPaint updated with paint changes`.
783    #[test]
784    fn nodes_needing_paint_updated_with_paint_changes() {
785        let cell = AppCell::new();
786        let mut app = cell.borrow_mut();
787        let owner = PipelineOwner::new(&mut app, None, None);
788        let node = RenderHandle::new_box(
789            &mut app,
790            Leaf {
791                render_object: RenderObjectData::new(),
792                render_box: RenderBoxData::new(),
793                repaint_boundary: true,
794                alpha: 255,
795                paints: Rc::new(Cell::new(0)),
796            },
797        );
798        node.as_object().attach(&mut app, owner);
799        assert!(owner.nodes_needing_paint(&app).is_empty());
800
801        node.mark_needs_paint(&mut app);
802        let root = OpacityLayer::new(&mut app);
803        root.as_layer().attach(&mut app, node.as_object().id());
804        node.as_object()
805            .schedule_initial_paint(&mut app, root.as_container_layer());
806        assert!(owner.nodes_needing_paint(&app).contains(&node.as_object()));
807
808        owner.flush_compositing_bits(&mut app);
809        owner.flush_paint(&mut app);
810        assert!(owner.nodes_needing_paint(&app).is_empty());
811    }
812
813    #[test]
814    fn child_boundary_repaints_without_its_parent() {
815        let cell = AppCell::new();
816        let mut app = cell.borrow_mut();
817        let tree = tree(&mut app, true);
818        frame(&mut app, tree.owner);
819        assert_eq!(paints(&tree), (1, 1));
820        assert!(leaf_layer_attached(&app, &tree));
821        let parent_layer = tree.parent.as_object().debug_layer(&app).expect("parent");
822        let leaf_layer = tree.leaf.as_object().debug_layer(&app).expect("leaf");
823        assert_eq!(leaf_layer.as_layer().parent(&app), Some(parent_layer));
824
825        tree.leaf.mark_needs_paint(&mut app);
826        assert_eq!(
827            tree.owner.nodes_needing_paint(&app),
828            vec![tree.leaf.as_object()]
829        );
830        frame(&mut app, tree.owner);
831        assert_eq!(paints(&tree), (1, 2));
832        let parent_layer = tree.parent.as_object().debug_layer(&app).expect("parent");
833        let leaf_layer = tree.leaf.as_object().debug_layer(&app).expect("leaf");
834        assert_eq!(leaf_layer.as_layer().parent(&app), Some(parent_layer));
835    }
836
837    #[test]
838    fn composited_layer_update_does_not_repaint() {
839        let cell = AppCell::new();
840        let mut app = cell.borrow_mut();
841        let tree = tree(&mut app, true);
842        frame(&mut app, tree.owner);
843
844        tree.leaf.get_mut(&mut app).alpha = 128;
845        tree.leaf.mark_needs_composited_layer_update(&mut app);
846        assert!(
847            tree.leaf
848                .as_object()
849                .debug_needs_composited_layer_update(&app)
850        );
851        frame(&mut app, tree.owner);
852        assert_eq!(paints(&tree), (1, 1));
853        let layer = tree.leaf.as_object().debug_layer(&app).expect("layer");
854        let opacity = app
855            .handle::<OpacityLayer>(layer.id())
856            .expect("OpacityLayer");
857        assert_eq!(opacity.alpha(&app), Some(128));
858    }
859
860    /// `layers_test.dart`: `non-painted layers are detached`.
861    #[test]
862    fn unpainted_boundary_is_detached_and_skipped() {
863        let cell = AppCell::new();
864        let mut app = cell.borrow_mut();
865        let tree = tree(&mut app, true);
866        frame(&mut app, tree.owner);
867        assert!(leaf_layer_attached(&app, &tree));
868
869        tree.parent.get_mut(&mut app).paints_child = false;
870        tree.parent.mark_needs_paint(&mut app);
871        frame(&mut app, tree.owner);
872        assert_eq!(paints(&tree), (2, 1));
873        assert!(!leaf_layer_attached(&app, &tree));
874
875        tree.leaf.mark_needs_paint(&mut app);
876        frame(&mut app, tree.owner);
877        assert_eq!(
878            paints(&tree),
879            (2, 1),
880            "a detached boundary is not repainted"
881        );
882        assert!(tree.leaf.as_object().debug_needs_paint(&app));
883
884        tree.parent.get_mut(&mut app).paints_child = true;
885        tree.parent.mark_needs_paint(&mut app);
886        frame(&mut app, tree.owner);
887        assert_eq!(paints(&tree), (3, 2));
888        assert!(leaf_layer_attached(&app, &tree));
889    }
890
891    #[test]
892    fn non_boundary_child_paints_into_the_parent_picture() {
893        let cell = AppCell::new();
894        let mut app = cell.borrow_mut();
895        let tree = tree(&mut app, false);
896        frame(&mut app, tree.owner);
897        assert_eq!(paints(&tree), (1, 1));
898        assert!(tree.leaf.as_object().debug_layer(&app).is_none());
899
900        tree.leaf.mark_needs_paint(&mut app);
901        assert_eq!(
902            tree.owner.nodes_needing_paint(&app),
903            vec![tree.parent.as_object()]
904        );
905        frame(&mut app, tree.owner);
906        assert_eq!(paints(&tree), (2, 2));
907    }
908
909    #[test]
910    fn composing_the_root_draws_the_child_boundary_through_its_layer() {
911        let cell = AppCell::new();
912        let mut app = cell.borrow_mut();
913        let tree = tree(&mut app, true);
914        tree.leaf.get_mut(&mut app).alpha = 128;
915        frame(&mut app, tree.owner);
916
917        let layer = tree.parent.as_object().layer(&app).expect("painted");
918        let scene = layer.build_scene(&mut app, SceneBuilder::new());
919        let parent = only_container(&scene.root.children);
920        let child = only_container(&parent.children);
921        assert!(
922            matches!(child.effect, Effect::Opacity { alpha: 128, .. }),
923            "the child's OpacityLayer: {:?}",
924            child.effect
925        );
926        assert!(
927            matches!(child.children.as_slice(), [Layer::DisplayList(_)]),
928            "the child's picture: {:?}",
929            child.children
930        );
931    }
932
933    /// The one container among `children`, ignoring the pictures beside it.
934    fn only_container(children: &[Layer]) -> &EngineLayer {
935        let mut containers = children.iter().filter_map(|child| match child {
936            Layer::Container(container) => Some(container),
937            _ => None,
938        });
939        let container = containers.next().expect("a container");
940        assert!(containers.next().is_none(), "one container: {children:?}");
941        container
942    }
943}