cranpose_ui_graphics/
shadow.rs1use 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}
48
49impl Default for Shadow {
50 fn default() -> Self {
51 Self {
52 radius: Dp(0.0),
53 spread: Dp(0.0),
54 offset: DpOffset::ZERO,
55 color: Color::BLACK,
56 brush: None,
57 alpha: 1.0,
58 blend_mode: BlendMode::SrcOver,
59 }
60 }
61}
62
63impl Shadow {
64 pub fn to_scope(&self, density: f32) -> ShadowScope {
65 ShadowScope {
66 radius: self.radius.to_px(density),
67 spread: self.spread.to_px(density),
68 offset: self.offset.to_px(density),
69 color: self.color,
70 brush: self.brush.clone(),
71 alpha: self.alpha,
72 blend_mode: self.blend_mode,
73 }
74 }
75}
76
77#[derive(Clone, Debug, PartialEq)]
79pub struct ShadowScope {
80 pub radius: f32,
81 pub spread: f32,
82 pub offset: Point,
83 pub color: Color,
84 pub brush: Option<Brush>,
85 pub alpha: f32,
86 pub blend_mode: BlendMode,
87}
88
89impl Default for ShadowScope {
90 fn default() -> Self {
91 Self {
92 radius: 0.0,
93 spread: 0.0,
94 offset: Point::ZERO,
95 color: Color::BLACK,
96 brush: None,
97 alpha: 1.0,
98 blend_mode: BlendMode::SrcOver,
99 }
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn dp_offset_converts_to_px() {
109 let offset = DpOffset::new(Dp(4.0), Dp(-2.5));
110 let px = offset.to_px(2.0);
111 assert_eq!(px, Point::new(8.0, -5.0));
112 }
113
114 #[test]
115 fn shadow_default_matches_black_src_over() {
116 let shadow = Shadow::default();
117 assert_eq!(shadow.radius, Dp(0.0));
118 assert_eq!(shadow.spread, Dp(0.0));
119 assert_eq!(shadow.offset, DpOffset::ZERO);
120 assert_eq!(shadow.color, Color::BLACK);
121 assert_eq!(shadow.brush, None);
122 assert!((shadow.alpha - 1.0).abs() < 1e-6);
123 assert_eq!(shadow.blend_mode, BlendMode::SrcOver);
124 }
125
126 #[test]
127 fn shadow_to_scope_uses_density() {
128 let shadow = Shadow {
129 radius: Dp(5.0),
130 spread: Dp(2.0),
131 offset: DpOffset::new(Dp(-1.0), Dp(3.0)),
132 color: Color::from_rgba_u8(10, 20, 30, 255),
133 brush: None,
134 alpha: 0.7,
135 blend_mode: BlendMode::Overlay,
136 };
137 let scope = shadow.to_scope(2.0);
138 assert!((scope.radius - 10.0).abs() < 1e-6);
139 assert!((scope.spread - 4.0).abs() < 1e-6);
140 assert_eq!(scope.offset, Point::new(-2.0, 6.0));
141 assert_eq!(scope.color, Color::from_rgba_u8(10, 20, 30, 255));
142 assert!((scope.alpha - 0.7).abs() < 1e-6);
143 assert_eq!(scope.blend_mode, BlendMode::Overlay);
144 }
145}