Skip to main content

inset_embedder/scene/
placement.rs

1//! Where a frame puts one composition layer, with everything the containers above it do to it:
2//! Flutter's `EmbeddedViewParams` and its `MutatorsStack`. A scene's slices hand a host one for
3//! each layer the scene places, and a layer places its sublayers by them too, in its buffer's
4//! pixels.
5//!
6//! A host applies what its compositor can and reads the rest through the summaries here: a
7//! compositor that only moves and clips rectangles reads [`Placement::bounds`],
8//! [`Placement::scale`] and [`Placement::visible_bounds`]. The invariant a later editor must
9//! keep: the mutators are what the containers above the layer do, outermost first, and nothing a
10//! host can work out from them is stored beside them.
11
12use std::sync::Arc;
13
14use crate::geometry::{Matrix4, RRect, Rect};
15use crate::painting::Path;
16
17/// Where one frame puts one composition layer: what a host compares to tell a frame worth
18/// presenting from one it already shows.
19#[derive(Clone, Debug, PartialEq)]
20pub struct Placement {
21    /// The layer's rectangle, in the coordinates the transforms among `mutators` lead to.
22    pub rect: Rect,
23    /// What the containers above the layer do to it, the outermost first: Flutter's
24    /// `MutatorsStack`. A view's scene begins with its root transform, which takes the
25    /// framework's logical pixels to the view's physical ones.
26    pub mutators: Vec<Mutator>,
27}
28
29/// One thing a container above a composition layer does to it: Flutter's `Mutator`. Each is in the
30/// coordinates the transforms before it lead to.
31#[derive(Clone, Debug, PartialEq)]
32pub enum Mutator {
33    Transform(Matrix4),
34    /// Nothing of the layer shows outside the rectangle.
35    ClipRect(Rect),
36    /// Nothing of the layer shows outside the rounded rectangle.
37    ClipRRect(RRect),
38    /// Nothing of the layer shows outside the path, filled non-zero.
39    ClipPath(Arc<Path>),
40    /// The layer shows at this alpha, out of 255.
41    Opacity(u8),
42}
43
44impl Placement {
45    /// Every transform above the layer in one: Flutter's `transformMatrix`, which takes `rect`'s
46    /// coordinates to the view's pixels.
47    pub fn transform(&self) -> Matrix4 {
48        self.mutators
49            .iter()
50            .fold(Matrix4::IDENTITY, |transform, mutator| match mutator {
51                Mutator::Transform(matrix) => transform.then(matrix),
52                _ => transform,
53            })
54    }
55
56    /// Every opacity above the layer multiplied, from 0 to 1.
57    pub fn opacity(&self) -> f64 {
58        self.mutators
59            .iter()
60            .fold(1.0, |opacity, mutator| match mutator {
61                Mutator::Opacity(alpha) => opacity * f64::from(*alpha) / 255.0,
62                _ => opacity,
63            })
64    }
65
66    /// How many of the view's pixels one unit of the layer's own coordinates spans along its
67    /// own horizontal: the scale of every transform above it, the root's device pixel ratio
68    /// among them. A producer draws its buffers at the device pixel ratio, so a host shows a
69    /// buffer at `scale / device_pixel_ratio` of its size.
70    pub fn scale(&self) -> f64 {
71        let m = self.transform().to_flutter_array();
72        f64::from(m[0]).hypot(f64::from(m[1]))
73    }
74
75    /// The box around the layer under every transform above it, in the view's pixels.
76    pub fn bounds(&self) -> Rect {
77        bounds_under(&self.transform(), self.rect)
78    }
79
80    /// [`bounds`](Self::bounds) cut down to the bounds of every clip above the layer: what a
81    /// compositor that clips by rectangles alone shows of it. An empty rectangle on the clip's
82    /// nearest edge when nothing shows.
83    pub fn visible_bounds(&self) -> Rect {
84        let mut transform = Matrix4::IDENTITY;
85        let mut visible = self.bounds();
86        for mutator in &self.mutators {
87            let clip_bounds = match mutator {
88                Mutator::Transform(matrix) => {
89                    transform = transform.then(matrix);
90                    continue;
91                }
92                Mutator::ClipRect(rect) => *rect,
93                Mutator::ClipRRect(rrect) => rrect.outer_rect(),
94                Mutator::ClipPath(path) => rect_of(path.bounds()),
95                Mutator::Opacity(_) => continue,
96            };
97            visible = visible_part_of(visible, bounds_under(&transform, clip_bounds));
98        }
99        visible
100    }
101}
102
103/// The box around `rect` under `transform`.
104fn bounds_under(transform: &Matrix4, rect: Rect) -> Rect {
105    rect_of(transform.map_rect(&rect.into()))
106}
107
108fn rect_of(rect: crate::valo::Rect) -> Rect {
109    Rect::from_ltwh(
110        f64::from(rect.x),
111        f64::from(rect.y),
112        f64::from(rect.width),
113        f64::from(rect.height),
114    )
115}
116
117/// What of `rect` lies inside `clip`; an empty rectangle on `clip`'s nearest edge when nothing
118/// does.
119fn visible_part_of(rect: Rect, clip: Rect) -> Rect {
120    let overlap = rect.intersect(clip);
121    Rect::from_ltrb(
122        overlap.left,
123        overlap.top,
124        overlap.right.max(overlap.left),
125        overlap.bottom.max(overlap.top),
126    )
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::painting::PathBuilder;
133
134    fn placement(rect: Rect, mutators: Vec<Mutator>) -> Placement {
135        Placement { rect, mutators }
136    }
137
138    /// A page at half its size on a display of two pixels a point: each unit of the page's own
139    /// spans one of the view's pixels, so a buffer drawn at two pixels a point shows at half its
140    /// size.
141    #[test]
142    fn a_placement_carries_the_scale_and_the_bounds_of_every_transform_above_it() {
143        let page = placement(
144            Rect::from_ltwh(0.0, 0.0, 390.0, 844.0),
145            vec![
146                Mutator::Transform(Matrix4::scale(2.0, 2.0)),
147                Mutator::Transform(Matrix4::translation(10.0, 20.0)),
148                Mutator::Transform(Matrix4::scale(0.5, 0.5)),
149            ],
150        );
151        assert_eq!(page.scale(), 1.0);
152        assert_eq!(page.bounds(), Rect::from_ltwh(20.0, 40.0, 390.0, 844.0));
153        assert_eq!(page.visible_bounds(), page.bounds(), "nothing clips it");
154    }
155
156    #[test]
157    fn a_turned_layer_is_bounded_by_the_box_around_it() {
158        let turned = placement(
159            Rect::from_ltwh(0.0, 0.0, 10.0, 10.0),
160            vec![Mutator::Transform(Matrix4::rotation(
161                std::f32::consts::FRAC_PI_4,
162            ))],
163        );
164        let bounds = turned.bounds();
165        let half_diagonal = 50f64.sqrt();
166        assert!((bounds.left + half_diagonal).abs() < 1e-4, "{bounds:?}");
167        assert!((bounds.right - half_diagonal).abs() < 1e-4, "{bounds:?}");
168        assert!((turned.scale() - 1.0).abs() < 1e-6);
169    }
170
171    #[test]
172    fn what_shows_is_cut_by_the_bounds_of_every_clip_under_its_own_transform() {
173        let path = {
174            let mut path = PathBuilder::new();
175            path.rrect(Rect::from_ltwh(0.0, 0.0, 200.0, 20.0).into(), 4.0);
176            path.build()
177        };
178        let clipped = placement(
179            Rect::from_ltwh(-5.0, -5.0, 300.0, 300.0),
180            vec![
181                Mutator::Transform(Matrix4::scale(2.0, 2.0)),
182                Mutator::ClipRect(Rect::from_ltwh(0.0, 0.0, 100.0, 50.0)),
183                Mutator::Transform(Matrix4::translation(10.0, 10.0)),
184                Mutator::ClipPath(path),
185            ],
186        );
187        assert_eq!(clipped.bounds(), Rect::from_ltwh(10.0, 10.0, 600.0, 600.0));
188        // The path's bounds, (10, 10) to (210, 30), cut by the rectangle's, (0, 0) to (100, 50),
189        // at two pixels to a unit.
190        assert_eq!(
191            clipped.visible_bounds(),
192            Rect::from_ltrb(20.0, 20.0, 200.0, 60.0)
193        );
194    }
195
196    #[test]
197    fn a_layer_outside_its_clip_shows_nothing() {
198        let outside = placement(
199            Rect::from_ltwh(200.0, 0.0, 50.0, 50.0),
200            vec![Mutator::ClipRect(Rect::from_ltwh(0.0, 0.0, 100.0, 100.0))],
201        );
202        assert!(outside.visible_bounds().is_empty());
203    }
204
205    #[test]
206    fn opacities_multiply() {
207        let faded = placement(
208            Rect::from_ltwh(0.0, 0.0, 1.0, 1.0),
209            vec![
210                Mutator::Opacity(51),
211                Mutator::Opacity(255),
212                Mutator::Opacity(51),
213            ],
214        );
215        assert!((faded.opacity() - 0.04).abs() < 1e-9, "{}", faded.opacity());
216        assert_eq!(placement(Rect::ZERO, Vec::new()).opacity(), 1.0);
217    }
218}