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