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