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