Skip to main content

inset_embedder/scene/
builder.rs

1//! Flutter counterpart: dart:ui `SceneBuilder`, as the framework's layers call it from
2//! `add_to_scene`.
3//!
4//! It builds the engine's layer tree as `lib/ui/compositing/scene_builder.cc` does, with a stack
5//! of the containers being built: a push starts one inside the last, `pop` builds the last and
6//! adds it to the one beneath, and an add puts a leaf, or a retained container, in the last. It
7//! records nothing and computes nothing; what the containers do is replayed by whoever walks the
8//! tree. The invariant a later editor must keep: a node is built before it is shared, so `pop`
9//! hands back a container no later call changes.
10
11use std::fmt;
12use std::sync::Arc;
13
14use super::Scene;
15use super::layer::{
16    ContainerLayer, DisplayListLayer, Effect, EmbeddedLayer, EngineLayer, Layer, VideoLayer,
17};
18use crate::composition::CompositionLayer;
19use crate::geometry::{Clip, Matrix4, Offset, RRect, Rect};
20use crate::media::VideoFrameReader;
21use crate::painting::{BlendMode, ImageFilter, Path, Picture};
22
23/// Builds a [`Scene`] containing the given visuals.
24///
25/// A [`Scene`] can then be rendered using [`crate::View::present`].
26///
27/// To draw graphical operations onto a [`Scene`], first create a [`Canvas`](crate::Canvas),
28/// record into it, and add the resulting [`Picture`] with [`add_picture`](Self::add_picture).
29/// Each `push_*` starts a container that [`pop`](Self::pop) builds into an [`EngineLayer`];
30/// unbalanced pushes are a bug the builder asserts on.
31pub struct SceneBuilder {
32    /// The containers being built, the scene's root first, as the engine's `layer_stack_`:
33    /// every push starts one inside the last, and every pop builds the last into the one beneath.
34    /// `build` builds the root.
35    layer_stack: Vec<ContainerLayerBuilder>,
36}
37
38/// The [`ContainerLayer`] being built while its push is open: its effect, and the children added
39/// so far.
40struct ContainerLayerBuilder {
41    effect: Effect,
42    children: Vec<Layer>,
43}
44
45impl ContainerLayerBuilder {
46    fn new(effect: Effect) -> ContainerLayerBuilder {
47        ContainerLayerBuilder {
48            effect,
49            children: Vec::new(),
50        }
51    }
52
53    fn build(self) -> ContainerLayer {
54        ContainerLayer {
55            effect: self.effect,
56            children: self.children,
57        }
58    }
59}
60
61impl fmt::Debug for SceneBuilder {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        f.debug_struct("SceneBuilder")
64            .field("open_pushes", &(self.layer_stack.len() - 1))
65            .finish_non_exhaustive()
66    }
67}
68
69impl Default for SceneBuilder {
70    fn default() -> SceneBuilder {
71        SceneBuilder::new()
72    }
73}
74
75impl SceneBuilder {
76    /// Creates an empty [`SceneBuilder`] object.
77    pub fn new() -> SceneBuilder {
78        SceneBuilder {
79            layer_stack: vec![ContainerLayerBuilder::new(Effect::None)],
80        }
81    }
82
83    /// Pushes a transform operation onto the operation stack.
84    ///
85    /// The objects are transformed by the given matrix before rasterization.
86    pub fn push_transform(&mut self, matrix4: &Matrix4) {
87        self.push_layer(Effect::Transform(*matrix4));
88    }
89
90    /// Pushes an offset operation onto the operation stack.
91    ///
92    /// This is equivalent to [`push_transform`](Self::push_transform) with a matrix with only
93    /// translation.
94    pub fn push_offset(&mut self, dx: f64, dy: f64) {
95        self.push_layer(Effect::Transform(Matrix4::translation(
96            dx as f32, dy as f32,
97        )));
98    }
99
100    /// Pushes a rectangular clip operation onto the operation stack.
101    ///
102    /// Rasterization outside the given rectangle is discarded.
103    pub fn push_clip_rect(&mut self, rect: Rect, clip_behavior: Clip) {
104        self.push_layer(Effect::ClipRect {
105            rect,
106            clip_behavior,
107        });
108    }
109
110    /// Pushes a rounded-rectangular clip operation onto the operation stack.
111    ///
112    /// Rasterization outside the given rounded rectangle is discarded.
113    pub fn push_clip_rrect(&mut self, rrect: RRect, clip_behavior: Clip) {
114        self.push_layer(Effect::ClipRRect {
115            rrect,
116            clip_behavior,
117        });
118    }
119
120    /// Pushes a path clip operation onto the operation stack.
121    ///
122    /// Rasterization outside the given path is discarded.
123    pub fn push_clip_path(&mut self, path: &Arc<Path>, clip_behavior: Clip) {
124        self.push_layer(Effect::ClipPath {
125            path: Arc::clone(path),
126            clip_behavior,
127        });
128    }
129
130    /// Pushes an opacity operation onto the operation stack.
131    ///
132    /// The given alpha value is blended into the alpha value of the objects' rasterization. An
133    /// alpha value of 0 makes the objects entirely invisible. An alpha value of 255 has no
134    /// effect (i.e., the objects retain the current opacity).
135    pub fn push_opacity(&mut self, alpha: i32, offset: Offset) {
136        self.push_layer(Effect::Opacity {
137            alpha: alpha.clamp(0, 255) as u8,
138            offset,
139        });
140    }
141
142    /// Pushes a backdrop filter operation onto the operation stack.
143    ///
144    /// The given filter is applied to the current contents of the scene as far back as the most
145    /// recent save layer and rendered back to the scene using the indicated `blend_mode` prior to
146    /// rasterizing the child layers.
147    ///
148    /// Filters sharing a `backdrop_id` sample one backdrop, as Flutter's `BackdropKey` groups
149    /// them.
150    pub fn push_backdrop_filter(
151        &mut self,
152        filter: &ImageFilter,
153        blend_mode: BlendMode,
154        backdrop_id: Option<u64>,
155    ) {
156        self.push_layer(Effect::BackdropFilter {
157            filter: filter.clone(),
158            blend_mode,
159            backdrop_id,
160        });
161    }
162
163    /// Ends the effect of the most recently pushed operation.
164    ///
165    /// The container built is the [`EngineLayer`] returned, a child of the one beneath it: the
166    /// layer that pushed keeps it and hands it to [`add_retained`](Self::add_retained) while its
167    /// subtree stays unchanged. dart:ui returns the engine layer from the push; a container here
168    /// is built, and shared, at its pop.
169    ///
170    /// # Panics
171    ///
172    /// If nothing is pushed.
173    pub fn pop(&mut self) -> EngineLayer {
174        assert!(
175            self.layer_stack.len() > 1,
176            "SceneBuilder::pop without a push"
177        );
178        let built = self.layer_stack.pop().expect("a push is open").build();
179        let layer = Arc::new(built);
180        self.add_layer(Layer::Container(Arc::clone(&layer)));
181        layer
182    }
183
184    /// Places a composition layer the host composites, at `rect` in the coordinates of the
185    /// container being built: Flutter's `addPlatformView`. It draws nothing; whoever composites
186    /// the scene makes room for it at its place in paint order.
187    pub fn add_embedded_layer(&mut self, layer: Arc<dyn CompositionLayer>, rect: Rect) {
188        self.add_layer(Layer::Embedded(EmbeddedLayer { layer, rect }));
189    }
190
191    /// Draws `reader`'s video into `rect`, in the coordinates of the container being built, at its
192    /// place in paint order: what Flutter's `addTexture` does for a texture id. Whoever draws the
193    /// scene asks the reader for the frame then.
194    pub fn add_video(&mut self, reader: Arc<dyn VideoFrameReader>, rect: Rect) {
195        self.add_layer(Layer::Video(VideoLayer { reader, rect }));
196    }
197
198    /// Adds a retained [`EngineLayer`] subtree from a previous frame.
199    ///
200    /// All the engine layers that are in the subtree of the retained layer will be automatically
201    /// appended to the current engine layer tree: the same node, in the container being built.
202    pub fn add_retained(&mut self, retained_layer: &EngineLayer) {
203        self.add_layer(Layer::Container(Arc::clone(retained_layer)));
204    }
205
206    /// Adds a [`Picture`] to the scene.
207    ///
208    /// The picture is rasterized at the given `offset`. The hints are for a raster cache valo
209    /// does not have; they are accepted so a layer can pass what Flutter passes.
210    pub fn add_picture(
211        &mut self,
212        offset: Offset,
213        picture: &Arc<Picture>,
214        is_complex_hint: bool,
215        will_change_hint: bool,
216    ) {
217        let _ = (is_complex_hint, will_change_hint);
218        self.add_layer(Layer::DisplayList(DisplayListLayer {
219            offset,
220            picture: Arc::clone(picture),
221        }));
222    }
223
224    /// Finishes building the scene.
225    ///
226    /// # Panics
227    ///
228    /// In debug builds, if a pushed operation was not popped.
229    pub fn build(mut self) -> Scene {
230        debug_assert!(
231            self.layer_stack.len() == 1,
232            "SceneBuilder::build with an open push"
233        );
234        while self.layer_stack.len() > 1 {
235            self.pop();
236        }
237        let root = self.layer_stack.pop().expect("the root is being built");
238        Scene { root: root.build() }
239    }
240
241    /// Starts building a container inside the last: the engine's `PushLayer`.
242    fn push_layer(&mut self, effect: Effect) {
243        self.layer_stack.push(ContainerLayerBuilder::new(effect));
244    }
245
246    /// Adds `layer` to the container being built last: the engine's `AddLayer`.
247    fn add_layer(&mut self, layer: Layer) {
248        self.layer_stack
249            .last_mut()
250            .expect("the root is built only by build")
251            .children
252            .push(layer);
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use crate::{Canvas, Paint};
260
261    fn square() -> Arc<Picture> {
262        let mut canvas = Canvas::new();
263        canvas.draw_rect(Rect::from_ltwh(0.0, 0.0, 10.0, 10.0), &Paint::default());
264        Arc::new(canvas.build())
265    }
266
267    fn container(layer: &Layer) -> &EngineLayer {
268        match layer {
269            Layer::Container(container) => container,
270            other => panic!("not a container: {other:?}"),
271        }
272    }
273
274    #[test]
275    fn a_popped_container_is_a_node_of_its_parent() {
276        let picture = square();
277        let mut builder = SceneBuilder::new();
278        builder.push_opacity(128, Offset::new(1.0, 2.0));
279        builder.push_transform(&Matrix4::scale(2.0, 2.0));
280        builder.add_picture(Offset::new(3.0, 4.0), &picture, false, false);
281        let inner = builder.pop();
282        let outer = builder.pop();
283        let scene = builder.build();
284
285        assert_eq!(scene.root.effect, Effect::None);
286        let [only] = scene.root.children.as_slice() else {
287            panic!("one child: {:?}", scene.root.children);
288        };
289        assert!(Arc::ptr_eq(container(only), &outer));
290        assert_eq!(
291            outer.effect,
292            Effect::Opacity {
293                alpha: 128,
294                offset: Offset::new(1.0, 2.0)
295            }
296        );
297        assert!(Arc::ptr_eq(container(&outer.children[0]), &inner));
298        assert_eq!(inner.effect, Effect::Transform(Matrix4::scale(2.0, 2.0)));
299        assert_eq!(
300            inner.children,
301            [Layer::DisplayList(DisplayListLayer {
302                offset: Offset::new(3.0, 4.0),
303                picture,
304            })]
305        );
306    }
307
308    #[test]
309    fn a_retained_container_is_the_same_node_in_the_next_scene() {
310        let mut builder = SceneBuilder::new();
311        builder.push_clip_rect(Rect::from_ltwh(0.0, 0.0, 5.0, 5.0), Clip::HardEdge);
312        builder.add_picture(Offset::ZERO, &square(), false, false);
313        let retained = builder.pop();
314        let _first = builder.build();
315
316        let mut builder = SceneBuilder::new();
317        builder.push_offset(10.0, 0.0);
318        builder.add_retained(&retained);
319        builder.pop();
320        let next = builder.build();
321        let moved = container(&next.root.children[0]);
322        assert!(Arc::ptr_eq(container(&moved.children[0]), &retained));
323    }
324
325    #[test]
326    fn an_opacity_is_kept_between_none_and_all() {
327        let mut builder = SceneBuilder::new();
328        builder.push_opacity(300, Offset::ZERO);
329        let all = builder.pop();
330        builder.push_opacity(-1, Offset::ZERO);
331        let none = builder.pop();
332        let alpha = |layer: &EngineLayer| match layer.effect {
333            Effect::Opacity { alpha, .. } => alpha,
334            _ => unreachable!(),
335        };
336        assert_eq!((alpha(&all), alpha(&none)), (255, 0));
337    }
338
339    #[test]
340    fn a_composition_layer_is_a_leaf_at_its_place_in_paint_order() {
341        let layer = super::super::idle_layer();
342        let rect = Rect::from_ltwh(1.0, 2.0, 3.0, 4.0);
343        let mut builder = SceneBuilder::new();
344        builder.add_picture(Offset::ZERO, &square(), false, false);
345        builder.add_embedded_layer(Arc::clone(&layer), rect);
346        let scene = builder.build();
347        assert!(matches!(scene.root.children[0], Layer::DisplayList(_)));
348        assert_eq!(
349            scene.root.children[1],
350            Layer::Embedded(EmbeddedLayer { layer, rect })
351        );
352    }
353
354    #[test]
355    fn a_scene_without_layers_has_an_empty_root() {
356        let scene = SceneBuilder::new().build();
357        assert!(scene.root.children.is_empty());
358    }
359
360    #[test]
361    #[should_panic(expected = "open push")]
362    fn build_with_an_open_push_panics() {
363        let mut builder = SceneBuilder::new();
364        builder.push_offset(0.0, 0.0);
365        let _scene = builder.build();
366    }
367
368    #[test]
369    #[should_panic(expected = "without a push")]
370    fn pop_without_a_push_panics() {
371        SceneBuilder::new().pop();
372    }
373}