1use std::sync::Arc;
13
14use crate::geometry::{Matrix4, RRect, Rect};
15use crate::painting::Path;
16
17#[derive(Clone, Debug, PartialEq)]
20pub struct Placement {
21 pub rect: Rect,
23 pub mutators: Vec<Mutator>,
27}
28
29#[derive(Clone, Debug, PartialEq)]
32pub enum Mutator {
33 Transform(Matrix4),
34 ClipRect(Rect),
36 ClipRRect(RRect),
38 ClipPath(Arc<Path>),
40 Opacity(u8),
42}
43
44impl Placement {
45 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 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 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 pub fn bounds(&self) -> Rect {
77 bounds_under(&self.transform(), self.rect)
78 }
79
80 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
103fn 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
117fn 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 #[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 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}