1#[derive(Debug, Clone, Copy)]
10pub struct SpotlightCard {
11 pub width: f32,
13
14 pub height: f32,
16
17 pub radius: f32,
19
20 spotlight_x: f32,
22
23 spotlight_y: f32,
25
26 is_active: bool,
28}
29
30impl Default for SpotlightCard {
31 fn default() -> Self {
32 Self::new(400.0, 300.0)
33 }
34}
35
36impl SpotlightCard {
37 pub fn new(width: f32, height: f32) -> Self {
39 Self {
40 width,
41 height,
42 radius: 200.0,
43 spotlight_x: width / 2.0,
44 spotlight_y: height / 2.0,
45 is_active: false,
46 }
47 }
48
49 pub fn with_radius(mut self, radius: f32) -> Self {
51 self.radius = radius;
52 self
53 }
54
55 pub fn set_dimensions(&mut self, width: f32, height: f32) {
57 self.width = width;
58 self.height = height;
59 }
60
61 pub fn update_cursor(
69 &mut self,
70 cursor_x: f32,
71 cursor_y: f32,
72 card_x: f32,
73 card_y: f32,
74 ) {
75 self.spotlight_x = cursor_x - card_x;
77 self.spotlight_y = cursor_y - card_y;
78
79 self.is_active = self.spotlight_x >= 0.0
81 && self.spotlight_x <= self.width
82 && self.spotlight_y >= 0.0
83 && self.spotlight_y <= self.height;
84 }
85
86 pub fn set_spotlight_position(&mut self, x: f32, y: f32) {
88 self.spotlight_x = x;
89 self.spotlight_y = y;
90
91 self.is_active = x >= 0.0
92 && x <= self.width
93 && y >= 0.0
94 && y <= self.height;
95 }
96
97 pub fn spotlight_x(&self) -> f32 {
99 self.spotlight_x
100 }
101
102 pub fn spotlight_y(&self) -> f32 {
104 self.spotlight_y
105 }
106
107 pub fn spotlight_center(&self) -> (f32, f32) {
109 (self.spotlight_x, self.spotlight_y)
110 }
111
112 pub fn is_active(&self) -> bool {
114 self.is_active
115 }
116
117 pub fn deactivate(&mut self) {
119 self.is_active = false;
120 }
121
122 pub fn normalized_position(&self) -> (f32, f32) {
124 let x = if self.width > 0.0 {
125 (self.spotlight_x / self.width).clamp(0.0, 1.0)
126 } else {
127 0.5
128 };
129
130 let y = if self.height > 0.0 {
131 (self.spotlight_y / self.height).clamp(0.0, 1.0)
132 } else {
133 0.5
134 };
135
136 (x, y)
137 }
138
139 pub fn gradient_params(&self) -> (f32, f32, f32, f32) {
146 let opacity = if self.is_active { 1.0 } else { 0.0 };
147 (self.spotlight_x, self.spotlight_y, self.radius, opacity)
148 }
149}
150
151#[derive(Debug, Clone, Copy)]
153pub struct SpotlightColor {
154 pub r: u8,
155 pub g: u8,
156 pub b: u8,
157 pub a: f32,
158}
159
160impl Default for SpotlightColor {
161 fn default() -> Self {
162 Self::white(0.25)
163 }
164}
165
166impl SpotlightColor {
167 pub fn white(alpha: f32) -> Self {
169 Self {
170 r: 255,
171 g: 255,
172 b: 255,
173 a: alpha,
174 }
175 }
176
177 pub fn rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
179 Self { r, g, b, a }
180 }
181
182 pub fn to_css(&self) -> String {
184 format!("rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a)
185 }
186
187 pub fn components(&self) -> (u8, u8, u8, f32) {
189 (self.r, self.g, self.b, self.a)
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_spotlight_creation() {
199 let spotlight = SpotlightCard::new(400.0, 300.0);
200 assert_eq!(spotlight.width, 400.0);
201 assert_eq!(spotlight.height, 300.0);
202 assert!(!spotlight.is_active());
203 }
204
205 #[test]
206 fn test_cursor_update() {
207 let mut spotlight = SpotlightCard::new(400.0, 300.0);
208
209 spotlight.update_cursor(150.0, 100.0, 50.0, 50.0);
212
213 assert_eq!(spotlight.spotlight_x(), 100.0);
214 assert_eq!(spotlight.spotlight_y(), 50.0);
215 assert!(spotlight.is_active());
216 }
217
218 #[test]
219 fn test_cursor_outside_bounds() {
220 let mut spotlight = SpotlightCard::new(400.0, 300.0);
221
222 spotlight.update_cursor(10.0, 10.0, 50.0, 50.0);
224
225 assert!(!spotlight.is_active());
226 }
227
228 #[test]
229 fn test_direct_position_set() {
230 let mut spotlight = SpotlightCard::new(400.0, 300.0);
231
232 spotlight.set_spotlight_position(200.0, 150.0);
233 assert_eq!(spotlight.spotlight_center(), (200.0, 150.0));
234 assert!(spotlight.is_active());
235
236 spotlight.set_spotlight_position(-10.0, 150.0);
238 assert!(!spotlight.is_active());
239 }
240
241 #[test]
242 fn test_normalized_position() {
243 let mut spotlight = SpotlightCard::new(400.0, 300.0);
244
245 spotlight.set_spotlight_position(200.0, 150.0);
247 let (nx, ny) = spotlight.normalized_position();
248 assert!((nx - 0.5).abs() < 0.01);
249 assert!((ny - 0.5).abs() < 0.01);
250
251 spotlight.set_spotlight_position(0.0, 0.0);
253 let (nx, ny) = spotlight.normalized_position();
254 assert!(nx < 0.01);
255 assert!(ny < 0.01);
256
257 spotlight.set_spotlight_position(400.0, 300.0);
259 let (nx, ny) = spotlight.normalized_position();
260 assert!((nx - 1.0).abs() < 0.01);
261 assert!((ny - 1.0).abs() < 0.01);
262 }
263
264 #[test]
265 fn test_gradient_params() {
266 let mut spotlight = SpotlightCard::new(400.0, 300.0)
267 .with_radius(150.0);
268
269 spotlight.set_spotlight_position(200.0, 150.0);
270 let (x, y, r, opacity) = spotlight.gradient_params();
271
272 assert_eq!(x, 200.0);
273 assert_eq!(y, 150.0);
274 assert_eq!(r, 150.0);
275 assert_eq!(opacity, 1.0);
276
277 spotlight.deactivate();
279 let (_, _, _, opacity) = spotlight.gradient_params();
280 assert_eq!(opacity, 0.0);
281 }
282
283 #[test]
284 fn test_spotlight_color() {
285 let white = SpotlightColor::white(0.5);
286 assert_eq!(white.r, 255);
287 assert_eq!(white.g, 255);
288 assert_eq!(white.b, 255);
289 assert!((white.a - 0.5).abs() < 0.01);
290
291 let custom = SpotlightColor::rgba(128, 64, 32, 0.75);
292 assert_eq!(custom.r, 128);
293 assert_eq!(custom.g, 64);
294 assert_eq!(custom.b, 32);
295 assert!((custom.a - 0.75).abs() < 0.01);
296
297 let css = white.to_css();
298 assert_eq!(css, "rgba(255, 255, 255, 0.5)");
299 }
300
301 #[test]
302 fn test_dimensions_update() {
303 let mut spotlight = SpotlightCard::new(400.0, 300.0);
304
305 spotlight.set_dimensions(800.0, 600.0);
306 assert_eq!(spotlight.width, 800.0);
307 assert_eq!(spotlight.height, 600.0);
308 }
309}