1use std::fmt;
8use std::sync::Arc;
9
10use crate::geometry::{Clip, Color, Matrix4, Offset, RRect, Rect, rrect_radii_elliptical};
11use crate::painting::{
12 Backdrop, BlendMode, Canvas, ClipOp, FillRule, ImageFilter, Paint, Path, Picture, valo,
13};
14
15pub type Scene = Picture;
17
18pub struct SceneBuilder {
27 canvas: Canvas,
28 open_scopes: usize,
30}
31
32impl fmt::Debug for SceneBuilder {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.debug_struct("SceneBuilder")
35 .field("open_scopes", &self.open_scopes)
36 .finish_non_exhaustive()
37 }
38}
39
40impl Default for SceneBuilder {
41 fn default() -> SceneBuilder {
42 SceneBuilder::new()
43 }
44}
45
46impl SceneBuilder {
47 pub fn new() -> SceneBuilder {
49 SceneBuilder {
50 canvas: Canvas::new(),
51 open_scopes: 0,
52 }
53 }
54
55 pub fn push_transform(&mut self, matrix4: &Matrix4) {
59 self.open(|canvas| {
60 canvas.save();
61 canvas.concat(matrix4);
62 });
63 }
64
65 pub fn push_offset(&mut self, dx: f64, dy: f64) {
70 self.open(|canvas| {
71 canvas.save();
72 canvas.translate(dx as f32, dy as f32);
73 });
74 }
75
76 pub fn push_clip_rect(&mut self, rect: Rect, clip_behavior: Clip) {
80 self.open(|canvas| {
81 open_clip(canvas, clip_behavior, rect.into());
82 canvas.clip_rect(rect, ClipOp::Intersect);
83 });
84 }
85
86 pub fn push_clip_rrect(&mut self, rrect: RRect, clip_behavior: Clip) {
90 self.open(|canvas| {
91 open_clip(canvas, clip_behavior, rrect.outer_rect().into());
92 canvas.clip_rrect_radii_elliptical(
93 rrect.outer_rect(),
94 rrect_radii_elliptical(rrect),
95 ClipOp::Intersect,
96 );
97 });
98 }
99
100 pub fn push_clip_path(&mut self, path: &Arc<Path>, clip_behavior: Clip) {
104 self.open(|canvas| {
105 open_clip(canvas, clip_behavior, path.bounds());
106 canvas.clip_path(path, FillRule::NonZero, ClipOp::Intersect);
107 });
108 }
109
110 pub fn push_opacity(&mut self, alpha: i32, offset: Offset) {
116 self.open(|canvas| {
117 let paint = Paint {
118 color: Color::from_argb(alpha, 0, 0, 0).into(),
119 ..Paint::default()
120 };
121 canvas.save_layer(None, &paint);
122 canvas.translate(offset.dx() as f32, offset.dy() as f32);
123 });
124 }
125
126 pub fn push_backdrop_filter(
136 &mut self,
137 filter: &ImageFilter,
138 blend_mode: BlendMode,
139 backdrop_id: Option<u64>,
140 ) {
141 let (sigma, bounds) = backdrop_blur(filter).unwrap_or((0.0, None));
142 let mut backdrop = Backdrop::blur(sigma as f32);
143 if let Some(id) = backdrop_id {
144 backdrop = backdrop.shared(id);
145 }
146 let paint = Paint {
147 blend_mode,
148 ..Paint::default()
149 };
150 self.open(|canvas| {
151 canvas.save_layer_backdrop(bounds.map(Into::into), &paint, backdrop);
152 });
153 }
154
155 pub fn pop(&mut self) {
161 debug_assert!(self.open_scopes > 0, "SceneBuilder::pop without a push");
162 self.open_scopes -= 1;
163 self.canvas.restore();
164 }
165
166 pub fn add_picture(
171 &mut self,
172 offset: Offset,
173 picture: &Arc<Picture>,
174 is_complex_hint: bool,
175 will_change_hint: bool,
176 ) {
177 let _ = (is_complex_hint, will_change_hint);
178 self.canvas.save();
179 self.canvas
180 .translate(offset.dx() as f32, offset.dy() as f32);
181 self.canvas.draw_display_list(picture);
182 self.canvas.restore();
183 }
184
185 pub fn build(self) -> Scene {
191 debug_assert_eq!(self.open_scopes, 0, "SceneBuilder::build with an open push");
192 self.canvas.build()
193 }
194
195 fn open(&mut self, push: impl FnOnce(&mut Canvas)) {
196 push(&mut self.canvas);
197 self.open_scopes += 1;
198 }
199}
200
201fn open_clip(canvas: &mut Canvas, clip_behavior: Clip, bounds: valo::Rect) {
204 match clip_behavior {
205 Clip::None | Clip::HardEdge | Clip::AntiAlias => canvas.save(),
206 Clip::AntiAliasWithSaveLayer => canvas.save_layer(Some(bounds), &Paint::default()),
207 }
208}
209
210fn backdrop_blur(filter: &ImageFilter) -> Option<(f64, Option<Rect>)> {
213 match filter {
214 ImageFilter::Blur {
215 sigma_x, bounds, ..
216 } => Some((*sigma_x, *bounds)),
217 ImageFilter::Color(_) => None,
218 ImageFilter::Compose { outer, inner } => {
219 backdrop_blur(inner).or_else(|| backdrop_blur(outer))
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn pushes_and_pops_balance() {
230 let mut builder = SceneBuilder::new();
231 builder.push_offset(1.0, 2.0);
232 builder.push_opacity(128, Offset::ZERO);
233 builder.pop();
234 builder.pop();
235 let _scene = builder.build();
236 }
237
238 #[test]
239 #[should_panic(expected = "open push")]
240 fn build_with_an_open_push_panics() {
241 let mut builder = SceneBuilder::new();
242 builder.push_offset(0.0, 0.0);
243 let _scene = builder.build();
244 }
245
246 #[test]
247 #[should_panic(expected = "without a push")]
248 fn pop_without_a_push_panics() {
249 SceneBuilder::new().pop();
250 }
251}