Skip to main content

inset_rendering/
composition_layer_view.rs

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