1use crate::Point;
4use crate::{BlendMode, Brush, Color, Dp};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct DpOffset {
9 pub x: Dp,
10 pub y: Dp,
11}
12
13impl DpOffset {
14 pub const fn new(x: Dp, y: Dp) -> Self {
15 Self { x, y }
16 }
17
18 pub const ZERO: DpOffset = DpOffset {
19 x: Dp(0.0),
20 y: Dp(0.0),
21 };
22
23 pub fn to_px(self, density: f32) -> Point {
24 Point::new(self.x.to_px(density), self.y.to_px(density))
25 }
26}
27
28impl Default for DpOffset {
29 fn default() -> Self {
30 Self::ZERO
31 }
32}
33
34#[derive(Clone, Debug, PartialEq)]
39pub struct Shadow {
40 pub radius: Dp,
41 pub spread: Dp,
42 pub offset: DpOffset,
43 pub color: Color,
44 pub brush: Option<Brush>,
45 pub alpha: f32,
46 pub blend_mode: BlendMode,
47 pub cutout: bool,
52}
53
54impl Default for Shadow {
55 fn default() -> Self {
56 Self {
57 radius: Dp(0.0),
58 spread: Dp(0.0),
59 offset: DpOffset::ZERO,
60 color: Color::BLACK,
61 brush: None,
62 alpha: 1.0,
63 blend_mode: BlendMode::SrcOver,
64 cutout: false,
65 }
66 }
67}
68
69impl Shadow {
70 pub fn to_scope(&self, density: f32) -> ShadowScope {
71 ShadowScope {
72 radius: self.radius.to_px(density),
73 spread: self.spread.to_px(density),
74 offset: self.offset.to_px(density),
75 color: self.color,
76 brush: self.brush.clone(),
77 alpha: self.alpha,
78 blend_mode: self.blend_mode,
79 cutout: self.cutout,
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq)]
86pub struct ShadowScope {
87 pub radius: f32,
88 pub spread: f32,
89 pub offset: Point,
90 pub color: Color,
91 pub brush: Option<Brush>,
92 pub alpha: f32,
93 pub blend_mode: BlendMode,
94 pub cutout: bool,
97}
98
99impl Default for ShadowScope {
100 fn default() -> Self {
101 Self {
102 radius: 0.0,
103 spread: 0.0,
104 offset: Point::ZERO,
105 color: Color::BLACK,
106 brush: None,
107 alpha: 1.0,
108 blend_mode: BlendMode::SrcOver,
109 cutout: false,
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn dp_offset_converts_to_px() {
120 let offset = DpOffset::new(Dp(4.0), Dp(-2.5));
121 let px = offset.to_px(2.0);
122 assert_eq!(px, Point::new(8.0, -5.0));
123 }
124
125 #[test]
126 fn shadow_default_matches_black_src_over() {
127 let shadow = Shadow::default();
128 assert_eq!(shadow.radius, Dp(0.0));
129 assert_eq!(shadow.spread, Dp(0.0));
130 assert_eq!(shadow.offset, DpOffset::ZERO);
131 assert_eq!(shadow.color, Color::BLACK);
132 assert_eq!(shadow.brush, None);
133 assert!((shadow.alpha - 1.0).abs() < 1e-6);
134 assert_eq!(shadow.blend_mode, BlendMode::SrcOver);
135 }
136
137 #[test]
138 fn shadow_to_scope_uses_density() {
139 let shadow = Shadow {
140 radius: Dp(5.0),
141 spread: Dp(2.0),
142 offset: DpOffset::new(Dp(-1.0), Dp(3.0)),
143 color: Color::from_rgba_u8(10, 20, 30, 255),
144 brush: None,
145 alpha: 0.7,
146 blend_mode: BlendMode::Overlay,
147 cutout: false,
148 };
149 let scope = shadow.to_scope(2.0);
150 assert!((scope.radius - 10.0).abs() < 1e-6);
151 assert!((scope.spread - 4.0).abs() < 1e-6);
152 assert_eq!(scope.offset, Point::new(-2.0, 6.0));
153 assert_eq!(scope.color, Color::from_rgba_u8(10, 20, 30, 255));
154 assert!((scope.alpha - 0.7).abs() < 1e-6);
155 assert_eq!(scope.blend_mode, BlendMode::Overlay);
156 }
157}