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