Skip to main content

inset_rendering/
surface_view.rs

1//! A box that shows a native surface the host composites: Flutter's platform view render
2//! object, over the `Surface` a producer feeds.
3//!
4//! It paints nothing of its own: it adds a [`SurfaceLayer`] at its place in paint order, and
5//! whoever composites the scene makes room for the surface there, so what the framework paints
6//! above the surface stays and what it painted beneath is gone. It fills what it is given: the
7//! surface's producer is told the size by whoever built this widget, not by the compositor. It
8//! always needs compositing, as Flutter's platform views do, so a transform, a clip or an opacity
9//! above it is a layer the surface is placed under rather than a change to the canvas alone. The
10//! invariant: the layer added is the box, at the offset it is painted at.
11
12use std::sync::Arc;
13
14use inset_embedder::{Offset, Size, Surface};
15use inset_foundation::App;
16
17use crate::box_::{BoxConstraints, RenderBox, RenderBoxData};
18use crate::layer::{ErasedLayer, SurfaceLayer};
19use crate::object::{RenderHandle, RenderObject, RenderObjectData};
20use crate::painting_context::PaintingContext;
21
22/// Shows a native surface in the box it is given.
23pub struct RenderSurfaceView {
24    render_object: RenderObjectData,
25    render_box: RenderBoxData,
26    surface: Arc<dyn Surface>,
27}
28
29impl RenderSurfaceView {
30    pub fn new(app: &mut App, surface: Arc<dyn Surface>) -> RenderHandle<RenderSurfaceView> {
31        RenderHandle::new_box(
32            app,
33            RenderSurfaceView {
34                render_object: RenderObjectData::new(),
35                render_box: RenderBoxData::new(),
36                surface,
37            },
38        )
39    }
40
41    /// The surface shown.
42    pub fn surface(self: RenderHandle<Self>, app: &App) -> &Arc<dyn Surface> {
43        &self.get(app).surface
44    }
45
46    /// Shows `surface` instead, from the next paint.
47    pub fn set_surface(self: RenderHandle<Self>, app: &mut App, surface: Arc<dyn Surface>) {
48        if Arc::ptr_eq(&self.get(app).surface, &surface) {
49            return;
50        }
51        self.get_mut(app).surface = surface;
52        self.mark_needs_paint(app);
53    }
54}
55
56impl RenderObject for RenderSurfaceView {
57    crate::render_object_accessors!();
58
59    fn always_needs_compositing(self: RenderHandle<Self>, _app: &App) -> bool {
60        true
61    }
62
63    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
64        let size = self.constraints(app).biggest();
65        self.set_size(app, size);
66    }
67
68    /// The surface placed at the box: the layer splits the picture there, so what is painted
69    /// after it lies above the surface.
70    fn paint(
71        self: RenderHandle<Self>,
72        app: &mut App,
73        context: &mut PaintingContext,
74        offset: Offset,
75    ) {
76        let surface = Arc::clone(&self.get(app).surface);
77        let layer = SurfaceLayer::new(app, surface, offset & self.size(app));
78        context.add_layer(app, layer.as_layer());
79    }
80}
81
82impl RenderBox for RenderSurfaceView {
83    crate::render_box_accessors!();
84
85    fn compute_dry_layout(
86        self: RenderHandle<Self>,
87        _app: &mut App,
88        constraints: BoxConstraints,
89    ) -> Size {
90        constraints.biggest()
91    }
92
93    /// A surface takes a hit anywhere in its box, so a listener around it hears the pointer.
94    fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
95        true
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use inset_embedder::test_support::idle_surface;
102    use inset_embedder::{Clip, Matrix4, Mutator, Part, Rect, SceneBuilder};
103    use inset_foundation::{AppCell, Handle};
104
105    use super::*;
106    use crate::layer::{ContainerLayer, OffsetLayer};
107    use crate::object::AnyRenderObject;
108    use crate::pipeline_owner::PipelineOwner;
109
110    /// Where a [`Parent`] paints its child.
111    const CHILD_OFFSET: Offset = Offset::new(10.0, 5.0);
112
113    /// A parent that lays its one child out at a fixed size and paints it as `painting` says.
114    struct Parent {
115        render_object: RenderObjectData,
116        render_box: RenderBoxData,
117        child: RenderHandle<RenderSurfaceView>,
118        painting: ChildPainting,
119    }
120
121    /// How a [`Parent`] paints its child.
122    #[derive(Clone, Copy)]
123    enum ChildPainting {
124        /// At [`CHILD_OFFSET`].
125        AtOffset,
126        /// At [`CHILD_OFFSET`], at half its size, clipped to the parent's top ten pixels.
127        HalvedUnderAClip,
128    }
129
130    impl RenderObject for Parent {
131        crate::render_object_accessors!();
132
133        /// The root of a painted tree owns the root layer.
134        fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
135            true
136        }
137
138        fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
139            let child = self.get(app).child;
140            child.layout(app, BoxConstraints::tight(Size::new(30.0, 20.0)), false);
141            self.set_size(app, Size::new(100.0, 100.0));
142        }
143
144        fn visit_children(
145            self: RenderHandle<Self>,
146            app: &App,
147            visitor: &mut dyn FnMut(AnyRenderObject),
148        ) {
149            visitor(self.get(app).child.as_object());
150        }
151
152        fn paint(
153            self: RenderHandle<Self>,
154            app: &mut App,
155            context: &mut PaintingContext,
156            offset: Offset,
157        ) {
158            let child = self.get(app).child.as_object();
159            match self.get(app).painting {
160                ChildPainting::AtOffset => context.paint_child(app, child, offset + CHILD_OFFSET),
161                ChildPainting::HalvedUnderAClip => {
162                    let needs_compositing = self.as_object().needs_compositing(app);
163                    context.push_clip_rect(
164                        app,
165                        needs_compositing,
166                        offset,
167                        Rect::from_ltwh(0.0, 0.0, 100.0, 10.0),
168                        |app, context, offset| {
169                            context.push_transform(
170                                app,
171                                needs_compositing,
172                                offset + CHILD_OFFSET,
173                                Matrix4::scale(0.5, 0.5),
174                                |app, context, offset| context.paint_child(app, child, offset),
175                                None,
176                            );
177                        },
178                        Clip::HardEdge,
179                        None,
180                    );
181                }
182            }
183        }
184    }
185
186    impl RenderBox for Parent {
187        crate::render_box_accessors!();
188    }
189
190    /// Lays out and paints a view of `surface` under a parent that paints it as `painting`
191    /// says, and answers the scene.
192    fn scene_of_one_frame(
193        app: &mut App,
194        surface: Arc<dyn Surface>,
195        painting: ChildPainting,
196    ) -> (inset_embedder::Scene, Handle<PipelineOwner>) {
197        let owner = PipelineOwner::new(app, None, None);
198        let child = RenderSurfaceView::new(app, surface);
199        let parent = RenderHandle::new_box(
200            app,
201            Parent {
202                render_object: RenderObjectData::new(),
203                render_box: RenderBoxData::new(),
204                child,
205                painting,
206            },
207        );
208        parent.adopt_child(app, child.as_object());
209        owner.set_root_node(app, Some(parent.as_object()));
210        parent.schedule_initial_layout(app);
211        let root = OffsetLayer::new(app, Offset::ZERO);
212        root.as_layer().attach(app, parent.as_object().id());
213        parent
214            .as_object()
215            .schedule_initial_paint(app, root.as_container_layer());
216        owner.flush_layout(app);
217        owner.flush_compositing_bits(app);
218        owner.flush_paint(app);
219        let scene = root
220            .as_container_layer()
221            .build_scene(app, SceneBuilder::new());
222        (scene, owner)
223    }
224
225    #[test]
226    fn the_box_is_placed_and_nothing_is_painted() {
227        let cell = AppCell::new();
228        let mut app = cell.borrow_mut();
229        let surface = idle_surface();
230        let (scene, _owner) =
231            scene_of_one_frame(&mut app, Arc::clone(&surface), ChildPainting::AtOffset);
232        let slices = inset_embedder::slices(&scene);
233        let [Part::Surface(placement)] = slices.parts() else {
234            panic!("the surface alone: {:?}", slices.parts());
235        };
236        assert!(Arc::ptr_eq(&placement.surface, &surface));
237        let placed = Rect::from_ltwh(10.0, 5.0, 30.0, 20.0);
238        assert_eq!(placement.bounds(), placed);
239        assert_eq!(placement.visible_bounds(), placed);
240        assert!(!slices.paints());
241    }
242
243    /// A transform and a clip above a surface are layers of their own, as they are above
244    /// Flutter's platform views, so the scene places the surface under both.
245    #[test]
246    fn a_surface_is_placed_under_the_transform_and_the_clip_above_it() {
247        let cell = AppCell::new();
248        let mut app = cell.borrow_mut();
249        let (scene, _owner) =
250            scene_of_one_frame(&mut app, idle_surface(), ChildPainting::HalvedUnderAClip);
251        let placement = inset_embedder::slices(&scene)
252            .surfaces()
253            .next()
254            .cloned()
255            .expect("placed");
256        assert!(
257            placement
258                .mutators
259                .contains(&Mutator::ClipRect(Rect::from_ltwh(0.0, 0.0, 100.0, 10.0)))
260        );
261        assert_eq!(placement.scale(), 0.5);
262        assert_eq!(placement.bounds(), Rect::from_ltwh(10.0, 5.0, 15.0, 10.0));
263        assert_eq!(
264            placement.visible_bounds(),
265            Rect::from_ltrb(10.0, 5.0, 25.0, 10.0)
266        );
267    }
268}