1use crate::{Brush, Color, ColorFilter, ImageBitmap};
4use std::ops::AddAssign;
5
6#[derive(Clone, Copy, Debug, PartialEq, Default)]
7pub struct Point {
8 pub x: f32,
9 pub y: f32,
10}
11
12impl Point {
13 pub const fn new(x: f32, y: f32) -> Self {
14 Self { x, y }
15 }
16
17 pub const ZERO: Point = Point { x: 0.0, y: 0.0 };
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Default)]
21pub struct Size {
22 pub width: f32,
23 pub height: f32,
24}
25
26impl Size {
27 pub const fn new(width: f32, height: f32) -> Self {
28 Self { width, height }
29 }
30
31 pub const ZERO: Size = Size {
32 width: 0.0,
33 height: 0.0,
34 };
35}
36
37#[derive(Clone, Copy, Debug, PartialEq)]
38pub struct Rect {
39 pub x: f32,
40 pub y: f32,
41 pub width: f32,
42 pub height: f32,
43}
44
45impl Rect {
46 pub fn from_origin_size(origin: Point, size: Size) -> Self {
47 Self {
48 x: origin.x,
49 y: origin.y,
50 width: size.width,
51 height: size.height,
52 }
53 }
54
55 pub fn from_size(size: Size) -> Self {
56 Self {
57 x: 0.0,
58 y: 0.0,
59 width: size.width,
60 height: size.height,
61 }
62 }
63
64 pub fn translate(&self, dx: f32, dy: f32) -> Self {
65 Self {
66 x: self.x + dx,
67 y: self.y + dy,
68 width: self.width,
69 height: self.height,
70 }
71 }
72
73 pub fn contains(&self, x: f32, y: f32) -> bool {
74 x >= self.x && y >= self.y && x <= self.x + self.width && y <= self.y + self.height
75 }
76
77 pub fn intersect(&self, other: Rect) -> Option<Rect> {
79 let left = self.x.max(other.x);
80 let top = self.y.max(other.y);
81 let right = (self.x + self.width).min(other.x + other.width);
82 let bottom = (self.y + self.height).min(other.y + other.height);
83 let width = right - left;
84 let height = bottom - top;
85 if width <= 0.0 || height <= 0.0 {
86 None
87 } else {
88 Some(Rect {
89 x: left,
90 y: top,
91 width,
92 height,
93 })
94 }
95 }
96
97 pub fn union(&self, other: Rect) -> Rect {
98 let left = self.x.min(other.x);
99 let top = self.y.min(other.y);
100 let right = (self.x + self.width).max(other.x + other.width);
101 let bottom = (self.y + self.height).max(other.y + other.height);
102 Rect {
103 x: left,
104 y: top,
105 width: (right - left).max(0.0),
106 height: (bottom - top).max(0.0),
107 }
108 }
109}
110
111#[derive(Clone, Copy, Debug, Default, PartialEq)]
113pub struct EdgeInsets {
114 pub left: f32,
115 pub top: f32,
116 pub right: f32,
117 pub bottom: f32,
118}
119
120impl EdgeInsets {
121 pub fn uniform(all: f32) -> Self {
122 Self {
123 left: all,
124 top: all,
125 right: all,
126 bottom: all,
127 }
128 }
129
130 pub fn horizontal(horizontal: f32) -> Self {
131 Self {
132 left: horizontal,
133 right: horizontal,
134 ..Self::default()
135 }
136 }
137
138 pub fn vertical(vertical: f32) -> Self {
139 Self {
140 top: vertical,
141 bottom: vertical,
142 ..Self::default()
143 }
144 }
145
146 pub fn symmetric(horizontal: f32, vertical: f32) -> Self {
147 Self {
148 left: horizontal,
149 right: horizontal,
150 top: vertical,
151 bottom: vertical,
152 }
153 }
154
155 pub fn from_components(left: f32, top: f32, right: f32, bottom: f32) -> Self {
156 Self {
157 left,
158 top,
159 right,
160 bottom,
161 }
162 }
163
164 pub fn is_zero(&self) -> bool {
165 self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
166 }
167
168 pub fn horizontal_sum(&self) -> f32 {
169 self.left + self.right
170 }
171
172 pub fn vertical_sum(&self) -> f32 {
173 self.top + self.bottom
174 }
175}
176
177impl AddAssign for EdgeInsets {
178 fn add_assign(&mut self, rhs: Self) {
179 self.left += rhs.left;
180 self.top += rhs.top;
181 self.right += rhs.right;
182 self.bottom += rhs.bottom;
183 }
184}
185
186#[derive(Clone, Copy, Debug, Default, PartialEq)]
187pub struct CornerRadii {
188 pub top_left: f32,
189 pub top_right: f32,
190 pub bottom_right: f32,
191 pub bottom_left: f32,
192}
193
194impl CornerRadii {
195 pub fn uniform(radius: f32) -> Self {
196 Self {
197 top_left: radius,
198 top_right: radius,
199 bottom_right: radius,
200 bottom_left: radius,
201 }
202 }
203}
204
205#[derive(Clone, Copy, Debug, PartialEq)]
206pub struct RoundedCornerShape {
207 radii: CornerRadii,
208}
209
210impl RoundedCornerShape {
211 pub fn new(top_left: f32, top_right: f32, bottom_right: f32, bottom_left: f32) -> Self {
212 Self {
213 radii: CornerRadii {
214 top_left,
215 top_right,
216 bottom_right,
217 bottom_left,
218 },
219 }
220 }
221
222 pub fn uniform(radius: f32) -> Self {
223 Self {
224 radii: CornerRadii::uniform(radius),
225 }
226 }
227
228 pub fn with_radii(radii: CornerRadii) -> Self {
229 Self { radii }
230 }
231
232 pub fn resolve(&self, width: f32, height: f32) -> CornerRadii {
233 let mut resolved = self.radii;
234 let max_width = (width / 2.0).max(0.0);
235 let max_height = (height / 2.0).max(0.0);
236 resolved.top_left = resolved.top_left.clamp(0.0, max_width).min(max_height);
237 resolved.top_right = resolved.top_right.clamp(0.0, max_width).min(max_height);
238 resolved.bottom_right = resolved.bottom_right.clamp(0.0, max_width).min(max_height);
239 resolved.bottom_left = resolved.bottom_left.clamp(0.0, max_width).min(max_height);
240 resolved
241 }
242
243 pub fn radii(&self) -> CornerRadii {
244 self.radii
245 }
246}
247
248#[derive(Clone, Copy, Debug, PartialEq)]
249pub struct TransformOrigin {
250 pub pivot_fraction_x: f32,
251 pub pivot_fraction_y: f32,
252}
253
254impl TransformOrigin {
255 pub const fn new(pivot_fraction_x: f32, pivot_fraction_y: f32) -> Self {
256 Self {
257 pivot_fraction_x,
258 pivot_fraction_y,
259 }
260 }
261
262 pub const CENTER: TransformOrigin = TransformOrigin::new(0.5, 0.5);
263}
264
265impl Default for TransformOrigin {
266 fn default() -> Self {
267 Self::CENTER
268 }
269}
270
271#[derive(Clone, Copy, Debug, Default, PartialEq)]
272pub enum LayerShape {
273 #[default]
274 Rectangle,
275 Rounded(RoundedCornerShape),
276}
277
278#[derive(Clone, Debug, PartialEq)]
279pub struct GraphicsLayer {
280 pub alpha: f32,
281 pub scale: f32,
282 pub scale_x: f32,
283 pub scale_y: f32,
284 pub rotation_x: f32,
285 pub rotation_y: f32,
286 pub rotation_z: f32,
287 pub camera_distance: f32,
288 pub transform_origin: TransformOrigin,
289 pub translation_x: f32,
290 pub translation_y: f32,
291 pub shadow_elevation: f32,
292 pub ambient_shadow_color: Color,
293 pub spot_shadow_color: Color,
294 pub shape: LayerShape,
295 pub clip: bool,
296 pub compositing_strategy: CompositingStrategy,
297 pub blend_mode: BlendMode,
298 pub color_filter: Option<ColorFilter>,
299 pub render_effect: Option<crate::render_effect::RenderEffect>,
300 pub backdrop_effect: Option<crate::render_effect::RenderEffect>,
301}
302
303impl Default for GraphicsLayer {
304 fn default() -> Self {
305 Self {
306 alpha: 1.0,
307 scale: 1.0,
308 scale_x: 1.0,
309 scale_y: 1.0,
310 rotation_x: 0.0,
311 rotation_y: 0.0,
312 rotation_z: 0.0,
313 camera_distance: 8.0,
314 transform_origin: TransformOrigin::CENTER,
315 translation_x: 0.0,
316 translation_y: 0.0,
317 shadow_elevation: 0.0,
318 ambient_shadow_color: Color::BLACK,
319 spot_shadow_color: Color::BLACK,
320 shape: LayerShape::Rectangle,
321 clip: false,
322 compositing_strategy: CompositingStrategy::Auto,
323 blend_mode: BlendMode::SrcOver,
324 color_filter: None,
325 render_effect: None,
326 backdrop_effect: None,
327 }
328 }
329}
330
331#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
336pub enum BlendMode {
337 Clear,
338 Src,
339 Dst,
340 #[default]
341 SrcOver,
342 DstOver,
343 SrcIn,
344 DstIn,
345 SrcOut,
346 DstOut,
347 SrcAtop,
348 DstAtop,
349 Xor,
350 Plus,
351 Modulate,
352 Screen,
353 Overlay,
354 Darken,
355 Lighten,
356 ColorDodge,
357 ColorBurn,
358 HardLight,
359 SoftLight,
360 Difference,
361 Exclusion,
362 Multiply,
363 Hue,
364 Saturation,
365 Color,
366 Luminosity,
367}
368
369#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
371pub enum CompositingStrategy {
372 #[default]
374 Auto,
375 Offscreen,
377 ModulateAlpha,
379}
380
381#[derive(Clone, Debug, PartialEq)]
382pub enum DrawPrimitive {
383 Content,
386 Blend {
388 primitive: Box<DrawPrimitive>,
389 blend_mode: BlendMode,
390 },
391 Rect {
392 rect: Rect,
393 brush: Brush,
394 },
395 RoundRect {
396 rect: Rect,
397 brush: Brush,
398 radii: CornerRadii,
399 },
400 Image {
401 rect: Rect,
402 image: ImageBitmap,
403 alpha: f32,
404 color_filter: Option<ColorFilter>,
405 src_rect: Option<Rect>,
409 },
410 Shadow(ShadowPrimitive),
413}
414
415#[derive(Clone, Debug, PartialEq)]
417pub enum ShadowPrimitive {
418 Drop {
420 shape: Box<DrawPrimitive>,
421 blur_radius: f32,
422 blend_mode: BlendMode,
423 },
424 Inner {
426 fill: Box<DrawPrimitive>,
427 cutout: Box<DrawPrimitive>,
428 blur_radius: f32,
429 blend_mode: BlendMode,
430 clip_rect: Rect,
432 },
433}
434
435pub trait DrawScope {
436 fn size(&self) -> Size;
437 fn draw_content(&mut self);
438 fn draw_rect(&mut self, brush: Brush);
439 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode);
440 fn draw_rect_at(&mut self, rect: Rect, brush: Brush);
442 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode);
443 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii);
444 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode);
445 fn draw_image(&mut self, image: ImageBitmap);
446 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode);
447 fn draw_image_at(
448 &mut self,
449 rect: Rect,
450 image: ImageBitmap,
451 alpha: f32,
452 color_filter: Option<ColorFilter>,
453 );
454 fn draw_image_at_blend(
455 &mut self,
456 rect: Rect,
457 image: ImageBitmap,
458 alpha: f32,
459 color_filter: Option<ColorFilter>,
460 blend_mode: BlendMode,
461 );
462 fn draw_image_src(
465 &mut self,
466 image: ImageBitmap,
467 src_rect: Rect,
468 dst_rect: Rect,
469 alpha: f32,
470 color_filter: Option<ColorFilter>,
471 );
472 fn draw_image_src_blend(
473 &mut self,
474 image: ImageBitmap,
475 src_rect: Rect,
476 dst_rect: Rect,
477 alpha: f32,
478 color_filter: Option<ColorFilter>,
479 blend_mode: BlendMode,
480 );
481 fn into_primitives(self) -> Vec<DrawPrimitive>;
482}
483
484#[derive(Default)]
485pub struct DrawScopeDefault {
486 size: Size,
487 primitives: Vec<DrawPrimitive>,
488}
489
490impl DrawScopeDefault {
491 pub fn new(size: Size) -> Self {
492 Self {
493 size,
494 primitives: Vec::new(),
495 }
496 }
497
498 fn push_blended_primitive(&mut self, primitive: DrawPrimitive, blend_mode: BlendMode) {
499 if blend_mode == BlendMode::SrcOver {
500 self.primitives.push(primitive);
501 } else {
502 self.primitives.push(DrawPrimitive::Blend {
503 primitive: Box::new(primitive),
504 blend_mode,
505 });
506 }
507 }
508}
509
510impl DrawScope for DrawScopeDefault {
511 fn size(&self) -> Size {
512 self.size
513 }
514
515 fn draw_content(&mut self) {
516 self.primitives.push(DrawPrimitive::Content);
517 }
518
519 fn draw_rect(&mut self, brush: Brush) {
520 self.draw_rect_blend(brush, BlendMode::SrcOver);
521 }
522
523 fn draw_rect_blend(&mut self, brush: Brush, blend_mode: BlendMode) {
524 self.push_blended_primitive(
525 DrawPrimitive::Rect {
526 rect: Rect::from_size(self.size),
527 brush,
528 },
529 blend_mode,
530 );
531 }
532
533 fn draw_rect_at(&mut self, rect: Rect, brush: Brush) {
534 self.draw_rect_at_blend(rect, brush, BlendMode::SrcOver);
535 }
536
537 fn draw_rect_at_blend(&mut self, rect: Rect, brush: Brush, blend_mode: BlendMode) {
538 self.push_blended_primitive(DrawPrimitive::Rect { rect, brush }, blend_mode);
539 }
540
541 fn draw_round_rect(&mut self, brush: Brush, radii: CornerRadii) {
542 self.draw_round_rect_blend(brush, radii, BlendMode::SrcOver);
543 }
544
545 fn draw_round_rect_blend(&mut self, brush: Brush, radii: CornerRadii, blend_mode: BlendMode) {
546 self.push_blended_primitive(
547 DrawPrimitive::RoundRect {
548 rect: Rect::from_size(self.size),
549 brush,
550 radii,
551 },
552 blend_mode,
553 );
554 }
555
556 fn draw_image(&mut self, image: ImageBitmap) {
557 self.draw_image_blend(image, BlendMode::SrcOver);
558 }
559
560 fn draw_image_blend(&mut self, image: ImageBitmap, blend_mode: BlendMode) {
561 self.push_blended_primitive(
562 DrawPrimitive::Image {
563 rect: Rect::from_size(self.size),
564 image,
565 alpha: 1.0,
566 color_filter: None,
567 src_rect: None,
568 },
569 blend_mode,
570 );
571 }
572
573 fn draw_image_at(
574 &mut self,
575 rect: Rect,
576 image: ImageBitmap,
577 alpha: f32,
578 color_filter: Option<ColorFilter>,
579 ) {
580 self.draw_image_at_blend(rect, image, alpha, color_filter, BlendMode::SrcOver);
581 }
582
583 fn draw_image_at_blend(
584 &mut self,
585 rect: Rect,
586 image: ImageBitmap,
587 alpha: f32,
588 color_filter: Option<ColorFilter>,
589 blend_mode: BlendMode,
590 ) {
591 self.push_blended_primitive(
592 DrawPrimitive::Image {
593 rect,
594 image,
595 alpha: alpha.clamp(0.0, 1.0),
596 color_filter,
597 src_rect: None,
598 },
599 blend_mode,
600 );
601 }
602
603 fn draw_image_src(
604 &mut self,
605 image: ImageBitmap,
606 src_rect: Rect,
607 dst_rect: Rect,
608 alpha: f32,
609 color_filter: Option<ColorFilter>,
610 ) {
611 self.draw_image_src_blend(
612 image,
613 src_rect,
614 dst_rect,
615 alpha,
616 color_filter,
617 BlendMode::SrcOver,
618 );
619 }
620
621 fn draw_image_src_blend(
622 &mut self,
623 image: ImageBitmap,
624 src_rect: Rect,
625 dst_rect: Rect,
626 alpha: f32,
627 color_filter: Option<ColorFilter>,
628 blend_mode: BlendMode,
629 ) {
630 self.push_blended_primitive(
631 DrawPrimitive::Image {
632 rect: dst_rect,
633 image,
634 alpha: alpha.clamp(0.0, 1.0),
635 color_filter,
636 src_rect: Some(src_rect),
637 },
638 blend_mode,
639 );
640 }
641
642 fn into_primitives(self) -> Vec<DrawPrimitive> {
643 self.primitives
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use crate::{Color, ImageBitmap, RenderEffect};
651
652 fn assert_image_alpha(primitive: &DrawPrimitive, expected: f32) {
653 match primitive {
654 DrawPrimitive::Image { alpha, .. } => assert!((alpha - expected).abs() < 1e-5),
655 DrawPrimitive::Blend { primitive, .. } => assert_image_alpha(primitive, expected),
656 other => panic!("expected image primitive, got {other:?}"),
657 }
658 }
659
660 fn unwrap_image(primitive: &DrawPrimitive) -> &DrawPrimitive {
661 match primitive {
662 DrawPrimitive::Image { .. } => primitive,
663 DrawPrimitive::Blend { primitive, .. } => unwrap_image(primitive),
664 other => panic!("expected image primitive, got {other:?}"),
665 }
666 }
667
668 #[test]
669 fn draw_content_inserts_content_marker() {
670 let mut scope = DrawScopeDefault::new(Size::new(8.0, 8.0));
671 scope.draw_rect(Brush::solid(Color::WHITE));
672 scope.draw_content();
673 scope.draw_rect_blend(Brush::solid(Color::BLACK), BlendMode::DstOut);
674
675 let primitives = scope.into_primitives();
676 assert!(matches!(primitives[1], DrawPrimitive::Content));
677 assert!(matches!(
678 primitives[2],
679 DrawPrimitive::Blend {
680 blend_mode: BlendMode::DstOut,
681 ..
682 }
683 ));
684 }
685
686 #[test]
687 fn draw_rect_blend_wraps_non_default_modes() {
688 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
689 scope.draw_rect_blend(Brush::solid(Color::RED), BlendMode::DstOut);
690
691 let primitives = scope.into_primitives();
692 assert_eq!(primitives.len(), 1);
693 match &primitives[0] {
694 DrawPrimitive::Blend {
695 primitive,
696 blend_mode,
697 } => {
698 assert_eq!(*blend_mode, BlendMode::DstOut);
699 assert!(matches!(**primitive, DrawPrimitive::Rect { .. }));
700 }
701 other => panic!("expected blended primitive, got {other:?}"),
702 }
703 }
704
705 #[test]
706 fn rect_union_encloses_both_inputs() {
707 let lhs = Rect {
708 x: 10.0,
709 y: 5.0,
710 width: 8.0,
711 height: 4.0,
712 };
713 let rhs = Rect {
714 x: 4.0,
715 y: 7.0,
716 width: 10.0,
717 height: 6.0,
718 };
719
720 assert_eq!(
721 lhs.union(rhs),
722 Rect {
723 x: 4.0,
724 y: 5.0,
725 width: 14.0,
726 height: 8.0,
727 }
728 );
729 }
730
731 #[test]
732 fn draw_image_uses_scope_size_as_default_rect() {
733 let mut scope = DrawScopeDefault::new(Size::new(40.0, 24.0));
734 let image = ImageBitmap::from_rgba8(2, 2, vec![255; 16]).expect("image");
735 scope.draw_image(image.clone());
736 let primitives = scope.into_primitives();
737 assert_eq!(primitives.len(), 1);
738 match unwrap_image(&primitives[0]) {
739 DrawPrimitive::Image {
740 rect,
741 image: actual,
742 alpha,
743 color_filter,
744 src_rect,
745 } => {
746 assert_eq!(*rect, Rect::from_size(Size::new(40.0, 24.0)));
747 assert_eq!(*actual, image);
748 assert_eq!(*alpha, 1.0);
749 assert!(color_filter.is_none());
750 assert!(src_rect.is_none());
751 }
752 other => panic!("expected image primitive, got {other:?}"),
753 }
754 }
755
756 #[test]
757 fn draw_image_src_stores_src_rect() {
758 let mut scope = DrawScopeDefault::new(Size::new(100.0, 100.0));
759 let image = ImageBitmap::from_rgba8(64, 64, vec![255; 64 * 64 * 4]).expect("image");
760 let src = Rect {
761 x: 10.0,
762 y: 20.0,
763 width: 30.0,
764 height: 40.0,
765 };
766 let dst = Rect {
767 x: 0.0,
768 y: 0.0,
769 width: 60.0,
770 height: 80.0,
771 };
772 scope.draw_image_src(image.clone(), src, dst, 0.8, None);
773 let primitives = scope.into_primitives();
774 assert_eq!(primitives.len(), 1);
775 match unwrap_image(&primitives[0]) {
776 DrawPrimitive::Image {
777 rect,
778 image: actual,
779 alpha,
780 src_rect,
781 ..
782 } => {
783 assert_eq!(*rect, dst);
784 assert_eq!(*actual, image);
785 assert!((alpha - 0.8).abs() < 1e-5);
786 assert_eq!(*src_rect, Some(src));
787 }
788 other => panic!("expected image primitive, got {other:?}"),
789 }
790 }
791
792 #[test]
793 fn draw_image_at_clamps_alpha() {
794 let mut scope = DrawScopeDefault::new(Size::new(10.0, 10.0));
795 let image = ImageBitmap::from_rgba8(1, 1, vec![255, 255, 255, 255]).expect("image");
796 scope.draw_image_at(
797 Rect::from_origin_size(Point::new(2.0, 3.0), Size::new(5.0, 6.0)),
798 image,
799 3.0,
800 Some(ColorFilter::Tint(Color::from_rgba_u8(128, 128, 255, 255))),
801 );
802 assert_image_alpha(&scope.into_primitives()[0], 1.0);
803 }
804
805 #[test]
806 fn graphics_layer_clone_with_render_effect() {
807 let layer = GraphicsLayer {
808 render_effect: Some(RenderEffect::blur(10.0)),
809 backdrop_effect: Some(RenderEffect::blur(6.0)),
810 color_filter: Some(ColorFilter::tint(Color::from_rgba_u8(128, 200, 255, 255))),
811 alpha: 0.5,
812 rotation_z: 12.0,
813 shadow_elevation: 4.0,
814 shape: LayerShape::Rounded(RoundedCornerShape::uniform(6.0)),
815 clip: true,
816 compositing_strategy: CompositingStrategy::Offscreen,
817 blend_mode: BlendMode::SrcOver,
818 ..Default::default()
819 };
820 let cloned = layer.clone();
821 assert_eq!(cloned.alpha, 0.5);
822 assert!(cloned.render_effect.is_some());
823 assert!(cloned.backdrop_effect.is_some());
824 assert_eq!(layer.color_filter, cloned.color_filter);
825 assert_eq!(layer.render_effect, cloned.render_effect);
826 assert_eq!(layer.backdrop_effect, cloned.backdrop_effect);
827 assert!((cloned.rotation_z - 12.0).abs() < 1e-6);
828 assert!((cloned.shadow_elevation - 4.0).abs() < 1e-6);
829 assert_eq!(
830 cloned.shape,
831 LayerShape::Rounded(RoundedCornerShape::uniform(6.0))
832 );
833 assert!(cloned.clip);
834 assert_eq!(cloned.compositing_strategy, CompositingStrategy::Offscreen);
835 assert_eq!(cloned.blend_mode, BlendMode::SrcOver);
836 }
837
838 #[test]
839 fn graphics_layer_default_has_no_effect() {
840 let layer = GraphicsLayer::default();
841 assert!(layer.color_filter.is_none());
842 assert!(layer.render_effect.is_none());
843 assert!(layer.backdrop_effect.is_none());
844 assert_eq!(layer.compositing_strategy, CompositingStrategy::Auto);
845 assert_eq!(layer.blend_mode, BlendMode::SrcOver);
846 assert_eq!(layer.alpha, 1.0);
847 assert_eq!(layer.transform_origin, TransformOrigin::CENTER);
848 assert!((layer.camera_distance - 8.0).abs() < 1e-6);
849 assert_eq!(layer.shape, LayerShape::Rectangle);
850 assert!(!layer.clip);
851 assert_eq!(layer.ambient_shadow_color, Color::BLACK);
852 assert_eq!(layer.spot_shadow_color, Color::BLACK);
853 }
854
855 #[test]
856 fn transform_origin_construction() {
857 let origin = TransformOrigin::new(0.25, 0.75);
858 assert!((origin.pivot_fraction_x - 0.25).abs() < 1e-6);
859 assert!((origin.pivot_fraction_y - 0.75).abs() < 1e-6);
860 }
861
862 #[test]
863 fn layer_shape_default_is_rectangle() {
864 assert_eq!(LayerShape::default(), LayerShape::Rectangle);
865 }
866}