1use kurbo::{Affine, BezPath, Point, Rect};
4use peniko::{Brush, Color, ImageData};
5
6use crate::glyph::GlyphRun;
7use crate::scene::{Command, CornerRadii, DashPattern, PathStyle, Scene};
8use crate::shader::ShaderProgram;
9
10pub struct SceneBuilder<'a> {
16 scene: &'a mut Scene,
17}
18
19impl<'a> SceneBuilder<'a> {
20 pub fn new(scene: &'a mut Scene) -> Self {
27 scene.transform_stack.clear();
28 scene.transform_stack.push(Affine::IDENTITY);
29 Self { scene }
30 }
31
32 pub fn current_transform(&self) -> Affine {
34 *self
35 .scene
36 .transform_stack
37 .last()
38 .expect("transform stack is never empty")
39 }
40
41 pub fn push_transform(&mut self, transform: Affine) {
46 let composed = self.current_transform() * transform;
47 self.scene.transform_stack.push(composed);
48 }
49
50 pub fn pop_transform(&mut self) {
55 if self.scene.transform_stack.len() > 1 {
56 self.scene.transform_stack.pop();
57 }
58 }
59
60 pub fn fill_rect(&mut self, rect: Rect, brush: Brush) {
62 let transform = self.current_transform();
63 self.scene.push(Command::FillRect {
64 rect,
65 brush,
66 transform,
67 });
68 }
69
70 pub fn fill_rounded_rect(&mut self, rect: Rect, radius: f64, brush: Brush) {
77 self.fill_rounded_rect_radii(rect, CornerRadii::from(radius), brush);
78 }
79
80 pub fn fill_rounded_rect_radii(&mut self, rect: Rect, radii: CornerRadii, brush: Brush) {
83 let transform = self.current_transform();
84 self.scene.push(Command::RoundedRect {
85 rect,
86 radii,
87 brush,
88 transform,
89 });
90 }
91
92 pub fn stroke_line(&mut self, p0: Point, p1: Point, width: f64, brush: Brush) {
95 let transform = self.current_transform();
96 self.scene.push(Command::Line {
97 p0,
98 p1,
99 width,
100 brush,
101 transform,
102 });
103 }
104
105 pub fn draw_glyph_run(&mut self, mut glyph_run: GlyphRun) {
107 glyph_run.transform = self.current_transform() * glyph_run.transform;
108 self.scene.push(Command::GlyphRun(glyph_run));
109 }
110
111 pub fn push_clip(&mut self, rect: Rect) {
115 let transform = self.current_transform();
116 self.scene.push(Command::PushClip { rect, transform });
117 }
118
119 pub fn push_clip_rounded(&mut self, rect: Rect, radius: f64) {
128 self.push_clip_rounded_radii(rect, CornerRadii::from(radius));
129 }
130
131 pub fn push_clip_rounded_radii(&mut self, rect: Rect, radii: CornerRadii) {
136 let transform = self.current_transform();
137 self.scene.push(Command::PushClipRounded {
138 rect,
139 radii,
140 transform,
141 });
142 }
143
144 pub fn pop_clip(&mut self) {
146 self.scene.push(Command::PopClip);
147 }
148
149 pub fn draw_image(&mut self, data: &ImageData, dest: Rect) {
155 let transform = self.current_transform();
156 self.scene.push(Command::Image {
157 data: data.clone(),
158 dest,
159 transform,
160 });
161 }
162
163 pub fn draw_blurred_rounded_rect(
167 &mut self,
168 rect: Rect,
169 radius: f64,
170 std_dev: f64,
171 color: Color,
172 ) {
173 self.draw_blurred_rounded_rect_radii(rect, CornerRadii::from(radius), std_dev, color);
174 }
175
176 pub fn draw_blurred_rounded_rect_radii(
184 &mut self,
185 rect: Rect,
186 radii: CornerRadii,
187 std_dev: f64,
188 color: Color,
189 ) {
190 let transform = self.current_transform();
191 self.scene.push(Command::BlurredRoundedRect {
192 rect,
193 radii,
194 std_dev,
195 color,
196 transform,
197 });
198 }
199
200 pub fn push_layer(&mut self, rect: Rect, alpha: f32) {
203 let transform = self.current_transform();
204 self.scene.push(Command::PushLayer {
205 rect,
206 alpha,
207 transform,
208 });
209 }
210
211 pub fn pop_layer(&mut self) {
213 self.scene.push(Command::PopLayer);
214 }
215
216 pub fn clear_rect(&mut self, rect: Rect) {
222 let transform = self.current_transform();
223 self.scene.push(Command::ClearRect { rect, transform });
224 }
225
226 pub fn fill_path(&mut self, path: BezPath, brush: Brush) {
229 let transform = self.current_transform();
230 self.scene.push(Command::Path {
231 path,
232 style: PathStyle::Fill,
233 brush,
234 transform,
235 });
236 }
237
238 pub fn stroke_path(&mut self, path: BezPath, width: f64, brush: Brush) {
241 let transform = self.current_transform();
242 self.scene.push(Command::Path {
243 path,
244 style: PathStyle::Stroke { width, dash: None },
245 brush,
246 transform,
247 });
248 }
249
250 pub fn stroke_path_dashed(
254 &mut self,
255 path: BezPath,
256 width: f64,
257 dash: DashPattern,
258 brush: Brush,
259 ) {
260 let transform = self.current_transform();
261 self.scene.push(Command::Path {
262 path,
263 style: PathStyle::Stroke {
264 width,
265 dash: Some(dash),
266 },
267 brush,
268 transform,
269 });
270 }
271
272 pub fn push_snapshot(&mut self, key: u64, rect: Rect, alpha: f32, scale: f64) {
279 let transform = self.current_transform();
280 self.scene.snapshot_depth += 1;
281 self.scene.push(Command::PushSnapshot {
282 key,
283 rect,
284 alpha,
285 scale,
286 transform,
287 });
288 }
289
290 pub fn pop_snapshot(&mut self) {
294 if self.scene.snapshot_depth > 0 {
295 self.scene.snapshot_depth -= 1;
296 self.scene.push(Command::PopSnapshot);
297 }
298 }
299
300 pub fn draw_shader(&mut self, program: &ShaderProgram, dest: Rect, time: f32) {
311 let transform = self.current_transform();
312 self.scene.push(Command::ShaderQuad {
313 program: program.clone(),
314 dest,
315 transform,
316 time,
317 });
318 }
319
320 pub fn scene_texture(&mut self, id: u64, dest: Rect) {
327 let transform = self.current_transform();
328 self.scene.push(Command::SceneTexture {
329 id,
330 dest,
331 transform,
332 });
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use crate::glyph::{FontHandle, Glyph};
340 use peniko::FontData;
341 use peniko::color::palette::css::RED;
342
343 fn red_brush() -> Brush {
344 Brush::Solid(RED)
345 }
346
347 #[test]
348 fn fill_rect_uses_identity_transform_by_default() {
349 let mut scene = Scene::new();
350 let mut builder = SceneBuilder::new(&mut scene);
351
352 let rect = Rect::new(0.0, 0.0, 10.0, 20.0);
353 builder.fill_rect(rect, red_brush());
354
355 let commands = scene.commands();
356 assert_eq!(commands.len(), 1);
357 match &commands[0] {
358 Command::FillRect {
359 rect: got_rect,
360 transform,
361 ..
362 } => {
363 assert_eq!(*got_rect, rect);
364 assert_eq!(*transform, Affine::IDENTITY);
365 }
366 other => panic!("expected FillRect, got {other:?}"),
367 }
368 }
369
370 #[test]
371 fn transform_stack_composes_for_fill_rect() {
372 let mut scene = Scene::new();
373 let mut builder = SceneBuilder::new(&mut scene);
374
375 let translate = Affine::translate((10.0, 5.0));
376 let scale = Affine::scale(2.0);
377
378 builder.push_transform(translate);
379 builder.push_transform(scale);
380
381 let rect = Rect::new(0.0, 0.0, 1.0, 1.0);
382 builder.fill_rect(rect, red_brush());
383
384 builder.pop_transform();
386 builder.fill_rect(rect, red_brush());
387
388 builder.pop_transform();
391 builder.pop_transform();
392 builder.fill_rect(rect, red_brush());
393
394 let expected = [translate * scale, translate, Affine::IDENTITY];
395 let commands = scene.commands();
396 assert_eq!(commands.len(), expected.len());
397 for (command, expected_transform) in commands.iter().zip(expected) {
398 match command {
399 Command::FillRect { transform, .. } => assert_eq!(*transform, expected_transform),
400 other => panic!("expected FillRect, got {other:?}"),
401 }
402 }
403 }
404
405 #[test]
406 fn glyph_run_round_trips_its_fields() {
407 let mut scene = Scene::new();
408 let mut builder = SceneBuilder::new(&mut scene);
409
410 let font = FontHandle::new(FontData::new(peniko::Blob::from(Vec::<u8>::new()), 0));
411 let glyphs = vec![
412 Glyph {
413 id: 1,
414 x: 0.0,
415 y: 0.0,
416 },
417 Glyph {
418 id: 2,
419 x: 8.0,
420 y: 0.0,
421 },
422 ];
423 let run = GlyphRun {
424 font: font.clone(),
425 font_size: 16.0,
426 brush: red_brush(),
427 transform: Affine::IDENTITY,
428 glyphs: glyphs.clone(),
429 };
430
431 builder.draw_glyph_run(run);
432
433 match &scene.commands()[0] {
434 Command::GlyphRun(got) => {
435 assert_eq!(got.font_size, 16.0);
436 assert_eq!(got.glyphs, glyphs);
437 assert_eq!(got.transform, Affine::IDENTITY);
438 }
439 other => panic!("expected GlyphRun, got {other:?}"),
440 }
441 }
442
443 #[test]
444 fn glyph_run_composes_with_current_transform() {
445 let mut scene = Scene::new();
446 let mut builder = SceneBuilder::new(&mut scene);
447
448 let translate = Affine::translate((3.0, 4.0));
449 builder.push_transform(translate);
450
451 let font = FontHandle::new(FontData::new(peniko::Blob::from(Vec::<u8>::new()), 0));
452 let run = GlyphRun {
453 font,
454 font_size: 12.0,
455 brush: red_brush(),
456 transform: Affine::IDENTITY,
457 glyphs: vec![],
458 };
459 builder.draw_glyph_run(run);
460
461 match &scene.commands()[0] {
462 Command::GlyphRun(got) => assert_eq!(got.transform, translate),
463 other => panic!("expected GlyphRun, got {other:?}"),
464 }
465 }
466
467 #[test]
468 fn push_pop_clip_emit_commands() {
469 let mut scene = Scene::new();
470 let mut builder = SceneBuilder::new(&mut scene);
471
472 let rect = Rect::new(0.0, 0.0, 5.0, 5.0);
473 builder.push_clip(rect);
474 builder.pop_clip();
475
476 let commands = scene.commands();
477 assert_eq!(commands.len(), 2);
478 assert!(matches!(commands[0], Command::PushClip { .. }));
479 assert!(matches!(commands[1], Command::PopClip));
480 }
481
482 #[test]
483 fn rounded_rect_round_trips_radius_and_transform() {
484 let mut scene = Scene::new();
485 let mut builder = SceneBuilder::new(&mut scene);
486 let translate = Affine::translate((2.0, 3.0));
487 builder.push_transform(translate);
488 let rect = Rect::new(0.0, 0.0, 10.0, 8.0);
489 builder.fill_rounded_rect(rect, 4.0, red_brush());
490
491 match &scene.commands()[0] {
492 Command::RoundedRect {
493 rect: got,
494 radii,
495 transform,
496 ..
497 } => {
498 assert_eq!(*got, rect);
499 assert_eq!(*radii, CornerRadii::uniform(4.0));
501 assert_eq!(*transform, translate);
502 }
503 other => panic!("expected RoundedRect, got {other:?}"),
504 }
505 }
506
507 #[test]
508 fn rounded_rect_radii_round_trips_each_corner() {
509 let mut scene = Scene::new();
510 let mut builder = SceneBuilder::new(&mut scene);
511 let rect = Rect::new(0.0, 0.0, 10.0, 8.0);
512 let radii = CornerRadii::new(1.0, 2.0, 3.0, 4.0);
513 builder.fill_rounded_rect_radii(rect, radii, red_brush());
514
515 match &scene.commands()[0] {
516 Command::RoundedRect {
517 rect: got,
518 radii: r,
519 ..
520 } => {
521 assert_eq!(*got, rect);
522 assert_eq!(*r, radii);
523 }
524 other => panic!("expected RoundedRect, got {other:?}"),
525 }
526 }
527
528 #[test]
529 fn stroke_line_round_trips_endpoints_and_width() {
530 let mut scene = Scene::new();
531 let mut builder = SceneBuilder::new(&mut scene);
532 let p0 = Point::new(1.0, 2.0);
533 let p1 = Point::new(9.0, 12.0);
534 builder.stroke_line(p0, p1, 2.5, red_brush());
535
536 match &scene.commands()[0] {
537 Command::Line {
538 p0: g0,
539 p1: g1,
540 width,
541 transform,
542 ..
543 } => {
544 assert_eq!(*g0, p0);
545 assert_eq!(*g1, p1);
546 assert_eq!(*width, 2.5);
547 assert_eq!(*transform, Affine::IDENTITY);
548 }
549 other => panic!("expected Line, got {other:?}"),
550 }
551 }
552
553 #[test]
554 fn push_clip_captures_current_transform() {
555 let mut scene = Scene::new();
556 let mut builder = SceneBuilder::new(&mut scene);
557 let scale = Affine::scale(2.0);
558 builder.push_transform(scale);
559 builder.push_clip(Rect::new(0.0, 0.0, 5.0, 5.0));
560
561 match &scene.commands()[0] {
562 Command::PushClip { transform, .. } => assert_eq!(*transform, scale),
563 other => panic!("expected PushClip, got {other:?}"),
564 }
565 }
566
567 #[test]
568 fn push_clip_rounded_pops_through_the_shared_pop_clip() {
569 let mut scene = Scene::new();
572 let mut builder = SceneBuilder::new(&mut scene);
573
574 builder.push_clip_rounded(Rect::new(0.0, 0.0, 5.0, 5.0), 2.0);
575 builder.pop_clip();
576
577 let commands = scene.commands();
578 assert_eq!(commands.len(), 2);
579 assert!(matches!(commands[0], Command::PushClipRounded { .. }));
580 assert!(matches!(commands[1], Command::PopClip));
581 }
582
583 #[test]
584 fn push_clip_rounded_round_trips_radius_and_captures_current_transform() {
585 let mut scene = Scene::new();
586 let mut builder = SceneBuilder::new(&mut scene);
587 let scale = Affine::scale(2.0);
588 builder.push_transform(scale);
589 let rect = Rect::new(0.0, 0.0, 40.0, 40.0);
590 builder.push_clip_rounded(rect, 8.0);
591
592 match &scene.commands()[0] {
593 Command::PushClipRounded {
594 rect: got,
595 radii,
596 transform,
597 } => {
598 assert_eq!(*got, rect);
599 assert_eq!(*radii, CornerRadii::uniform(8.0));
600 assert_eq!(*transform, scale);
601 }
602 other => panic!("expected PushClipRounded, got {other:?}"),
603 }
604 }
605
606 #[test]
607 fn push_clip_rounded_radii_round_trips_each_corner() {
608 let mut scene = Scene::new();
609 let mut builder = SceneBuilder::new(&mut scene);
610 let rect = Rect::new(0.0, 0.0, 40.0, 40.0);
611 let radii = CornerRadii::new(8.0, 8.0, 0.0, 0.0);
612 builder.push_clip_rounded_radii(rect, radii);
613 builder.pop_clip();
614
615 let commands = scene.commands();
616 assert_eq!(commands.len(), 2);
617 match &commands[0] {
618 Command::PushClipRounded { radii: r, .. } => assert_eq!(*r, radii),
619 other => panic!("expected PushClipRounded, got {other:?}"),
620 }
621 assert!(matches!(commands[1], Command::PopClip));
623 }
624
625 #[test]
626 fn nested_rounded_clip_and_layer_preserve_push_pop_ordering() {
627 let mut scene = Scene::new();
631 let mut builder = SceneBuilder::new(&mut scene);
632 let clip_rect = Rect::new(0.0, 0.0, 100.0, 100.0);
633 let layer_rect = Rect::new(10.0, 10.0, 50.0, 50.0);
634
635 builder.push_clip_rounded(clip_rect, 12.0);
636 builder.push_layer(layer_rect, 0.75);
637 builder.fill_rect(Rect::new(0.0, 0.0, 1.0, 1.0), red_brush());
638 builder.pop_layer();
639 builder.pop_clip();
640
641 let commands = scene.commands();
642 assert_eq!(commands.len(), 5);
643 assert!(matches!(commands[0], Command::PushClipRounded { .. }));
644 assert!(matches!(commands[1], Command::PushLayer { .. }));
645 assert!(matches!(commands[2], Command::FillRect { .. }));
646 assert!(matches!(commands[3], Command::PopLayer));
647 assert!(matches!(commands[4], Command::PopClip));
648 }
649
650 fn two_by_two_image() -> ImageData {
651 ImageData {
652 data: peniko::Blob::from(vec![0u8; 2 * 2 * 4]),
653 format: peniko::ImageFormat::Rgba8,
654 alpha_type: peniko::ImageAlphaType::Alpha,
655 width: 2,
656 height: 2,
657 }
658 }
659
660 #[test]
661 fn draw_image_round_trips_data_and_dest_under_identity_transform() {
662 let mut scene = Scene::new();
663 let mut builder = SceneBuilder::new(&mut scene);
664 let data = two_by_two_image();
665 let dest = Rect::new(0.0, 0.0, 20.0, 20.0);
666 builder.draw_image(&data, dest);
667
668 match &scene.commands()[0] {
669 Command::Image {
670 data: got_data,
671 dest: got_dest,
672 transform,
673 } => {
674 assert_eq!(*got_data, data);
675 assert_eq!(*got_dest, dest);
676 assert_eq!(*transform, Affine::IDENTITY);
677 }
678 other => panic!("expected Image, got {other:?}"),
679 }
680 }
681
682 #[test]
683 fn draw_image_composes_with_current_transform() {
684 let mut scene = Scene::new();
685 let mut builder = SceneBuilder::new(&mut scene);
686 let translate = Affine::translate((5.0, 6.0));
687 builder.push_transform(translate);
688 let data = two_by_two_image();
689 builder.draw_image(&data, Rect::new(0.0, 0.0, 4.0, 4.0));
690
691 match &scene.commands()[0] {
692 Command::Image { transform, .. } => assert_eq!(*transform, translate),
693 other => panic!("expected Image, got {other:?}"),
694 }
695 }
696
697 #[test]
698 fn draw_shader_round_trips_program_dest_and_time_under_identity_transform() {
699 let mut scene = Scene::new();
700 let mut builder = SceneBuilder::new(&mut scene);
701 let program = ShaderProgram::new("fn main() {}");
702 let dest = Rect::new(0.0, 0.0, 20.0, 20.0);
703 builder.draw_shader(&program, dest, 1.5);
704
705 match &scene.commands()[0] {
706 Command::ShaderQuad {
707 program: got_program,
708 dest: got_dest,
709 transform,
710 time,
711 } => {
712 assert_eq!(got_program.id(), program.id());
713 assert_eq!(*got_dest, dest);
714 assert_eq!(*transform, Affine::IDENTITY);
715 assert_eq!(*time, 1.5);
716 }
717 other => panic!("expected ShaderQuad, got {other:?}"),
718 }
719 }
720
721 #[test]
722 fn draw_shader_composes_with_current_transform() {
723 let mut scene = Scene::new();
724 let mut builder = SceneBuilder::new(&mut scene);
725 let translate = Affine::translate((5.0, 6.0));
726 builder.push_transform(translate);
727 let program = ShaderProgram::new("fn main() {}");
728 builder.draw_shader(&program, Rect::new(0.0, 0.0, 4.0, 4.0), 0.0);
729
730 match &scene.commands()[0] {
731 Command::ShaderQuad { transform, .. } => assert_eq!(*transform, translate),
732 other => panic!("expected ShaderQuad, got {other:?}"),
733 }
734 }
735
736 #[test]
737 fn draw_shader_clones_program_sharing_id() {
738 let mut scene = Scene::new();
739 let mut builder = SceneBuilder::new(&mut scene);
740 let program = ShaderProgram::new("fn main() {}");
741 builder.draw_shader(&program, Rect::new(0.0, 0.0, 4.0, 4.0), 0.0);
742
743 match &scene.commands()[0] {
744 Command::ShaderQuad {
745 program: got_program,
746 ..
747 } => {
748 assert_eq!(got_program.id(), program.id());
749 assert_eq!(got_program.source(), program.source());
750 }
751 other => panic!("expected ShaderQuad, got {other:?}"),
752 }
753 }
754
755 #[test]
756 fn draw_blurred_rounded_rect_round_trips_fields_under_identity_transform() {
757 let mut scene = Scene::new();
758 let mut builder = SceneBuilder::new(&mut scene);
759 let rect = Rect::new(0.0, 0.0, 10.0, 8.0);
760 builder.draw_blurred_rounded_rect(rect, 4.0, 2.5, RED);
761
762 match &scene.commands()[0] {
763 Command::BlurredRoundedRect {
764 rect: got_rect,
765 radii,
766 std_dev,
767 color,
768 transform,
769 } => {
770 assert_eq!(*got_rect, rect);
771 assert_eq!(*radii, CornerRadii::uniform(4.0));
772 assert_eq!(*std_dev, 2.5);
773 assert_eq!(*color, RED);
774 assert_eq!(*transform, Affine::IDENTITY);
775 }
776 other => panic!("expected BlurredRoundedRect, got {other:?}"),
777 }
778 }
779
780 #[test]
781 fn draw_blurred_rounded_rect_composes_with_current_transform() {
782 let mut scene = Scene::new();
783 let mut builder = SceneBuilder::new(&mut scene);
784 let translate = Affine::translate((1.0, 2.0));
785 builder.push_transform(translate);
786 builder.draw_blurred_rounded_rect(Rect::new(0.0, 0.0, 5.0, 5.0), 2.0, 1.0, RED);
787
788 match &scene.commands()[0] {
789 Command::BlurredRoundedRect { transform, .. } => assert_eq!(*transform, translate),
790 other => panic!("expected BlurredRoundedRect, got {other:?}"),
791 }
792 }
793
794 #[test]
795 fn draw_blurred_rounded_rect_radii_round_trips_each_corner() {
796 let mut scene = Scene::new();
797 let mut builder = SceneBuilder::new(&mut scene);
798 let radii = CornerRadii::new(12.0, 12.0, 0.0, 0.0);
799 builder.draw_blurred_rounded_rect_radii(Rect::new(0.0, 0.0, 10.0, 8.0), radii, 2.5, RED);
800
801 match &scene.commands()[0] {
802 Command::BlurredRoundedRect { radii: r, .. } => assert_eq!(*r, radii),
803 other => panic!("expected BlurredRoundedRect, got {other:?}"),
804 }
805 }
806
807 #[test]
808 fn push_pop_layer_emit_commands_with_alpha_and_transform() {
809 let mut scene = Scene::new();
810 let mut builder = SceneBuilder::new(&mut scene);
811 let scale = Affine::scale(2.0);
812 builder.push_transform(scale);
813 let rect = Rect::new(0.0, 0.0, 5.0, 5.0);
814 builder.push_layer(rect, 0.5);
815 builder.pop_layer();
816
817 let commands = scene.commands();
818 assert_eq!(commands.len(), 2);
819 match &commands[0] {
820 Command::PushLayer {
821 rect: got_rect,
822 alpha,
823 transform,
824 } => {
825 assert_eq!(*got_rect, rect);
826 assert_eq!(*alpha, 0.5);
827 assert_eq!(*transform, scale);
828 }
829 other => panic!("expected PushLayer, got {other:?}"),
830 }
831 assert!(matches!(commands[1], Command::PopLayer));
832 }
833
834 #[test]
835 fn push_pop_snapshot_emit_commands_with_key_rect_alpha_scale_and_transform() {
836 let mut scene = Scene::new();
837 let mut builder = SceneBuilder::new(&mut scene);
838 let translate = Affine::translate((2.0, 3.0));
839 builder.push_transform(translate);
840 let rect = Rect::new(0.0, 0.0, 40.0, 40.0);
841 builder.push_snapshot(7, rect, 0.5, 0.9);
842 builder.fill_rect(Rect::new(0.0, 0.0, 1.0, 1.0), red_brush());
843 builder.pop_snapshot();
844
845 let commands = scene.commands();
846 assert_eq!(commands.len(), 3);
847 match &commands[0] {
848 Command::PushSnapshot {
849 key,
850 rect: got_rect,
851 alpha,
852 scale,
853 transform,
854 } => {
855 assert_eq!(*key, 7);
856 assert_eq!(*got_rect, rect);
857 assert_eq!(*alpha, 0.5);
858 assert_eq!(*scale, 0.9);
859 assert_eq!(*transform, translate);
860 }
861 other => panic!("expected PushSnapshot, got {other:?}"),
862 }
863 assert!(matches!(commands[1], Command::FillRect { .. }));
864 assert!(matches!(commands[2], Command::PopSnapshot));
865 }
866
867 #[test]
868 fn pop_snapshot_without_a_push_is_a_no_op() {
869 let mut scene = Scene::new();
870 let mut builder = SceneBuilder::new(&mut scene);
871 builder.pop_snapshot();
872 assert!(scene.commands().is_empty());
873 assert_eq!(scene.snapshot_depth, 0);
874 }
875
876 #[test]
877 fn nested_snapshot_brackets_balance() {
878 let mut scene = Scene::new();
879 let mut builder = SceneBuilder::new(&mut scene);
880 let outer = Rect::new(0.0, 0.0, 100.0, 100.0);
881 let inner = Rect::new(10.0, 10.0, 50.0, 50.0);
882
883 builder.push_snapshot(1, outer, 1.0, 1.0);
884 builder.push_snapshot(2, inner, 0.8, 1.2);
885 builder.pop_snapshot();
886 builder.pop_snapshot();
887 builder.pop_snapshot();
889
890 let commands = scene.commands();
891 assert_eq!(commands.len(), 4);
892 assert!(matches!(commands[0], Command::PushSnapshot { key: 1, .. }));
893 assert!(matches!(commands[1], Command::PushSnapshot { key: 2, .. }));
894 assert!(matches!(commands[2], Command::PopSnapshot));
895 assert!(matches!(commands[3], Command::PopSnapshot));
896 assert_eq!(scene.snapshot_depth, 0);
897 }
898
899 #[test]
900 fn push_snapshot_increments_depth_and_pop_decrements_it() {
901 let mut scene = Scene::new();
902 {
903 let mut builder = SceneBuilder::new(&mut scene);
904 builder.push_snapshot(1, Rect::new(0.0, 0.0, 1.0, 1.0), 1.0, 1.0);
905 }
906 assert_eq!(scene.snapshot_depth, 1);
907 {
908 let mut builder = SceneBuilder::new(&mut scene);
909 builder.pop_snapshot();
910 }
911 assert_eq!(scene.snapshot_depth, 0);
912 }
913
914 #[test]
915 fn clear_rect_records_command_with_current_transform() {
916 let mut scene = Scene::new();
917 let mut builder = SceneBuilder::new(&mut scene);
918 let translate = Affine::translate((7.0, 4.0));
919 builder.push_transform(translate);
920 let rect = Rect::new(0.0, 0.0, 80.0, 60.0);
921 builder.clear_rect(rect);
922
923 let commands = scene.commands();
924 assert_eq!(commands.len(), 1);
925 match &commands[0] {
926 Command::ClearRect {
927 rect: got_rect,
928 transform,
929 } => {
930 assert_eq!(*got_rect, rect);
931 assert_eq!(*transform, translate);
932 }
933 other => panic!("expected ClearRect, got {other:?}"),
934 }
935 }
936
937 #[test]
938 fn nested_clip_and_layer_preserve_push_pop_ordering() {
939 let mut scene = Scene::new();
942 let mut builder = SceneBuilder::new(&mut scene);
943 let clip_rect = Rect::new(0.0, 0.0, 100.0, 100.0);
944 let layer_rect = Rect::new(10.0, 10.0, 50.0, 50.0);
945
946 builder.push_clip(clip_rect);
947 builder.push_layer(layer_rect, 0.75);
948 builder.fill_rect(Rect::new(0.0, 0.0, 1.0, 1.0), red_brush());
949 builder.pop_layer();
950 builder.pop_clip();
951
952 let commands = scene.commands();
953 assert_eq!(commands.len(), 5);
954 assert!(matches!(commands[0], Command::PushClip { .. }));
955 assert!(matches!(commands[1], Command::PushLayer { .. }));
956 assert!(matches!(commands[2], Command::FillRect { .. }));
957 assert!(matches!(commands[3], Command::PopLayer));
958 assert!(matches!(commands[4], Command::PopClip));
959 }
960
961 fn triangle_path() -> BezPath {
962 let mut path = BezPath::new();
963 path.move_to((0.0, 0.0));
964 path.line_to((10.0, 0.0));
965 path.line_to((5.0, 10.0));
966 path.close_path();
967 path
968 }
969
970 #[test]
971 fn fill_path_round_trips_path_and_style_under_identity_transform() {
972 let mut scene = Scene::new();
973 let mut builder = SceneBuilder::new(&mut scene);
974 let path = triangle_path();
975 builder.fill_path(path.clone(), red_brush());
976
977 match &scene.commands()[0] {
978 Command::Path {
979 path: got_path,
980 style,
981 transform,
982 ..
983 } => {
984 assert_eq!(*got_path, path);
985 assert_eq!(*style, PathStyle::Fill);
986 assert_eq!(*transform, Affine::IDENTITY);
987 }
988 other => panic!("expected Path, got {other:?}"),
989 }
990 }
991
992 #[test]
993 fn stroke_path_round_trips_width_and_style() {
994 let mut scene = Scene::new();
995 let mut builder = SceneBuilder::new(&mut scene);
996 let path = triangle_path();
997 builder.stroke_path(path.clone(), 2.5, red_brush());
998
999 match &scene.commands()[0] {
1000 Command::Path {
1001 path: got_path,
1002 style,
1003 ..
1004 } => {
1005 assert_eq!(*got_path, path);
1006 assert_eq!(
1009 *style,
1010 PathStyle::Stroke {
1011 width: 2.5,
1012 dash: None
1013 }
1014 );
1015 }
1016 other => panic!("expected Path, got {other:?}"),
1017 }
1018 }
1019
1020 #[test]
1021 fn stroke_path_dashed_round_trips_its_pattern() {
1022 let mut scene = Scene::new();
1023 let mut builder = SceneBuilder::new(&mut scene);
1024 let path = triangle_path();
1025 let dash = DashPattern::new(4.0, 2.0).with_phase(1.0);
1026 builder.stroke_path_dashed(path.clone(), 2.5, dash, red_brush());
1027
1028 match &scene.commands()[0] {
1029 Command::Path {
1030 path: got_path,
1031 style,
1032 ..
1033 } => {
1034 assert_eq!(*got_path, path);
1035 assert_eq!(
1036 *style,
1037 PathStyle::Stroke {
1038 width: 2.5,
1039 dash: Some(dash)
1040 }
1041 );
1042 }
1043 other => panic!("expected Path, got {other:?}"),
1044 }
1045 }
1046
1047 #[test]
1048 fn scene_texture_round_trips_id_and_dest_under_identity_transform() {
1049 let mut scene = Scene::new();
1050 let mut builder = SceneBuilder::new(&mut scene);
1051 let dest = Rect::new(0.0, 0.0, 30.0, 40.0);
1052 builder.scene_texture(42, dest);
1053
1054 match &scene.commands()[0] {
1055 Command::SceneTexture {
1056 id,
1057 dest: got_dest,
1058 transform,
1059 } => {
1060 assert_eq!(*id, 42);
1061 assert_eq!(*got_dest, dest);
1062 assert_eq!(*transform, Affine::IDENTITY);
1063 }
1064 other => panic!("expected SceneTexture, got {other:?}"),
1065 }
1066 }
1067
1068 #[test]
1069 fn scene_texture_composes_with_current_transform() {
1070 let mut scene = Scene::new();
1071 let mut builder = SceneBuilder::new(&mut scene);
1072 let translate = Affine::translate((5.0, 6.0));
1073 builder.push_transform(translate);
1074 builder.scene_texture(7, Rect::new(0.0, 0.0, 4.0, 4.0));
1075
1076 match &scene.commands()[0] {
1077 Command::SceneTexture { transform, .. } => assert_eq!(*transform, translate),
1078 other => panic!("expected SceneTexture, got {other:?}"),
1079 }
1080 }
1081
1082 #[test]
1083 fn fill_path_composes_with_current_transform() {
1084 let mut scene = Scene::new();
1085 let mut builder = SceneBuilder::new(&mut scene);
1086 let translate = Affine::translate((3.0, 4.0));
1087 builder.push_transform(translate);
1088 builder.fill_path(triangle_path(), red_brush());
1089
1090 match &scene.commands()[0] {
1091 Command::Path { transform, .. } => assert_eq!(*transform, translate),
1092 other => panic!("expected Path, got {other:?}"),
1093 }
1094 }
1095}