Skip to main content

inset_rendering/
custom_paint.rs

1//! Flutter counterpart: `rendering/custom_paint.dart` (`CustomPainter`, `RenderCustomPaint`).
2//!
3//! Semantics (`SemanticsBuilderCallback`, `CustomPainterSemantics`,
4//! `CustomPainter.semanticsBuilder` / `shouldRebuildSemantics`) waits.
5
6use std::any::Any;
7use std::rc::Rc;
8
9use inset_embedder::{Canvas, Matrix4, Offset, Size, TextBaseline};
10use inset_foundation::{App, Handle, Listenable, Listener};
11use inset_painting::ClipContext;
12
13use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
14use crate::object::{
15    AnyRenderObject, RenderHandle, RenderObject, RenderObjectData, RenderObjectWithChildData,
16    RenderObjectWithChildMixin,
17};
18use crate::painting_context::PaintingContext;
19use crate::pipeline_owner::PipelineOwner;
20use crate::proxy_box::{RenderProxyBoxMixin, same_instance};
21
22/// The interface used by `CustomPaint` (in the widgets library) and
23/// [`RenderCustomPaint`] (in the rendering library).
24///
25/// To implement a custom painter, implement this trait to define your custom paint delegate.
26/// A [`CustomPainter`] must implement the [`paint`](Self::paint) and
27/// [`should_repaint`](Self::should_repaint) methods, and may optionally also implement the
28/// [`hit_test`](Self::hit_test) method.
29///
30/// The [`paint`](Self::paint) method is called whenever the custom object needs to be
31/// repainted.
32///
33/// The [`should_repaint`](Self::should_repaint) method is called when a new instance of the
34/// class is provided, to check if the new instance actually represents different information.
35///
36/// The most efficient way to trigger a repaint is to supply a [`repaint`](Self::repaint)
37/// listenable, which notifies its listeners when it is time to repaint: the
38/// [`RenderCustomPaint`] listens to it and repaints whenever the animation ticks, avoiding both
39/// the build and layout phases of the pipeline.
40///
41/// The [`hit_test`](Self::hit_test) method is called when the user interacts with the
42/// underlying render object, to determine if the user hit the object or missed it.
43///
44/// ## Composition and the sharing of canvases
45///
46/// Widgets (or rather, render objects) are composited together using a minimum
47/// number of [`Canvas`]es, for performance reasons. As a result, a
48/// [`CustomPainter`]'s [`Canvas`] may be the same as that used by other widgets
49/// (including other `CustomPaint` widgets).
50///
51/// This is mostly unnoticeable, except when using unusual blend modes. For
52/// example, trying to use `BlendMode::DstOut` to "punch a hole" through a
53/// previously-drawn image may erase more than was intended, because previous
54/// widgets will have been painted onto the same canvas.
55///
56/// To avoid this issue, consider using `Canvas::save_layer` and
57/// `Canvas::restore` when using such blend modes. Creating new layers is
58/// relatively expensive, however, and should be done sparingly to avoid
59/// introducing jank.
60pub trait CustomPainter: 'static {
61    /// The [`Listenable`] that notifies when it is time to repaint.
62    ///
63    /// Dart's `CustomPainter` takes it as a constructor argument and forwards `addListener` /
64    /// `removeListener` to it; here the painter answers with it and the same forwarding is the
65    /// [`Listenable`] implementation of `dyn CustomPainter`.
66    fn repaint(&self) -> Option<&Rc<dyn Listenable>> {
67        None
68    }
69
70    /// Called whenever the object needs to paint. The given [`Canvas`] has its
71    /// coordinate space configured such that the origin is at the top left of the
72    /// box. The area of the box is the size of the `size` argument.
73    ///
74    /// Paint operations should remain inside the given area. Graphical
75    /// operations outside the bounds may be silently ignored, clipped, or not
76    /// clipped. It may sometimes be difficult to guarantee that a certain
77    /// operation is inside the bounds (e.g., drawing a rectangle whose size is
78    /// determined by user inputs). In that case, consider calling
79    /// `Canvas::clip_rect` at the beginning of `paint` so everything that follows
80    /// will be guaranteed to only draw within the clipped area.
81    ///
82    /// Implementations should be wary of correctly pairing any calls to
83    /// `Canvas::save` / `Canvas::save_layer` and `Canvas::restore`, otherwise all
84    /// subsequent painting on this canvas may be affected, with potentially
85    /// hilarious but confusing results.
86    ///
87    /// To paint text on a [`Canvas`], use a `TextPainter`.
88    ///
89    /// `app` reaches what a Dart painter reads directly: an animation's value, the fonts.
90    fn paint(&self, app: &mut App, canvas: &mut Canvas, size: Size);
91
92    /// Called whenever a new instance of the custom painter delegate class is
93    /// provided to the [`RenderCustomPaint`] object, or any time that a new
94    /// `CustomPaint` object is created with a new instance of the custom painter
95    /// delegate class (which amounts to the same thing, because the latter is
96    /// implemented in terms of the former).
97    ///
98    /// If the new instance represents different information than the old
99    /// instance, then the method should return true, otherwise it should return
100    /// false.
101    ///
102    /// If the method returns false, then the [`paint`](Self::paint) call might be optimized
103    /// away.
104    ///
105    /// It's possible that the [`paint`](Self::paint) method will get called even if
106    /// [`should_repaint`](Self::should_repaint) returns false (e.g. if an ancestor or
107    /// descendant needed to be repainted). It's also possible that the [`paint`](Self::paint)
108    /// method will get called without [`should_repaint`](Self::should_repaint) being called at
109    /// all (e.g. if the box changes size).
110    ///
111    /// If a custom delegate has a particularly expensive paint function such that
112    /// repaints should be avoided as much as possible, a `RepaintBoundary` or
113    /// [`crate::RenderRepaintBoundary`] (or other render object with
114    /// [`RenderObject::is_repaint_boundary`] set to true) might be helpful.
115    ///
116    /// `old_delegate` is only ever a painter of this painter's own type, as Dart's `covariant`
117    /// parameter is.
118    fn should_repaint(&self, app: &App, old_delegate: &dyn CustomPainter) -> bool;
119
120    /// Called whenever a hit test is being performed on an object that is using
121    /// this custom paint delegate.
122    ///
123    /// The given point is relative to the same coordinate space as the last
124    /// [`paint`](Self::paint) call.
125    ///
126    /// The default behavior is to consider all points to be hits for
127    /// background painters, and no points to be hits for foreground painters.
128    ///
129    /// Return `Some(true)` if the given position corresponds to a point on the drawn
130    /// image that should be considered a "hit", `Some(false)` if it corresponds to a
131    /// point that should be considered outside the painted image, and `None` to use
132    /// the default behavior.
133    fn hit_test(&self, app: &App, position: Offset) -> Option<bool> {
134        let _ = (app, position);
135        None
136    }
137
138    /// Downcast support for Dart's `runtimeType` comparison and `covariant` parameter.
139    fn as_any(&self) -> &dyn Any;
140}
141
142impl Listenable for dyn CustomPainter {
143    /// Register a closure to be notified when it is time to repaint.
144    ///
145    /// Forwards to the [`repaint`](CustomPainter::repaint) listenable, if it is not `None`.
146    fn add_listener(&self, app: &mut App, listener: Listener) {
147        if let Some(repaint) = self.repaint() {
148            repaint.add_listener(app, listener);
149        }
150    }
151
152    /// Remove a previously registered closure from the list of closures that the
153    /// object notifies when it is time to repaint.
154    fn remove_listener(&self, app: &mut App, listener: &Listener) {
155        if let Some(repaint) = self.repaint() {
156            repaint.remove_listener(app, listener);
157        }
158    }
159}
160
161/// Delegates its painting to a [`painter`](Self::painter) and a
162/// [`foreground_painter`](Self::foreground_painter).
163pub struct RenderCustomPaint {
164    render_object: RenderObjectData,
165    render_box: RenderBoxData,
166    child: RenderObjectWithChildData<AnyRenderBox>,
167    painter: Option<Rc<dyn CustomPainter>>,
168    foreground_painter: Option<Rc<dyn CustomPainter>>,
169    preferred_size: Size,
170    is_complex: bool,
171    will_change: bool,
172}
173
174impl RenderCustomPaint {
175    /// Creates a render object that delegates its painting.
176    pub fn new(
177        app: &mut App,
178        painter: Option<Rc<dyn CustomPainter>>,
179        foreground_painter: Option<Rc<dyn CustomPainter>>,
180        preferred_size: Size,
181        is_complex: bool,
182        will_change: bool,
183        child: Option<AnyRenderBox>,
184    ) -> RenderHandle<RenderCustomPaint> {
185        let this = RenderHandle::new_box(
186            app,
187            RenderCustomPaint {
188                render_object: RenderObjectData::new(),
189                render_box: RenderBoxData::new(),
190                child: RenderObjectWithChildData::new(),
191                painter,
192                foreground_painter,
193                preferred_size,
194                is_complex,
195                will_change,
196            },
197        );
198        this.set_child(app, child);
199        this
200    }
201
202    /// The background custom paint delegate.
203    ///
204    /// This painter, if non-`None`, is called to paint behind the children.
205    pub fn painter(self: RenderHandle<Self>, app: &App) -> Option<Rc<dyn CustomPainter>> {
206        self.get(app).painter.clone()
207    }
208
209    /// Set a new background custom paint delegate.
210    ///
211    /// If the new delegate is the same as the previous one, this does nothing.
212    ///
213    /// If the new delegate is the same class as the previous one, then the new
214    /// delegate has its [`CustomPainter::should_repaint`] called; if the result is
215    /// true, then the delegate will be called.
216    ///
217    /// If the new delegate is a different class than the previous one, then the
218    /// delegate will be called.
219    ///
220    /// If the new value is `None`, then there is no background custom painter.
221    pub fn set_painter(
222        self: RenderHandle<Self>,
223        app: &mut App,
224        value: Option<Rc<dyn CustomPainter>>,
225    ) {
226        let old_painter = self.get(app).painter.clone();
227        if same_instance(old_painter.as_ref(), value.as_ref()) {
228            return;
229        }
230        self.get_mut(app).painter = value.clone();
231        self.did_update_painter(app, value.as_ref(), old_painter.as_ref());
232    }
233
234    /// The foreground custom paint delegate.
235    ///
236    /// This painter, if non-`None`, is called to paint in front of the children.
237    pub fn foreground_painter(
238        self: RenderHandle<Self>,
239        app: &App,
240    ) -> Option<Rc<dyn CustomPainter>> {
241        self.get(app).foreground_painter.clone()
242    }
243
244    /// Set a new foreground custom paint delegate.
245    ///
246    /// If the new delegate is the same as the previous one, this does nothing.
247    ///
248    /// If the new delegate is the same class as the previous one, then the new
249    /// delegate has its [`CustomPainter::should_repaint`] called; if the result is
250    /// true, then the delegate will be called.
251    ///
252    /// If the new delegate is a different class than the previous one, then the
253    /// delegate will be called.
254    ///
255    /// If the new value is `None`, then there is no foreground custom painter.
256    pub fn set_foreground_painter(
257        self: RenderHandle<Self>,
258        app: &mut App,
259        value: Option<Rc<dyn CustomPainter>>,
260    ) {
261        let old_painter = self.get(app).foreground_painter.clone();
262        if same_instance(old_painter.as_ref(), value.as_ref()) {
263            return;
264        }
265        self.get_mut(app).foreground_painter = value.clone();
266        self.did_update_painter(app, value.as_ref(), old_painter.as_ref());
267    }
268
269    fn did_update_painter(
270        self: RenderHandle<Self>,
271        app: &mut App,
272        new_painter: Option<&Rc<dyn CustomPainter>>,
273        old_painter: Option<&Rc<dyn CustomPainter>>,
274    ) {
275        // Check if we need to repaint.
276        let repaint = match (new_painter, old_painter) {
277            (None, old_painter) => {
278                debug_assert!(old_painter.is_some()); // We should be called only for changes.
279                true
280            }
281            (Some(new_painter), Some(old_painter)) => {
282                new_painter.as_any().type_id() != old_painter.as_any().type_id()
283                    || new_painter.should_repaint(app, &**old_painter)
284            }
285            (Some(_), None) => true,
286        };
287        if repaint {
288            self.mark_needs_paint(app);
289        }
290        if self.attached(app) {
291            if let Some(old_painter) = old_painter {
292                old_painter.remove_listener(app, &self.paint_listener());
293            }
294            if let Some(new_painter) = new_painter {
295                new_painter.add_listener(app, self.paint_listener());
296            }
297        }
298        // Dart also rebuilds semantics here; semantics waits.
299    }
300
301    /// The `markNeedsPaint` tear-off, equal to itself across registrations.
302    fn paint_listener(self: RenderHandle<Self>) -> Listener {
303        Listener::handle_method(self.handle(), mark_needs_paint)
304    }
305
306    /// The size that this [`RenderCustomPaint`] should aim for, given the layout
307    /// constraints, if there is no child.
308    ///
309    /// Defaults to [`Size::ZERO`].
310    ///
311    /// If there's a child, this is ignored, and the size of the child is used
312    /// instead.
313    pub fn preferred_size(self: RenderHandle<Self>, app: &App) -> Size {
314        self.get(app).preferred_size
315    }
316
317    /// Sets [`preferred_size`](Self::preferred_size).
318    pub fn set_preferred_size(self: RenderHandle<Self>, app: &mut App, value: Size) {
319        if self.preferred_size(app) == value {
320            return;
321        }
322        self.get_mut(app).preferred_size = value;
323        self.mark_needs_layout(app);
324    }
325
326    /// Whether to hint that this layer's painting should be cached.
327    ///
328    /// The compositor contains a raster cache that holds bitmaps of layers in
329    /// order to avoid the cost of repeatedly rendering those layers on each
330    /// frame. If this flag is not set, then the compositor will apply its own
331    /// heuristics to decide whether the layer containing this render object is
332    /// complex enough to benefit from caching.
333    pub fn is_complex(self: RenderHandle<Self>, app: &App) -> bool {
334        self.get(app).is_complex
335    }
336
337    /// Sets [`is_complex`](Self::is_complex). Dart's plain field.
338    pub fn set_is_complex(self: RenderHandle<Self>, app: &mut App, value: bool) {
339        self.get_mut(app).is_complex = value;
340    }
341
342    /// Whether the raster cache should be told that this painting is likely
343    /// to change in the next frame.
344    ///
345    /// This hint tells the compositor not to cache the layer containing this
346    /// render object because the cache will not be used in the future. If this
347    /// hint is not set, the compositor will apply its own heuristics to decide
348    /// whether this layer is likely to be reused in the future.
349    pub fn will_change(self: RenderHandle<Self>, app: &App) -> bool {
350        self.get(app).will_change
351    }
352
353    /// Sets [`will_change`](Self::will_change). Dart's plain field.
354    pub fn set_will_change(self: RenderHandle<Self>, app: &mut App, value: bool) {
355        self.get_mut(app).will_change = value;
356    }
357
358    fn paint_with_painter(
359        self: RenderHandle<Self>,
360        app: &mut App,
361        canvas: &mut Canvas,
362        offset: Offset,
363        painter: &dyn CustomPainter,
364    ) {
365        canvas.save();
366        let debug_previous_canvas_save_count = canvas.save_count();
367        if offset != Offset::ZERO {
368            canvas.translate(offset.dx() as f32, offset.dy() as f32);
369        }
370        let size = self.size(app);
371        painter.paint(app, canvas, size);
372        // This isn't perfect. For example, we can't catch the case of
373        // someone first restoring, then setting a transform or whatnot,
374        // then saving.
375        debug_assert_eq!(
376            debug_previous_canvas_save_count,
377            canvas.save_count(),
378            "the custom painter had mismatching save and restore calls"
379        );
380        canvas.restore();
381    }
382
383    fn set_raster_cache_hints(self: RenderHandle<Self>, app: &App, context: &mut PaintingContext) {
384        if self.get(app).is_complex {
385            context.set_is_complex_hint();
386        }
387        if self.get(app).will_change {
388            context.set_will_change_hint();
389        }
390    }
391}
392
393fn mark_needs_paint(this: Handle<RenderCustomPaint>, app: &mut App) {
394    RenderHandle::from_handle(this).mark_needs_paint(app);
395}
396
397impl RenderObjectWithChildMixin for RenderCustomPaint {
398    type ChildType = AnyRenderBox;
399
400    fn child_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectWithChildData<AnyRenderBox> {
401        &self.get(app).child
402    }
403
404    fn child_data_mut(
405        self: RenderHandle<Self>,
406        app: &mut App,
407    ) -> &mut RenderObjectWithChildData<AnyRenderBox> {
408        &mut self.get_mut(app).child
409    }
410}
411
412impl RenderProxyBoxMixin for RenderCustomPaint {
413    fn compute_size_for_no_child(
414        self: RenderHandle<Self>,
415        app: &App,
416        constraints: BoxConstraints,
417    ) -> Size {
418        constraints.constrain(self.get(app).preferred_size)
419    }
420}
421
422impl RenderObject for RenderCustomPaint {
423    crate::render_object_accessors!();
424
425    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
426        if let Some(child) = self.child(app) {
427            child.as_object().attach(app, owner);
428        }
429        if let Some(painter) = self.painter(app) {
430            painter.add_listener(app, self.paint_listener());
431        }
432        if let Some(foreground_painter) = self.foreground_painter(app) {
433            foreground_painter.add_listener(app, self.paint_listener());
434        }
435    }
436
437    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
438        if let Some(painter) = self.painter(app) {
439            painter.remove_listener(app, &self.paint_listener());
440        }
441        if let Some(foreground_painter) = self.foreground_painter(app) {
442            foreground_painter.remove_listener(app, &self.paint_listener());
443        }
444        if let Some(child) = self.child(app) {
445            child.as_object().detach(app);
446        }
447    }
448
449    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
450        RenderProxyBoxMixin::perform_layout(self, app);
451    }
452
453    fn paint(
454        self: RenderHandle<Self>,
455        app: &mut App,
456        context: &mut PaintingContext,
457        offset: Offset,
458    ) {
459        if let Some(painter) = self.painter(app) {
460            self.paint_with_painter(app, context.canvas(), offset, &*painter);
461            self.set_raster_cache_hints(app, context);
462        }
463        RenderProxyBoxMixin::paint(self, app, context, offset);
464        if let Some(foreground_painter) = self.foreground_painter(app) {
465            self.paint_with_painter(app, context.canvas(), offset, &*foreground_painter);
466            self.set_raster_cache_hints(app, context);
467        }
468    }
469
470    fn visit_children(
471        self: RenderHandle<Self>,
472        app: &App,
473        visitor: &mut dyn FnMut(AnyRenderObject),
474    ) {
475        if let Some(child) = self.child(app) {
476            visitor(child.as_object());
477        }
478    }
479}
480
481impl RenderBox for RenderCustomPaint {
482    crate::render_box_accessors!();
483
484    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
485        RenderProxyBoxMixin::setup_parent_data(self, app, child);
486    }
487
488    fn apply_paint_transform(
489        self: RenderHandle<Self>,
490        app: &App,
491        child: AnyRenderObject,
492        transform: &mut Matrix4,
493    ) {
494        RenderProxyBoxMixin::apply_paint_transform(self, app, child, transform);
495    }
496
497    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
498        if self.child(app).is_none() {
499            let width = self.preferred_size(app).width();
500            return if width.is_finite() { width } else { 0.0 };
501        }
502        RenderProxyBoxMixin::compute_min_intrinsic_width(self, app, height)
503    }
504
505    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
506        if self.child(app).is_none() {
507            let width = self.preferred_size(app).width();
508            return if width.is_finite() { width } else { 0.0 };
509        }
510        RenderProxyBoxMixin::compute_max_intrinsic_width(self, app, height)
511    }
512
513    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
514        if self.child(app).is_none() {
515            let height = self.preferred_size(app).height();
516            return if height.is_finite() { height } else { 0.0 };
517        }
518        RenderProxyBoxMixin::compute_min_intrinsic_height(self, app, width)
519    }
520
521    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
522        if self.child(app).is_none() {
523            let height = self.preferred_size(app).height();
524            return if height.is_finite() { height } else { 0.0 };
525        }
526        RenderProxyBoxMixin::compute_max_intrinsic_height(self, app, width)
527    }
528
529    fn compute_distance_to_actual_baseline(
530        self: RenderHandle<Self>,
531        app: &mut App,
532        baseline: TextBaseline,
533    ) -> Option<f64> {
534        RenderProxyBoxMixin::compute_distance_to_actual_baseline(self, app, baseline)
535    }
536
537    fn compute_dry_baseline(
538        self: RenderHandle<Self>,
539        app: &mut App,
540        constraints: BoxConstraints,
541        baseline: TextBaseline,
542    ) -> Option<f64> {
543        RenderProxyBoxMixin::compute_dry_baseline(self, app, constraints, baseline)
544    }
545
546    fn compute_dry_layout(
547        self: RenderHandle<Self>,
548        app: &mut App,
549        constraints: BoxConstraints,
550    ) -> Size {
551        RenderProxyBoxMixin::compute_dry_layout(self, app, constraints)
552    }
553
554    fn hit_test_self(self: RenderHandle<Self>, app: &App, position: Offset) -> bool {
555        match &self.get(app).painter {
556            Some(painter) => painter.hit_test(app, position).unwrap_or(true),
557            None => false,
558        }
559    }
560
561    fn hit_test_children(
562        self: RenderHandle<Self>,
563        app: &mut App,
564        result: &mut BoxHitTestResult<'_>,
565        position: Offset,
566    ) -> bool {
567        if let Some(foreground_painter) = self.foreground_painter(app)
568            && foreground_painter.hit_test(app, position).unwrap_or(false)
569        {
570            return true;
571        }
572        RenderProxyBoxMixin::hit_test_children(self, app, result, position)
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use inset_foundation::AppCell;
579    use std::cell::Cell;
580
581    use inset_embedder::{Color, Paint, valo};
582    use inset_foundation::ValueNotifier;
583    use inset_gestures::HitTestResult;
584
585    use super::*;
586    use crate::box_::BoxHitTestResult;
587    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer, PictureLayer};
588    use crate::object::AnyRenderObject;
589    use crate::proxy_box::RenderRepaintBoundary;
590
591    const RED: Color = Color::from_argb(255, 255, 0, 0);
592    const BLUE: Color = Color::from_argb(255, 0, 0, 255);
593    const GREEN: Color = Color::from_argb(255, 0, 255, 0);
594
595    /// Fills the box with `color`, counting its paints; `hit` is what it reports for every
596    /// position, and `should_repaint` what it answers about any older painter.
597    struct Recorder {
598        color: Color,
599        paints: Rc<Cell<u32>>,
600        hit: Option<bool>,
601        repaint: Option<Rc<dyn Listenable>>,
602        should_repaint: bool,
603    }
604
605    impl Recorder {
606        fn new(color: Color) -> Recorder {
607            Recorder {
608                color,
609                paints: Rc::new(Cell::new(0)),
610                hit: None,
611                repaint: None,
612                should_repaint: false,
613            }
614        }
615    }
616
617    impl CustomPainter for Recorder {
618        fn repaint(&self) -> Option<&Rc<dyn Listenable>> {
619            self.repaint.as_ref()
620        }
621
622        fn paint(&self, _app: &mut App, canvas: &mut Canvas, size: Size) {
623            self.paints.set(self.paints.get() + 1);
624            canvas.draw_rect(
625                Offset::ZERO & size,
626                &Paint {
627                    color: self.color.into(),
628                    ..Paint::default()
629                },
630            );
631        }
632
633        fn should_repaint(&self, _app: &App, _old_delegate: &dyn CustomPainter) -> bool {
634            self.should_repaint
635        }
636
637        fn hit_test(&self, _app: &App, _position: Offset) -> Option<bool> {
638            self.hit
639        }
640
641        fn as_any(&self) -> &dyn Any {
642            self
643        }
644    }
645
646    fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
647        let root = OffsetLayer::new(app, Offset::ZERO);
648        root.as_layer().attach(app, node.id());
649        node.schedule_initial_paint(app, root.as_container_layer());
650    }
651
652    /// The test binding's first frame: attach, lay the root out, schedule and flush paint.
653    fn first_frame(app: &mut App, root: AnyRenderBox) -> Handle<PipelineOwner> {
654        let owner = PipelineOwner::new(app, None, None);
655        owner.set_root_node(app, Some(root.as_object()));
656        root.layout(app, BoxConstraints::tight(Size::new(100.0, 100.0)), false);
657        schedule_root_paint(app, root.as_object());
658        owner.flush_compositing_bits(app);
659        owner.flush_paint(app);
660        owner
661    }
662
663    fn pump_frame(app: &mut App, owner: Handle<PipelineOwner>) {
664        owner.flush_layout(app);
665        owner.flush_compositing_bits(app);
666        owner.flush_paint(app);
667    }
668
669    /// The color of every fill in a repaint boundary's recording, in paint order.
670    fn drawn_colors(app: &App, boundary: AnyRenderObject) -> Vec<valo::Color> {
671        let layer = boundary.debug_layer(app).expect("painted");
672        layer
673            .depth_first_iterate_children(app)
674            .into_iter()
675            .filter_map(|child| {
676                app.handle::<PictureLayer>(child.id())
677                    .and_then(|picture| picture.picture(app).map(|p| p.ops().to_vec()))
678            })
679            .flatten()
680            .filter_map(|op| match op {
681                valo::Op::DrawRect { paint, .. } => Some(paint.color),
682                _ => None,
683            })
684            .collect()
685    }
686
687    fn hit_test(app: &mut App, target: AnyRenderBox, position: Offset) -> (bool, usize) {
688        let mut result = HitTestResult::new();
689        let is_hit = target.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
690        (is_hit, result.path().len())
691    }
692
693    #[test]
694    fn both_painters_paint_around_the_child() {
695        let cell = AppCell::new();
696        let mut app = cell.borrow_mut();
697        let background = Rc::new(Recorder::new(RED));
698        let foreground = Rc::new(Recorder::new(BLUE));
699        let child = crate::proxy_box::RenderColoredBox::new(&mut app, GREEN, true, None);
700        let custom = RenderCustomPaint::new(
701            &mut app,
702            Some(background.clone()),
703            Some(foreground.clone()),
704            Size::ZERO,
705            false,
706            false,
707            Some(child.as_box()),
708        );
709        let root = RenderRepaintBoundary::new(&mut app, Some(custom.as_box()));
710        first_frame(&mut app, root.as_box());
711
712        assert_eq!((background.paints.get(), foreground.paints.get()), (1, 1));
713        assert_eq!(
714            drawn_colors(&app, root.as_object()),
715            [RED.into(), GREEN.into(), BLUE.into()],
716            "background, child, foreground"
717        );
718    }
719
720    #[test]
721    fn a_painter_repaints_when_its_repaint_listenable_notifies() {
722        let cell = AppCell::new();
723        let mut app = cell.borrow_mut();
724        let notifier = app.create(ValueNotifier::new(0));
725        let painter = Rc::new(Recorder {
726            repaint: Some(Rc::new(notifier)),
727            ..Recorder::new(RED)
728        });
729        let custom = RenderCustomPaint::new(
730            &mut app,
731            Some(painter.clone()),
732            None,
733            Size::new(10.0, 10.0),
734            false,
735            false,
736            None,
737        );
738        let root = RenderRepaintBoundary::new(&mut app, Some(custom.as_box()));
739        let owner = first_frame(&mut app, root.as_box());
740        assert_eq!(painter.paints.get(), 1);
741        assert!(!custom.as_object().debug_needs_paint(&app));
742
743        notifier.set_value(&mut app, 1);
744        assert!(custom.as_object().debug_needs_paint(&app));
745        pump_frame(&mut app, owner);
746        assert_eq!(painter.paints.get(), 2);
747    }
748
749    /// Dart's `_didUpdatePainter`: the new painter decides, and the listener moves with it.
750    #[test]
751    fn replacing_the_painter_asks_it_whether_to_repaint() {
752        let cell = AppCell::new();
753        let mut app = cell.borrow_mut();
754        let notifier = app.create(ValueNotifier::new(0));
755        let painter = Rc::new(Recorder {
756            repaint: Some(Rc::new(notifier)),
757            ..Recorder::new(RED)
758        });
759        let custom = RenderCustomPaint::new(
760            &mut app,
761            Some(painter.clone()),
762            None,
763            Size::new(10.0, 10.0),
764            false,
765            false,
766            None,
767        );
768        let root = RenderRepaintBoundary::new(&mut app, Some(custom.as_box()));
769        let owner = first_frame(&mut app, root.as_box());
770
771        let quiet = Rc::new(Recorder::new(BLUE));
772        custom.set_painter(&mut app, Some(quiet.clone()));
773        assert!(
774            !custom.as_object().debug_needs_paint(&app),
775            "should_repaint is false"
776        );
777        notifier.set_value(&mut app, 1);
778        assert!(
779            !custom.as_object().debug_needs_paint(&app),
780            "the old painter's listenable was unsubscribed"
781        );
782
783        let eager = Rc::new(Recorder {
784            should_repaint: true,
785            ..Recorder::new(BLUE)
786        });
787        custom.set_painter(&mut app, Some(eager.clone()));
788        assert!(custom.as_object().debug_needs_paint(&app));
789        pump_frame(&mut app, owner);
790        assert_eq!((quiet.paints.get(), eager.paints.get()), (0, 1));
791
792        custom.set_painter(&mut app, None);
793        assert!(
794            custom.as_object().debug_needs_paint(&app),
795            "a removal repaints"
796        );
797    }
798
799    #[test]
800    fn preferred_size_is_used_without_a_child() {
801        let cell = AppCell::new();
802        let mut app = cell.borrow_mut();
803        let custom = RenderCustomPaint::new(
804            &mut app,
805            None,
806            None,
807            Size::new(20.0, 30.0),
808            false,
809            false,
810            None,
811        );
812        custom.layout(&mut app, BoxConstraints::new(), false);
813        assert_eq!(custom.size(&app), Size::new(20.0, 30.0));
814
815        custom.layout(
816            &mut app,
817            BoxConstraints::loose(Size::new(10.0, 10.0)),
818            false,
819        );
820        assert_eq!(
821            custom.size(&app),
822            Size::new(10.0, 10.0),
823            "the preferred size is constrained"
824        );
825    }
826
827    #[test]
828    fn a_child_sizes_the_custom_paint() {
829        let cell = AppCell::new();
830        let mut app = cell.borrow_mut();
831        let child = crate::proxy_box::RenderConstrainedBox::new(
832            &mut app,
833            BoxConstraints::tight(Size::new(40.0, 40.0)),
834            None,
835        );
836        let custom = RenderCustomPaint::new(
837            &mut app,
838            None,
839            None,
840            Size::new(20.0, 30.0),
841            false,
842            false,
843            Some(child.as_box()),
844        );
845        custom.layout(&mut app, BoxConstraints::new(), false);
846        assert_eq!(custom.size(&app), Size::new(40.0, 40.0));
847    }
848
849    #[test]
850    fn the_painters_answer_hit_tests() {
851        let cell = AppCell::new();
852        let mut app = cell.borrow_mut();
853        let background = Rc::new(Recorder::new(RED));
854        let custom = RenderCustomPaint::new(
855            &mut app,
856            Some(background),
857            None,
858            Size::new(100.0, 100.0),
859            false,
860            false,
861            None,
862        );
863        custom.layout(&mut app, BoxConstraints::new(), false);
864        assert_eq!(
865            hit_test(&mut app, custom.as_box(), Offset::new(50.0, 50.0)),
866            (true, 1),
867            "a background painter is hit by default"
868        );
869
870        let missing = Rc::new(Recorder {
871            hit: Some(false),
872            ..Recorder::new(RED)
873        });
874        custom.set_painter(&mut app, Some(missing));
875        assert_eq!(
876            hit_test(&mut app, custom.as_box(), Offset::new(50.0, 50.0)),
877            (false, 0)
878        );
879
880        custom.set_painter(&mut app, None);
881        assert_eq!(
882            hit_test(&mut app, custom.as_box(), Offset::new(50.0, 50.0)),
883            (false, 0),
884            "no painter, no hit"
885        );
886
887        let foreground = Rc::new(Recorder {
888            hit: Some(true),
889            ..Recorder::new(BLUE)
890        });
891        custom.set_foreground_painter(&mut app, Some(foreground));
892        assert_eq!(
893            hit_test(&mut app, custom.as_box(), Offset::new(50.0, 50.0)),
894            (true, 1),
895            "a foreground painter answers for the children"
896        );
897    }
898}