Skip to main content

inset_rendering/
binding.rs

1//! Flutter counterpart: `rendering/binding.dart` (`RendererBinding`).
2//!
3//! Semantics and `performReassemble` wait.
4
5use std::rc::Rc;
6
7use inset_embedder::{Offset, ViewId};
8use inset_foundation::{App, Handle, Listener};
9use inset_gestures::{GestureBinding, GestureBindingOverridesObject, HitTestResult, PointerEvent};
10use inset_scheduler::{FrameCallback, SchedulerBinding};
11
12use crate::mouse_tracker::MouseTracker;
13use crate::object::RenderHandle;
14use crate::pipeline_owner::{PipelineManifold, PipelineOwner};
15use crate::view::{RenderView, ViewConfiguration};
16
17/// The object side of Flutter's `WidgetsBinding` overrides of `RendererBinding.drawFrame`:
18/// the widget layer builds before the renderer's frame and finalizes its tree after it.
19/// Implement it on the binding above and register `Rc::new(handle)` with
20/// [`RendererBinding::set_overrides`]; `Handle<T>` is then [`RendererBindingOverrides`].
21pub trait RendererBindingOverridesObject: Sized + 'static {
22    /// Runs at the start of [`RendererBinding::draw_frame`], before layout.
23    fn will_draw_frame(self: Handle<Self>, app: &mut App);
24
25    /// Runs at the end of [`RendererBinding::draw_frame`], after compositing.
26    fn did_draw_frame(self: Handle<Self>, app: &mut App);
27
28    /// Runs at the end of [`RendererBinding::handle_metrics_changed`]: Flutter's
29    /// `WidgetsBinding.handleMetricsChanged` override, which tells its observers.
30    fn did_handle_metrics_changed(self: Handle<Self>, app: &mut App) {
31        let _ = app;
32    }
33}
34
35/// What the binding above this one adds around `drawFrame`; kept erased.
36pub trait RendererBindingOverrides {
37    /// See [`RendererBindingOverridesObject::will_draw_frame`].
38    fn will_draw_frame(&self, app: &mut App);
39
40    /// See [`RendererBindingOverridesObject::did_draw_frame`].
41    fn did_draw_frame(&self, app: &mut App);
42
43    /// See [`RendererBindingOverridesObject::did_handle_metrics_changed`].
44    fn did_handle_metrics_changed(&self, app: &mut App);
45}
46
47impl<T: RendererBindingOverridesObject> RendererBindingOverrides for Handle<T> {
48    fn will_draw_frame(&self, app: &mut App) {
49        T::will_draw_frame(*self, app);
50    }
51
52    fn did_draw_frame(&self, app: &mut App) {
53        T::did_draw_frame(*self, app);
54    }
55
56    fn did_handle_metrics_changed(&self, app: &mut App) {
57        T::did_handle_metrics_changed(*self, app);
58    }
59}
60
61/// Dart's `_BindingPipelineManifold`: the [`PipelineManifold`] the binding attaches its
62/// root [`PipelineOwner`] to. Its `semanticsEnabled` waits with semantics.
63struct BindingPipelineManifold;
64
65impl PipelineManifold for BindingPipelineManifold {
66    fn request_visual_update(&self, app: &mut App) {
67        SchedulerBinding::ensure_visual_update(app);
68    }
69}
70
71#[derive(Default)]
72pub struct RendererBinding {
73    overrides: Option<Rc<dyn RendererBindingOverrides>>,
74    root_pipeline_owner: Option<Handle<PipelineOwner>>,
75    /// Flutter's deprecated `pipelineOwner`, created on first use.
76    pipeline_owner: Option<Handle<PipelineOwner>>,
77    /// Flutter's deprecated `renderView` for the implicit view, created on first use.
78    render_view: Option<RenderHandle<RenderView>>,
79    render_view_initial_frame_prepared: bool,
80    render_views: Vec<(ViewId, RenderHandle<RenderView>)>,
81    mouse_tracker: Option<Handle<MouseTracker>>,
82    debug_mouse_tracker_update_scheduled: bool,
83    first_frame_deferred_count: u32,
84    first_frame_sent: bool,
85}
86
87impl RendererBinding {
88    /// The current binding, initialized on first access (Flutter's `initInstances`).
89    pub fn instance(app: &mut App) -> Handle<RendererBinding> {
90        let this: Handle<RendererBinding> = app.singleton();
91        if app.get(this).root_pipeline_owner.is_none() {
92            let root_pipeline_owner = this.create_root_pipeline_owner(app);
93            app.get_mut(this).root_pipeline_owner = Some(root_pipeline_owner);
94            root_pipeline_owner.attach(app, Rc::new(BindingPipelineManifold));
95            SchedulerBinding::add_persistent_frame_callback(
96                app,
97                FrameCallback::new(|app, _time_stamp| {
98                    RendererBinding::instance(app).handle_persistent_frame_callback(app)
99                }),
100            );
101            this.init_mouse_tracker(app, None);
102            // Flutter's `RendererBinding` overrides `GestureBinding.hitTestInView` and
103            // `dispatchEvent`.
104            GestureBinding::instance(app).set_overrides(app, Rc::new(this));
105        }
106        this
107    }
108
109    /// The object that manages state about currently connected mice, for hover
110    /// notification.
111    pub fn mouse_tracker(self: Handle<Self>, app: &App) -> Handle<MouseTracker> {
112        app.get(self)
113            .mouse_tracker
114            .expect("RendererBinding::instance must run first")
115    }
116
117    /// Creates a [`MouseTracker`] which manages state about currently connected
118    /// mice, for hover notification.
119    ///
120    /// Used by testing framework to reinitialize the mouse tracker between tests.
121    pub fn init_mouse_tracker(
122        self: Handle<Self>,
123        app: &mut App,
124        tracker: Option<Handle<MouseTracker>>,
125    ) {
126        if let Some(previous) = app.get(self).mouse_tracker {
127            previous.dispose(app);
128        }
129        let tracker = tracker.unwrap_or_else(|| {
130            MouseTracker::new(
131                app,
132                Rc::new(move |app: &mut App, position: Offset, view_id: ViewId| {
133                    let mut result = HitTestResult::new();
134                    self.hit_test_in_view(app, &mut result, position, view_id);
135                    result
136                }),
137            )
138        });
139        app.get_mut(self).mouse_tracker = Some(tracker);
140    }
141
142    /// The [`PipelineOwner`] that is the root of the pipeline owner tree.
143    ///
144    /// Typically, this owner has no root render object (a `RenderView` is attached to the
145    /// owners the widget layer adds as children). Until then, [`init_render_view`](Self::init_render_view)
146    /// attaches the implicit view here.
147    pub fn root_pipeline_owner(self: Handle<Self>, app: &App) -> Handle<PipelineOwner> {
148        app.get(self)
149            .root_pipeline_owner
150            .expect("RendererBinding::instance must run first")
151    }
152
153    /// Creates the [`PipelineOwner`] that serves as the root of the pipeline owner tree.
154    fn create_root_pipeline_owner(self: Handle<Self>, app: &mut App) -> Handle<PipelineOwner> {
155        PipelineOwner::new(
156            app,
157            Some(Listener::new(|app| {
158                SchedulerBinding::ensure_visual_update(app);
159            })),
160        )
161    }
162
163    /// The [`PipelineOwner`] Flutter's binding keeps for the implicit view.
164    pub fn pipeline_owner(self: Handle<Self>, app: &mut App) -> Handle<PipelineOwner> {
165        if let Some(owner) = app.get(self).pipeline_owner {
166            return owner;
167        }
168        let owner = PipelineOwner::new(app, None);
169        app.get_mut(self).pipeline_owner = Some(owner);
170        owner
171    }
172
173    /// The [`RenderView`] for the platform's implicit view, created on first access.
174    ///
175    /// # Panics
176    ///
177    /// If the platform has no implicit view.
178    pub fn render_view(self: Handle<Self>, app: &mut App) -> RenderHandle<RenderView> {
179        if let Some(view) = app.get(self).render_view {
180            return view;
181        }
182        let implicit_view = app
183            .platform()
184            .implicit_view()
185            .expect("the platform has no implicit view");
186        let view = RenderView::new(app, None, None, implicit_view);
187        app.get_mut(self).render_view = Some(view);
188        view
189    }
190
191    /// The [`RenderView`]s managed by this binding.
192    pub fn render_views(self: Handle<Self>, app: &App) -> Vec<RenderHandle<RenderView>> {
193        app.get(self)
194            .render_views
195            .iter()
196            .map(|(_, view)| *view)
197            .collect()
198    }
199
200    /// Adds a [`RenderView`] to this binding.
201    ///
202    /// The binding will interact with the [`RenderView`] in the following ways:
203    ///
204    /// - setting and updating [`RenderView::configuration`],
205    /// - calling [`RenderView::composite_frame`] when it is time to produce a new frame.
206    pub fn add_render_view(self: Handle<Self>, app: &mut App, view: RenderHandle<RenderView>) {
207        let view_id = view.flutter_view(app).id();
208        debug_assert!(
209            !app.get(self)
210                .render_views
211                .iter()
212                .any(|(id, existing)| *id == view_id || *existing == view)
213        );
214        app.get_mut(self).render_views.push((view_id, view));
215        let configuration = self.create_view_configuration_for(app, view);
216        view.set_configuration(app, configuration);
217    }
218
219    /// Removes a [`RenderView`] previously added with [`add_render_view`](Self::add_render_view).
220    pub fn remove_render_view(self: Handle<Self>, app: &mut App, view: RenderHandle<RenderView>) {
221        let view_id = view.flutter_view(app).id();
222        let views = &mut app.get_mut(self).render_views;
223        debug_assert!(
224            views
225                .iter()
226                .any(|(id, existing)| *id == view_id && *existing == view)
227        );
228        views.retain(|(id, _)| *id != view_id);
229    }
230
231    /// Returns a [`ViewConfiguration`] configured for the provided [`RenderView`] based on
232    /// the current environment.
233    pub fn create_view_configuration_for(
234        self: Handle<Self>,
235        app: &App,
236        render_view: RenderHandle<RenderView>,
237    ) -> ViewConfiguration {
238        ViewConfiguration::from_view(&*render_view.flutter_view(app))
239    }
240
241    /// Called when the system metrics change.
242    ///
243    /// See `View::metrics`.
244    pub fn handle_metrics_changed(self: Handle<Self>, app: &mut App) {
245        let mut force_frame = false;
246        for view in self.render_views(app) {
247            force_frame = force_frame || view.child(app).is_some();
248            let configuration = self.create_view_configuration_for(app, view);
249            view.set_configuration(app, configuration);
250        }
251        if force_frame {
252            SchedulerBinding::schedule_forced_frame(app);
253        }
254        if let Some(overrides) = app.get(self).overrides.clone() {
255            overrides.did_handle_metrics_changed(app);
256        }
257    }
258
259    fn handle_persistent_frame_callback(self: Handle<Self>, app: &mut App) {
260        self.draw_frame(app);
261        self.schedule_mouse_tracker_update(app);
262    }
263
264    fn schedule_mouse_tracker_update(self: Handle<Self>, app: &mut App) {
265        debug_assert!(!app.get(self).debug_mouse_tracker_update_scheduled);
266        if cfg!(debug_assertions) {
267            app.get_mut(self).debug_mouse_tracker_update_scheduled = true;
268        }
269        SchedulerBinding::add_post_frame_callback(
270            app,
271            FrameCallback::new(move |app, _duration| {
272                debug_assert!(app.get(self).debug_mouse_tracker_update_scheduled);
273                if cfg!(debug_assertions) {
274                    app.get_mut(self).debug_mouse_tracker_update_scheduled = false;
275                }
276                self.mouse_tracker(app).update_all_devices(app);
277            }),
278        );
279    }
280
281    /// Whether frames produced by [`draw_frame`](Self::draw_frame) are sent to the host.
282    ///
283    /// If false the framework will do all the work to produce a frame, but the frame is never
284    /// sent to the host to actually appear on screen.
285    pub fn send_frames_to_engine(self: Handle<Self>, app: &App) -> bool {
286        let data = app.get(self);
287        data.first_frame_sent || data.first_frame_deferred_count == 0
288    }
289
290    /// Tell the framework to not send the first frames to the host until there is a
291    /// corresponding call to [`allow_first_frame`](Self::allow_first_frame).
292    pub fn defer_first_frame(self: Handle<Self>, app: &mut App) {
293        app.get_mut(self).first_frame_deferred_count += 1;
294    }
295
296    /// Called after [`defer_first_frame`](Self::defer_first_frame) to tell the framework that it
297    /// is ok to send the first frame to the host now.
298    pub fn allow_first_frame(self: Handle<Self>, app: &mut App) {
299        debug_assert!(app.get(self).first_frame_deferred_count > 0);
300        app.get_mut(self).first_frame_deferred_count -= 1;
301        if !app.get(self).first_frame_sent {
302            SchedulerBinding::schedule_forced_frame(app);
303        }
304    }
305
306    /// Call this to pretend that no frames have been sent to the host yet.
307    pub fn reset_first_frame_sent(self: Handle<Self>, app: &mut App) {
308        app.get_mut(self).first_frame_sent = false;
309    }
310
311    /// Pump the rendering pipeline to generate a frame.
312    ///
313    /// This method is called by `handle_draw_frame`, which itself is called automatically by
314    /// the host when it is time to lay out and paint a frame.
315    ///
316    /// Each frame consists of the following phases: layout, paint, then compositing (for each
317    /// registered [`RenderView`]).
318    /// Registers the binding layered above this one, as Flutter's `WidgetsBinding` does by
319    /// overriding `drawFrame`.
320    pub fn set_overrides(
321        self: Handle<Self>,
322        app: &mut App,
323        overrides: Rc<dyn RendererBindingOverrides>,
324    ) {
325        app.get_mut(self).overrides = Some(overrides);
326    }
327
328    pub fn draw_frame(self: Handle<Self>, app: &mut App) {
329        let overrides = app.get(self).overrides.clone();
330        if let Some(overrides) = &overrides {
331            overrides.will_draw_frame(app);
332        }
333        let root_pipeline_owner = self.root_pipeline_owner(app);
334        root_pipeline_owner.flush_layout(app);
335        root_pipeline_owner.flush_compositing_bits(app);
336        root_pipeline_owner.flush_paint(app);
337        if self.send_frames_to_engine(app) {
338            for render_view in self.render_views(app) {
339                render_view.composite_frame(app); // this sends the bits to the GPU
340            }
341            app.get_mut(self).first_frame_sent = true;
342        }
343        if let Some(overrides) = &overrides {
344            overrides.did_draw_frame(app);
345        }
346    }
347
348    /// Flutter's `TestRenderingFlutterBinding.initRenderView`: makes the implicit view's
349    /// [`RenderView`] the root of [`root_pipeline_owner`](Self::root_pipeline_owner), registers
350    /// it, and prepares its first frame. Idempotent, as `_ReusableRenderView` is.
351    pub fn init_render_view(self: Handle<Self>, app: &mut App) -> RenderHandle<RenderView> {
352        let render_view = self.render_view(app);
353        if app.get(self).render_view_initial_frame_prepared {
354            return render_view;
355        }
356        self.root_pipeline_owner(app)
357            .set_root_node(app, Some(render_view.as_object()));
358        self.add_render_view(app, render_view);
359        render_view.prepare_initial_frame(app);
360        app.get_mut(self).render_view_initial_frame_prepared = true;
361        render_view
362    }
363}
364
365/// Flutter's `RendererBinding.hitTestInView` override: the render view for `view_id` hit-tests
366/// first; `GestureBinding` then adds itself.
367/// Flutter's `RendererBinding` overrides of `GestureBinding`: `hitTestInView` walks the render
368/// trees, `dispatchEvent` updates the mouse tracker before routing.
369impl GestureBindingOverridesObject for RendererBinding {
370    fn hit_test(self: Handle<Self>, app: &mut App, result: &mut HitTestResult, position: Offset) {
371        let Some(view) = app.platform().implicit_view() else {
372            return;
373        };
374        self.hit_test_in_view(app, result, position, view.id());
375    }
376
377    fn hit_test_in_view(
378        self: Handle<Self>,
379        app: &mut App,
380        result: &mut HitTestResult,
381        position: Offset,
382        view_id: ViewId,
383    ) {
384        let render_view = app
385            .get(self)
386            .render_views
387            .iter()
388            .find(|(id, _)| *id == view_id)
389            .map(|(_, view)| *view);
390        if let Some(render_view) = render_view {
391            render_view.hit_test(app, result, position);
392        }
393    }
394
395    fn will_dispatch_event(
396        self: Handle<Self>,
397        app: &mut App,
398        event: &PointerEvent,
399        hit_test_result: Option<&HitTestResult>,
400    ) {
401        // When the button is pressed, normal hit test uses a cached
402        // result, but MouseTracker requires that the hit test is re-executed to
403        // update the hovering events.
404        let result = match event {
405            PointerEvent::Move(_) => None,
406            _ => hit_test_result,
407        };
408        self.mouse_tracker(app)
409            .update_with_event(app, event, result);
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use inset_foundation::AppCell;
416    use std::time::Duration;
417
418    use inset_embedder::{Size, TargetPlatform};
419    use inset_test::{TestPlatform, TestView};
420
421    use super::*;
422    use crate::box_::{BoxConstraints, RenderBox};
423    use crate::proxy_box::RenderConstrainedBox;
424
425    /// An [`App`] over one 800x600 view at 2x, with the view and the platform to read back.
426    fn app_with_view() -> (Rc<AppCell>, Rc<TestView>, Rc<TestPlatform>) {
427        let view = Rc::new(TestView::with_pixel_ratio(800.0, 600.0, 2.0));
428        let platform = Rc::new(
429            TestPlatform::new()
430                .on(TargetPlatform::MacOS)
431                .with_view(view.clone()),
432        );
433        (AppCell::with_platform(platform.clone()), view, platform)
434    }
435
436    #[test]
437    fn a_frame_lays_out_paints_and_presents_the_implicit_view() {
438        let (cell, view, _platform) = app_with_view();
439        let mut app = cell.borrow_mut();
440        let binding = RendererBinding::instance(&mut app);
441        let render_view = binding.init_render_view(&mut app);
442        assert_eq!(
443            render_view.configuration(&app).logical_constraints,
444            BoxConstraints::tight(Size::new(400.0, 300.0))
445        );
446        let child =
447            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
448        render_view.set_child(&mut app, Some(child.as_box()));
449
450        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
451        SchedulerBinding::handle_draw_frame(&mut app);
452
453        assert_eq!(render_view.size(&app), Size::new(400.0, 300.0));
454        assert_eq!(child.size(&app), Size::new(400.0, 300.0));
455        assert_eq!(view.presented(), 1);
456        assert!(!child.debug_needs_layout(&app));
457        assert!(!child.as_object().debug_needs_paint(&app));
458    }
459
460    #[test]
461    fn a_dirty_node_requests_a_visual_update() {
462        let (cell, _view, platform) = app_with_view();
463        let mut app = cell.borrow_mut();
464        let binding = RendererBinding::instance(&mut app);
465        let render_view = binding.init_render_view(&mut app);
466        let child =
467            RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(10.0, 10.0)), None);
468        render_view.set_child(&mut app, Some(child.as_box()));
469        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
470        SchedulerBinding::handle_draw_frame(&mut app);
471        let before = platform.frames_requested();
472
473        child.mark_needs_layout(&mut app);
474        assert!(
475            platform.frames_requested() > before,
476            "the root owner's visual update reaches the host"
477        );
478    }
479
480    #[test]
481    fn a_pointer_down_reaches_a_pointer_listener_through_the_gesture_binding() {
482        use inset_gestures::{PointerDownEvent, PointerEvent};
483
484        use crate::proxy_box::{HitTestBehavior, RenderPointerListener};
485
486        let (cell, _view, _platform) = app_with_view();
487        let mut app = cell.borrow_mut();
488        let binding = RendererBinding::instance(&mut app);
489        let render_view = binding.init_render_view(&mut app);
490        let listener = RenderPointerListener::new(&mut app, HitTestBehavior::Opaque, None);
491        let positions = Rc::new(std::cell::RefCell::new(Vec::new()));
492        let recorded = Rc::clone(&positions);
493        listener.set_on_pointer_down(
494            &mut app,
495            Some(Rc::new(move |_app: &mut App, event: PointerDownEvent| {
496                recorded.borrow_mut().push(event.local_position());
497            })),
498        );
499        render_view.set_child(&mut app, Some(listener.as_box()));
500        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
501        SchedulerBinding::handle_draw_frame(&mut app);
502
503        GestureBinding::instance(&mut app).handle_pointer_event(
504            &mut app,
505            PointerEvent::Down(PointerDownEvent {
506                position: Offset::new(30.0, 40.0),
507                ..PointerDownEvent::default()
508            }),
509        );
510        assert_eq!(*positions.borrow(), [Offset::new(30.0, 40.0)]);
511    }
512
513    #[test]
514    fn a_mouse_hover_enters_a_mouse_region_and_a_frame_notices_it_moving_away() {
515        use inset_embedder::PointerDeviceKind;
516        use inset_gestures::{
517            PointerAddedEvent, PointerEnterEvent, PointerEvent, PointerExitEvent,
518        };
519        use inset_painting::EdgeInsetsGeometry;
520
521        use crate::proxy_box::RenderMouseRegion;
522        use crate::shifted_box::RenderPadding;
523
524        let (cell, _view, _platform) = app_with_view();
525        let mut app = cell.borrow_mut();
526        let binding = RendererBinding::instance(&mut app);
527        let render_view = binding.init_render_view(&mut app);
528        let region = RenderMouseRegion::new(&mut app, true, None);
529        let log = Rc::new(std::cell::RefCell::new(Vec::new()));
530        let entered = Rc::clone(&log);
531        region.set_on_enter(
532            &mut app,
533            Some(Rc::new(move |_app: &mut App, event: PointerEnterEvent| {
534                entered
535                    .borrow_mut()
536                    .push(format!("enter {:?}", event.local_position()));
537            })),
538        );
539        let exited = Rc::clone(&log);
540        region.set_on_exit(
541            &mut app,
542            Some(Rc::new(move |_app: &mut App, event: PointerExitEvent| {
543                exited
544                    .borrow_mut()
545                    .push(format!("exit {:?}", event.local_position()));
546            })),
547        );
548        let padding = RenderPadding::new(
549            &mut app,
550            EdgeInsetsGeometry::all(100.0),
551            None,
552            Some(region.as_box()),
553        );
554        render_view.set_child(&mut app, Some(padding.as_box()));
555        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
556        SchedulerBinding::handle_draw_frame(&mut app);
557
558        GestureBinding::instance(&mut app).handle_pointer_event(
559            &mut app,
560            PointerEvent::Added(PointerAddedEvent {
561                kind: PointerDeviceKind::Mouse,
562                position: Offset::new(150.0, 150.0),
563                ..PointerAddedEvent::default()
564            }),
565        );
566        assert_eq!(*log.borrow(), ["enter Offset(50.0, 50.0)"]);
567        assert!(binding.mouse_tracker(&app).mouse_is_connected(&app));
568
569        // The tree moves under the still mouse; the post-frame update sends the exit.
570        padding.set_padding(&mut app, EdgeInsetsGeometry::only(300.0, 0.0, 0.0, 0.0));
571        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::from_millis(16)));
572        SchedulerBinding::handle_draw_frame(&mut app);
573        assert_eq!(
574            *log.borrow(),
575            ["enter Offset(50.0, 50.0)", "exit Offset(50.0, 50.0)"],
576            "the exit is placed with the transform recorded when the region was entered"
577        );
578    }
579
580    #[test]
581    fn deferred_first_frame_is_not_presented() {
582        let (cell, view, _platform) = app_with_view();
583        let mut app = cell.borrow_mut();
584        let binding = RendererBinding::instance(&mut app);
585        binding.init_render_view(&mut app);
586        binding.defer_first_frame(&mut app);
587        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
588        SchedulerBinding::handle_draw_frame(&mut app);
589        assert_eq!(view.presented(), 0);
590
591        binding.allow_first_frame(&mut app);
592        SchedulerBinding::handle_begin_frame(&mut app, Some(Duration::ZERO));
593        SchedulerBinding::handle_draw_frame(&mut app);
594        assert_eq!(view.presented(), 1);
595    }
596}