Skip to main content

inset_rendering/
view.rs

1//! Flutter counterpart: `rendering/view.dart` (`ViewConfiguration`, `RenderView`).
2//!
3//! `applyPaintTransform` and semantics wait.
4
5use std::rc::Rc;
6
7use inset_embedder::{
8    Matrix4, Offset, Rect, SceneBuilder, Size, SystemUiOverlayStyle, TargetPlatform, View, ViewRef,
9};
10use inset_foundation::{App, Handle};
11use inset_gestures::{HitTestEntry, HitTestResult, HitTestTarget, PointerEvent};
12use inset_services::SystemChrome;
13
14use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult};
15use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayerMixin, TransformLayer};
16use crate::object::{
17    AnyRenderObject, Constraints, RenderHandle, RenderObject, RenderObjectData, RenderObjectVTable,
18    resolve,
19};
20use crate::painting_context::PaintingContext;
21
22/// The layout constraints for the root render object.
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct ViewConfiguration {
25    /// The constraints of the output surface in logical pixels.
26    pub logical_constraints: BoxConstraints,
27    /// The constraints of the output surface in physical pixels.
28    pub physical_constraints: BoxConstraints,
29    /// The pixel density of the output surface.
30    pub device_pixel_ratio: f64,
31}
32
33impl ViewConfiguration {
34    /// Creates a view configuration.
35    ///
36    /// By default, the view has zero constraints and a pixel ratio of 1.0.
37    pub const fn new(
38        physical_constraints: BoxConstraints,
39        logical_constraints: BoxConstraints,
40        device_pixel_ratio: f64,
41    ) -> ViewConfiguration {
42        ViewConfiguration {
43            logical_constraints,
44            physical_constraints,
45            device_pixel_ratio,
46        }
47    }
48
49    /// Creates a view configuration that matches `view`.
50    pub fn from_view(view: &dyn View) -> ViewConfiguration {
51        let metrics = view.metrics();
52        let physical_constraints =
53            BoxConstraints::from_view_constraints(metrics.physical_constraints);
54        let device_pixel_ratio = metrics.device_pixel_ratio;
55        ViewConfiguration {
56            logical_constraints: physical_constraints / device_pixel_ratio,
57            physical_constraints,
58            device_pixel_ratio,
59        }
60    }
61
62    /// Creates a transformation matrix that applies the
63    /// [`device_pixel_ratio`](Self::device_pixel_ratio).
64    pub fn to_matrix(&self) -> Matrix4 {
65        Matrix4::scale(
66            self.device_pixel_ratio as f32,
67            self.device_pixel_ratio as f32,
68        )
69    }
70
71    /// Returns whether [`to_matrix`](Self::to_matrix) would return a different value for
72    /// this configuration than it would for the given `old_configuration`.
73    pub fn should_update_matrix(&self, old_configuration: &ViewConfiguration) -> bool {
74        old_configuration.device_pixel_ratio != self.device_pixel_ratio
75    }
76
77    /// Transforms the provided `logical_size` to physical pixels.
78    pub fn to_physical_size(&self, logical_size: Size) -> Size {
79        self.physical_constraints
80            .constrain(logical_size * self.device_pixel_ratio)
81    }
82}
83
84impl Default for ViewConfiguration {
85    fn default() -> ViewConfiguration {
86        ViewConfiguration::new(
87            BoxConstraints::new().max_width(0.0).max_height(0.0),
88            BoxConstraints::new().max_width(0.0).max_height(0.0),
89            1.0,
90        )
91    }
92}
93
94/// The root of the render tree.
95///
96/// The view represents the total output surface of the render tree and handles
97/// bootstrapping the rendering pipeline. The view has a unique child
98/// [`crate::RenderBox`], which is required to fill the entire output surface.
99///
100/// Not a box: its constraints come from its [`configuration`](Self::configuration), and its
101/// child is the one slot Flutter's `RenderObjectWithChildMixin` gives it.
102pub struct RenderView {
103    render_object: RenderObjectData,
104    child: Option<AnyRenderBox>,
105    size: Size,
106    configuration: Option<ViewConfiguration>,
107    view: ViewRef,
108    root_transform: Option<Matrix4>,
109    needs_composite_frame: bool,
110    /// Whether the system overlays are styled from the layer tree at every frame.
111    ///
112    /// An application that would rather drive them itself, through
113    /// [`SystemChrome::set_system_ui_overlay_style`], turns this off so its own style is
114    /// not overwritten by whatever happens to be annotated under the bars.
115    pub automatic_system_ui_adjustment: bool,
116}
117
118impl RenderView {
119    /// Creates the root of the render tree.
120    ///
121    /// Typically created by the binding (e.g., `RendererBinding`).
122    ///
123    /// Providing a `configuration` is optional, but a configuration must be set
124    /// before calling [`prepare_initial_frame`](Self::prepare_initial_frame).
125    pub fn new(
126        app: &mut App,
127        child: Option<AnyRenderBox>,
128        configuration: Option<ViewConfiguration>,
129        view: ViewRef,
130    ) -> RenderHandle<RenderView> {
131        let this = RenderHandle::from_handle(app.create(RenderView {
132            render_object: RenderObjectData::new(),
133            child: None,
134            size: Size::ZERO,
135            configuration: None,
136            view,
137            root_transform: None,
138            needs_composite_frame: false,
139            automatic_system_ui_adjustment: true,
140        }));
141        let data = this.render_object_data_mut(app);
142        data.object_vtable = Some(&VTABLE);
143        data.was_repaint_boundary = true;
144        data.needs_compositing = true;
145        if let Some(configuration) = configuration {
146            this.set_configuration(app, configuration);
147        }
148        this.set_child(app, child);
149        this
150    }
151
152    /// The type-erased `RenderObject` handle.
153    pub fn as_object(self: RenderHandle<Self>) -> AnyRenderObject {
154        AnyRenderObject::from_vtable(self.id(), const { &VTABLE })
155    }
156
157    /// The render object's unique child. Flutter's `RenderObjectWithChildMixin.child`.
158    pub fn child(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
159        self.get(app).child
160    }
161
162    /// Sets the unique child, adopting or dropping as Flutter's setter does.
163    pub fn set_child(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderBox>) {
164        if let Some(old) = self.get(app).child {
165            self.as_object().drop_child(app, old.as_object());
166        }
167        self.get_mut(app).child = value;
168        if let Some(new) = value {
169            self.as_object().adopt_child(app, new.as_object());
170        }
171    }
172
173    /// The current layout size of the view.
174    pub fn size(self: RenderHandle<Self>, app: &App) -> Size {
175        self.get(app).size
176    }
177
178    /// The constraints used for the root layout.
179    ///
180    /// Typically, this configuration is set by the `RendererBinding`, when the
181    /// [`RenderView`] is registered with it. It will also update the configuration
182    /// if necessary. Therefore, if used in conjunction with the `RendererBinding`
183    /// this property must not be set manually as the `RendererBinding` will just
184    /// override it.
185    ///
186    /// # Panics
187    ///
188    /// If no configuration has been set.
189    pub fn configuration(self: RenderHandle<Self>, app: &App) -> ViewConfiguration {
190        self.get(app)
191            .configuration
192            .expect("RenderView has not been given a configuration yet")
193    }
194
195    /// Sets [`configuration`](Self::configuration).
196    pub fn set_configuration(self: RenderHandle<Self>, app: &mut App, value: ViewConfiguration) {
197        if self.get(app).configuration == Some(value) {
198            return;
199        }
200        let old_configuration = self.get(app).configuration;
201        self.get_mut(app).configuration = Some(value);
202        if self.get(app).root_transform.is_none() {
203            // [prepare_initial_frame] has not been called yet, nothing more to do for now.
204            return;
205        }
206        if old_configuration.is_none_or(|old| value.should_update_matrix(&old)) {
207            let root_layer = self.update_matrices_and_create_new_root_layer(app);
208            self.as_object()
209                .replace_root_layer(app, root_layer.as_offset_layer());
210        }
211        debug_assert!(self.get(app).root_transform.is_some());
212        self.as_object().mark_needs_layout(app);
213    }
214
215    /// Whether a [`configuration`](Self::configuration) has been set.
216    pub fn has_configuration(self: RenderHandle<Self>, app: &App) -> bool {
217        self.get(app).configuration.is_some()
218    }
219
220    /// The constraints most recently supplied for layout: the configuration's logical
221    /// constraints.
222    ///
223    /// # Panics
224    ///
225    /// If no configuration has been set.
226    pub fn constraints(self: RenderHandle<Self>, app: &App) -> BoxConstraints {
227        debug_assert!(
228            self.has_configuration(app),
229            "Constraints are not available because RenderView has not been given a configuration yet."
230        );
231        self.configuration(app).logical_constraints
232    }
233
234    /// The host view into which this render tree is rendered.
235    pub fn flutter_view(self: RenderHandle<Self>, app: &App) -> ViewRef {
236        self.get(app).view.clone()
237    }
238
239    /// Bootstrap the rendering pipeline by preparing the first frame.
240    ///
241    /// This should only be called once, and must be called before changing
242    /// [`configuration`](Self::configuration). It is typically called immediately after calling
243    /// the constructor.
244    ///
245    /// This does not actually schedule the first frame. Call
246    /// `SchedulerBinding::ensure_visual_update` for that.
247    pub fn prepare_initial_frame(self: RenderHandle<Self>, app: &mut App) {
248        debug_assert!(
249            self.as_object().owner(app).is_some(),
250            "attach the RenderView to a PipelineOwner before calling prepare_initial_frame"
251        );
252        debug_assert!(
253            self.get(app).root_transform.is_none(),
254            "prepare_initial_frame must only be called once"
255        );
256        debug_assert!(
257            self.has_configuration(app),
258            "set a configuration before calling prepare_initial_frame"
259        );
260        self.as_object().schedule_initial_layout(app);
261        let root_layer = self.update_matrices_and_create_new_root_layer(app);
262        self.as_object()
263            .schedule_initial_paint(app, root_layer.as_container_layer());
264        debug_assert!(self.get(app).root_transform.is_some());
265    }
266
267    /// Whether this view must be composited on the next frame.
268    ///
269    /// This flag is cleared once the view gets successfully composited.
270    pub fn needs_composite_frame(self: RenderHandle<Self>, app: &App) -> bool {
271        self.get(app).needs_composite_frame
272    }
273
274    /// Marks this view required to be composited on the next frame.
275    ///
276    /// This happens by the [`PipelineOwner`](crate::PipelineOwner) during
277    /// [`flush_paint`](crate::PipelineOwner::flush_paint) if there are any render objects that
278    /// need to be painted. It is also set for a new view, for the warm-up frame, and when the
279    /// app is resumed from the background.
280    pub fn mark_needs_composite_frame(self: RenderHandle<Self>, app: &mut App) {
281        self.get_mut(app).needs_composite_frame = true;
282    }
283
284    fn update_matrices_and_create_new_root_layer(
285        self: RenderHandle<Self>,
286        app: &mut App,
287    ) -> Handle<TransformLayer> {
288        debug_assert!(self.has_configuration(app));
289        let root_transform = self.configuration(app).to_matrix();
290        self.get_mut(app).root_transform = Some(root_transform);
291        let root_layer = TransformLayer::new(app);
292        root_layer.set_transform(app, root_transform);
293        root_layer.attach(app, self.id());
294        debug_assert!(self.get(app).root_transform.is_some());
295        root_layer
296    }
297
298    /// Determines the set of render objects located at the given position.
299    ///
300    /// Returns true if the given point is contained in this render object or one of its
301    /// descendants. Adds any render objects that contain the point to the given hit test result.
302    ///
303    /// The `position` argument is in the coordinate system of the render view, which is to say,
304    /// in logical pixels. This is not necessarily the same coordinate system as that expected by
305    /// the root layer (which is in physical pixels).
306    pub fn hit_test(
307        self: RenderHandle<Self>,
308        app: &mut App,
309        result: &mut HitTestResult,
310        position: Offset,
311    ) -> bool {
312        if let Some(child) = self.child(app) {
313            child.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
314        }
315        result.add(HitTestEntry::new(self));
316        true
317    }
318
319    /// Uploads the composited layer tree to the host.
320    ///
321    /// Actually causes the output of the rendering pipeline to appear on screen.
322    pub fn composite_frame(self: RenderHandle<Self>, app: &mut App) {
323        debug_assert!(
324            self.has_configuration(app),
325            "set the RenderView configuration before calling composite_frame"
326        );
327        debug_assert!(
328            self.get(app).root_transform.is_some(),
329            "call prepare_initial_frame before calling composite_frame"
330        );
331        let layer = self
332            .as_object()
333            .layer(app)
334            .expect("call prepare_initial_frame before calling composite_frame");
335        let scene = layer.build_scene(app, SceneBuilder::new());
336        if self.get(app).automatic_system_ui_adjustment {
337            self.update_system_chrome(app);
338        }
339        debug_assert!(
340            self.configuration(app)
341                .logical_constraints
342                .is_satisfied_by(self.size(app))
343        );
344        self.get(app).view.present(std::sync::Arc::new(scene));
345        self.get_mut(app).needs_composite_frame = false;
346    }
347
348    /// Styles the system overlays from whatever the layer tree annotates beneath them.
349    ///
350    /// Takes the overlay style from the place where a system status bar and system
351    /// navigation bar are placed to update system style overlay. The center of the system
352    /// navigation bar and the center of the status bar are used to get
353    /// `SystemUiOverlayStyle`'s to update system overlay appearance.
354    ///
355    /// ```text
356    ///         Horizontal center of the screen
357    ///                 V
358    ///    ++++++++++++++++++++++++++
359    ///    |                        |
360    ///    |    System status bar   |  <- Vertical center of the status bar
361    ///    |                        |
362    ///    ++++++++++++++++++++++++++
363    ///    |                        |
364    ///    |        Content         |
365    ///    ~                        ~
366    ///    |                        |
367    ///    ++++++++++++++++++++++++++
368    ///    |                        |
369    ///    |  System navigation bar | <- Vertical center of the navigation bar
370    ///    |                        |
371    ///    ++++++++++++++++++++++++++ <- bounds.bottom
372    /// ```
373    fn update_system_chrome(self: RenderHandle<Self>, app: &mut App) {
374        let Some(layer) = self.as_object().layer(app) else {
375            return;
376        };
377        let bounds = self.paint_bounds(app);
378        let padding = self.get(app).view.metrics().padding;
379        // Center of the status bar
380        let top = Offset::new(
381            // Horizontal center of the screen
382            bounds.center().dx(),
383            // The vertical center of the system status bar. The system status bar height
384            // is kept as top window padding.
385            padding.top / 2.0,
386        );
387        // Center of the navigation bar
388        let bottom = Offset::new(
389            // Horizontal center of the screen
390            bounds.center().dx(),
391            // Vertical center of the system navigation bar. The system navigation bar
392            // height is kept as bottom window padding. The "1" needs to be subtracted from
393            // the bottom because available pixels are in (0..bottom) range. I.e. for a
394            // device with 1920 height, bound.bottom is 1920, but the most bottom drawn
395            // pixel is at 1919 position.
396            bounds.bottom - 1.0 - padding.bottom / 2.0,
397        );
398        let layer = layer.as_layer();
399        let upper = layer.find::<SystemUiOverlayStyle>(app, top);
400        // Only Android has a customizable system navigation bar.
401        let is_android = app.platform().target_platform() == TargetPlatform::Android;
402        let lower = is_android
403            .then(|| layer.find::<SystemUiOverlayStyle>(app, bottom))
404            .flatten();
405        // If there are no overlay style in the UI don't bother updating.
406        let Some(style) = merged_overlay_style(upper, lower, is_android) else {
407            return;
408        };
409        SystemChrome::set_system_ui_overlay_style(app, &style);
410    }
411
412    /// An estimate of the bounds within which this render object will paint, in physical
413    /// pixels.
414    pub fn paint_bounds(self: RenderHandle<Self>, app: &App) -> Rect {
415        Offset::ZERO & (self.size(app) * self.configuration(app).device_pixel_ratio)
416    }
417}
418
419/// The style the two sampled points make between them, and `None` where the tree annotates
420/// neither: nothing was said about the overlays, so nothing is said to the host.
421///
422/// When both are annotated the upper provides the status bar properties and the lower
423/// provides the system navigation bar properties. This is for the case where a widget at
424/// the top, an app bar say, annotates the status bar's style while another at the bottom
425/// annotates the navigation bar's. When only one of them is, it provides every property,
426/// which lets one annotated region set both.
427fn merged_overlay_style(
428    upper: Option<Rc<SystemUiOverlayStyle>>,
429    lower: Option<Rc<SystemUiOverlayStyle>>,
430    is_android: bool,
431) -> Option<SystemUiOverlayStyle> {
432    if let (Some(upper), Some(lower)) = (&upper, &lower) {
433        return Some(SystemUiOverlayStyle {
434            status_bar_brightness: upper.status_bar_brightness,
435            status_bar_icon_brightness: upper.status_bar_icon_brightness,
436            status_bar_color: upper.status_bar_color,
437            system_status_bar_contrast_enforced: upper.system_status_bar_contrast_enforced,
438            system_navigation_bar_color: lower.system_navigation_bar_color,
439            system_navigation_bar_divider_color: lower.system_navigation_bar_divider_color,
440            system_navigation_bar_icon_brightness: lower.system_navigation_bar_icon_brightness,
441            system_navigation_bar_contrast_enforced: lower.system_navigation_bar_contrast_enforced,
442        });
443    }
444    let defined = upper.or(lower)?;
445    if !is_android {
446        // Only Android has a system navigation bar to style.
447        return Some(SystemUiOverlayStyle {
448            status_bar_brightness: defined.status_bar_brightness,
449            status_bar_icon_brightness: defined.status_bar_icon_brightness,
450            status_bar_color: defined.status_bar_color,
451            system_status_bar_contrast_enforced: defined.system_status_bar_contrast_enforced,
452            ..SystemUiOverlayStyle::new()
453        });
454    }
455    Some(*defined)
456}
457
458impl RenderObject for RenderView {
459    fn render_object_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectData {
460        &self.get(app).render_object
461    }
462
463    fn render_object_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderObjectData {
464        &mut self.get_mut(app).render_object
465    }
466
467    fn perform_resize(self: RenderHandle<Self>, _app: &mut App) {
468        panic!("RenderView is never sized by its parent");
469    }
470
471    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
472        debug_assert!(self.get(app).root_transform.is_some());
473        let constraints = self.constraints(app);
474        let sized_by_child = !constraints.is_tight();
475        let child = self.child(app);
476        if let Some(child) = child {
477            child.layout(app, constraints, sized_by_child);
478        }
479        let size = match child {
480            Some(child) if sized_by_child => child.size(app),
481            _ => constraints.smallest(),
482        };
483        self.get_mut(app).size = size;
484        debug_assert!(size.is_finite());
485        debug_assert!(constraints.is_satisfied_by(size));
486    }
487
488    fn visit_children(
489        self: RenderHandle<Self>,
490        app: &App,
491        visitor: &mut dyn FnMut(AnyRenderObject),
492    ) {
493        if let Some(child) = self.child(app) {
494            visitor(child.as_object());
495        }
496    }
497
498    fn apply_paint_transform(
499        self: RenderHandle<Self>,
500        app: &App,
501        child: AnyRenderObject,
502        transform: &mut Matrix4,
503    ) {
504        let root_transform = self
505            .get(app)
506            .root_transform
507            .as_ref()
508            .expect("the view has a configuration");
509        crate::object::multiply(transform, root_transform);
510        debug_assert!(child.parent(app).map(AnyRenderObject::id) == Some(self.id()));
511    }
512
513    fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
514        true
515    }
516
517    fn paint(
518        self: RenderHandle<Self>,
519        app: &mut App,
520        context: &mut PaintingContext,
521        offset: Offset,
522    ) {
523        if let Some(child) = self.child(app) {
524            context.paint_child(app, child.as_object(), offset);
525        }
526    }
527}
528
529/// Flutter's `RenderObject.handleEvent` default: the view itself does nothing with events.
530impl HitTestTarget for RenderHandle<RenderView> {
531    fn handle_event(&self, _app: &mut App, _event: &PointerEvent, _entry: &HitTestEntry) {}
532}
533
534/// The view is its own protocol: not a box, one object, so its vtable is a plain constant.
535static VTABLE: RenderObjectVTable = RenderObjectVTable::of::<RenderView>(
536    |app, id, child| <RenderView as RenderObject>::setup_parent_data(resolve(id), app, child),
537    |app, id| RenderView::paint_bounds(resolve(id), app),
538    |app, id, child, transform| {
539        <RenderView as RenderObject>::apply_paint_transform(resolve(id), app, child, transform)
540    },
541    |app, id| <RenderView as RenderObject>::perform_resize(resolve(id), app),
542    |app, id| crate::object::RenderObjectBase::mark_needs_layout(resolve::<RenderView>(id), app),
543    None,
544    None,
545);
546
547#[cfg(test)]
548mod tests {
549    use inset_foundation::AppCell;
550    use std::cell::Cell;
551    use std::rc::Rc;
552
553    use inset_embedder::{ViewId, ViewMetrics};
554
555    use super::*;
556    use crate::box_::RenderBox;
557    use crate::layer::TransformLayer;
558    use crate::pipeline_owner::PipelineOwner;
559    use crate::proxy_box::RenderConstrainedBox;
560    use crate::shifted_box::RenderPadding;
561    use inset_embedder::ViewHost;
562    use inset_painting::EdgeInsetsGeometry;
563    use inset_test::TestViewHost;
564
565    struct TestView {
566        metrics: ViewMetrics,
567        presented: Rc<Cell<u32>>,
568        host: TestViewHost,
569    }
570
571    impl View for TestView {
572        fn id(&self) -> ViewId {
573            ViewId(0)
574        }
575
576        fn metrics(&self) -> ViewMetrics {
577            self.metrics
578        }
579
580        fn present(&self, _scene: std::sync::Arc<inset_embedder::Scene>) {
581            self.presented.set(self.presented.get() + 1);
582        }
583
584        fn host(&self) -> &dyn ViewHost {
585            &self.host
586        }
587    }
588
589    fn test_view(presented: &Rc<Cell<u32>>) -> ViewRef {
590        Rc::new(TestView {
591            metrics: ViewMetrics::default(),
592            presented: Rc::clone(presented),
593            host: TestViewHost::default(),
594        })
595    }
596
597    /// `view_test.dart`'s `createViewConfiguration`.
598    fn create_view_configuration(size: Size, device_pixel_ratio: f64) -> ViewConfiguration {
599        let constraints = BoxConstraints::tight(size);
600        ViewConfiguration::new(
601            constraints * device_pixel_ratio,
602            constraints,
603            device_pixel_ratio,
604        )
605    }
606
607    /// `view_test.dart`: `ViewConfiguration == and hashCode`.
608    #[test]
609    fn view_configuration_equality() {
610        let a = create_view_configuration(Size::new(800.0, 600.0), 1.0);
611        let b = create_view_configuration(Size::new(800.0, 600.0), 1.0);
612        let c = create_view_configuration(Size::new(800.0, 600.0), 3.0);
613        assert_eq!(a, b);
614        assert_ne!(a, c);
615    }
616
617    /// `view_test.dart`: `Constraints are derived from configuration`.
618    #[test]
619    fn constraints_are_derived_from_configuration() {
620        let cell = AppCell::new();
621        let mut app = cell.borrow_mut();
622        let presented = Rc::new(Cell::new(0));
623        let view = RenderView::new(&mut app, None, None, test_view(&presented));
624        assert!(!view.has_configuration(&app));
625        let constraints = BoxConstraints::new()
626            .min_width(1.0)
627            .max_width(2.0)
628            .min_height(3.0)
629            .max_height(4.0);
630        view.set_configuration(
631            &mut app,
632            ViewConfiguration::new(constraints * 3.0, constraints, 3.0),
633        );
634        assert_eq!(view.constraints(&app), constraints);
635    }
636
637    /// `view_test.dart`: `Config can be set and changed after instantiation without calling
638    /// prepareInitialFrame first`.
639    #[test]
640    fn configuration_can_change_before_prepare_initial_frame() {
641        let cell = AppCell::new();
642        let mut app = cell.borrow_mut();
643        let presented = Rc::new(Cell::new(0));
644        let view = RenderView::new(&mut app, None, None, test_view(&presented));
645        view.set_configuration(
646            &mut app,
647            create_view_configuration(Size::new(100.0, 200.0), 3.0),
648        );
649        view.set_configuration(
650            &mut app,
651            create_view_configuration(Size::new(200.0, 300.0), 2.0),
652        );
653        let owner = PipelineOwner::new(&mut app, None, None);
654        owner.set_root_node(&mut app, Some(view.as_object()));
655        view.prepare_initial_frame(&mut app);
656    }
657
658    /// `view_test.dart`: `does not replace the root layer unnecessarily` (and when the view
659    /// resizes). Ours observes the replacement through the paint mark.
660    #[test]
661    fn does_not_replace_the_root_layer_unnecessarily() {
662        let cell = AppCell::new();
663        let mut app = cell.borrow_mut();
664        let presented = Rc::new(Cell::new(0));
665        let view = RenderView::new(
666            &mut app,
667            None,
668            Some(create_view_configuration(Size::new(100.0, 100.0), 1.0)),
669            test_view(&presented),
670        );
671        let owner = PipelineOwner::new(&mut app, None, None);
672        owner.set_root_node(&mut app, Some(view.as_object()));
673        view.prepare_initial_frame(&mut app);
674        owner.flush_layout(&mut app);
675        owner.flush_compositing_bits(&mut app);
676        owner.flush_paint(&mut app);
677        assert!(!view.as_object().debug_needs_paint(&app));
678
679        view.set_configuration(
680            &mut app,
681            create_view_configuration(Size::new(100.0, 1117.0), 1.0),
682        );
683        assert!(
684            !view.as_object().debug_needs_paint(&app),
685            "a resize keeps the root layer"
686        );
687
688        view.set_configuration(
689            &mut app,
690            create_view_configuration(Size::new(100.0, 1117.0), 5.0),
691        );
692        assert!(
693            view.as_object().debug_needs_paint(&app),
694            "a new ratio replaces it"
695        );
696        let layer = view.as_object().debug_layer(&app).expect("root layer");
697        let transform = app
698            .handle::<TransformLayer>(layer.id())
699            .expect("the root is a TransformLayer");
700        assert_eq!(transform.transform(&app), Some(Matrix4::scale(5.0, 5.0)));
701    }
702
703    /// `view_test.dart`: `accounts for device pixel ratio in paintBounds`, plus one frame
704    /// composited to the host.
705    #[test]
706    fn accounts_for_device_pixel_ratio_in_paint_bounds_and_presents() {
707        let cell = AppCell::new();
708        let mut app = cell.borrow_mut();
709        let presented = Rc::new(Cell::new(0));
710        let child =
711            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
712        let view = RenderView::new(
713            &mut app,
714            Some(child.as_box()),
715            Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
716            test_view(&presented),
717        );
718        let owner = PipelineOwner::new(&mut app, None, None);
719        owner.set_root_node(&mut app, Some(view.as_object()));
720        view.prepare_initial_frame(&mut app);
721        owner.flush_layout(&mut app);
722        owner.flush_compositing_bits(&mut app);
723        owner.flush_paint(&mut app);
724        view.composite_frame(&mut app);
725
726        assert_eq!(view.size(&app), Size::new(800.0, 600.0));
727        assert_eq!(
728            view.paint_bounds(&app),
729            Offset::ZERO & Size::new(1600.0, 1200.0)
730        );
731        assert_eq!(presented.get(), 1);
732    }
733
734    #[test]
735    fn coordinates_convert_through_the_paint_transforms_below_the_view() {
736        let cell = AppCell::new();
737        let mut app = cell.borrow_mut();
738        let presented = Rc::new(Cell::new(0));
739        let child =
740            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
741        let padding = RenderPadding::new(
742            &mut app,
743            EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
744            None,
745            Some(child.as_box()),
746        );
747        let view = RenderView::new(
748            &mut app,
749            Some(padding.as_box()),
750            Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
751            test_view(&presented),
752        );
753        let owner = PipelineOwner::new(&mut app, None, None);
754        owner.set_root_node(&mut app, Some(view.as_object()));
755        view.prepare_initial_frame(&mut app);
756        owner.flush_layout(&mut app);
757
758        // The root view's own transform (logical to physical) is left out, as in Dart.
759        let child = child.as_box();
760        assert_eq!(
761            child.local_to_global(&app, Offset::ZERO, None),
762            Offset::new(10.0, 20.0)
763        );
764        assert_eq!(
765            child.global_to_local(&app, Offset::new(15.0, 25.0), None),
766            Offset::new(5.0, 5.0)
767        );
768        assert_eq!(
769            child.local_to_global(&app, Offset::new(1.0, 1.0), Some(padding.as_object())),
770            Offset::new(11.0, 21.0)
771        );
772        assert_eq!(
773            padding.as_box().global_to_local(
774                &app,
775                Offset::new(10.0, 20.0),
776                Some(child.as_object())
777            ),
778            Offset::new(20.0, 40.0)
779        );
780        assert!(
781            child
782                .as_object()
783                .paint_bounds(&app)
784                .inflate(70.0)
785                .contains(child.global_to_local(&app, Offset::new(100.0, 100.0), None))
786        );
787    }
788
789    #[test]
790    fn a_proxy_box_adds_no_paint_transform() {
791        let cell = AppCell::new();
792        let mut app = cell.borrow_mut();
793        let presented = Rc::new(Cell::new(0));
794        let child =
795            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
796        let proxy =
797            RenderConstrainedBox::new(&mut app, BoxConstraints::new(), Some(child.as_box()));
798        let padding = RenderPadding::new(
799            &mut app,
800            EdgeInsetsGeometry::from_ltrb(10.0, 20.0, 0.0, 0.0),
801            None,
802            Some(proxy.as_box()),
803        );
804        let view = RenderView::new(
805            &mut app,
806            Some(padding.as_box()),
807            Some(create_view_configuration(Size::new(800.0, 600.0), 2.0)),
808            test_view(&presented),
809        );
810        let owner = PipelineOwner::new(&mut app, None, None);
811        owner.set_root_node(&mut app, Some(view.as_object()));
812        view.prepare_initial_frame(&mut app);
813        owner.flush_layout(&mut app);
814
815        assert_eq!(
816            child.as_box().local_to_global(&app, Offset::ZERO, None),
817            Offset::new(10.0, 20.0)
818        );
819        assert_eq!(
820            child
821                .as_box()
822                .global_to_local(&app, Offset::new(15.0, 25.0), None),
823            Offset::new(5.0, 5.0)
824        );
825    }
826}