#[derive(Debug, Clone, Copy)]
pub struct SpotlightCard {
pub width: f32,
pub height: f32,
pub radius: f32,
spotlight_x: f32,
spotlight_y: f32,
is_active: bool,
}
impl Default for SpotlightCard {
fn default() -> Self {
Self::new(400.0, 300.0)
}
}
impl SpotlightCard {
pub fn new(width: f32, height: f32) -> Self {
Self {
width,
height,
radius: 200.0,
spotlight_x: width / 2.0,
spotlight_y: height / 2.0,
is_active: false,
}
}
pub fn with_radius(mut self, radius: f32) -> Self {
self.radius = radius;
self
}
pub fn set_dimensions(&mut self, width: f32, height: f32) {
self.width = width;
self.height = height;
}
pub fn update_cursor(
&mut self,
cursor_x: f32,
cursor_y: f32,
card_x: f32,
card_y: f32,
) {
self.spotlight_x = cursor_x - card_x;
self.spotlight_y = cursor_y - card_y;
self.is_active = self.spotlight_x >= 0.0
&& self.spotlight_x <= self.width
&& self.spotlight_y >= 0.0
&& self.spotlight_y <= self.height;
}
pub fn set_spotlight_position(&mut self, x: f32, y: f32) {
self.spotlight_x = x;
self.spotlight_y = y;
self.is_active = x >= 0.0
&& x <= self.width
&& y >= 0.0
&& y <= self.height;
}
pub fn spotlight_x(&self) -> f32 {
self.spotlight_x
}
pub fn spotlight_y(&self) -> f32 {
self.spotlight_y
}
pub fn spotlight_center(&self) -> (f32, f32) {
(self.spotlight_x, self.spotlight_y)
}
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn deactivate(&mut self) {
self.is_active = false;
}
pub fn normalized_position(&self) -> (f32, f32) {
let x = if self.width > 0.0 {
(self.spotlight_x / self.width).clamp(0.0, 1.0)
} else {
0.5
};
let y = if self.height > 0.0 {
(self.spotlight_y / self.height).clamp(0.0, 1.0)
} else {
0.5
};
(x, y)
}
pub fn gradient_params(&self) -> (f32, f32, f32, f32) {
let opacity = if self.is_active { 1.0 } else { 0.0 };
(self.spotlight_x, self.spotlight_y, self.radius, opacity)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpotlightColor {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: f32,
}
impl Default for SpotlightColor {
fn default() -> Self {
Self::white(0.25)
}
}
impl SpotlightColor {
pub fn white(alpha: f32) -> Self {
Self {
r: 255,
g: 255,
b: 255,
a: alpha,
}
}
pub fn rgba(r: u8, g: u8, b: u8, a: f32) -> Self {
Self { r, g, b, a }
}
pub fn to_css(&self) -> String {
format!("rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a)
}
pub fn components(&self) -> (u8, u8, u8, f32) {
(self.r, self.g, self.b, self.a)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spotlight_creation() {
let spotlight = SpotlightCard::new(400.0, 300.0);
assert_eq!(spotlight.width, 400.0);
assert_eq!(spotlight.height, 300.0);
assert!(!spotlight.is_active());
}
#[test]
fn test_cursor_update() {
let mut spotlight = SpotlightCard::new(400.0, 300.0);
spotlight.update_cursor(150.0, 100.0, 50.0, 50.0);
assert_eq!(spotlight.spotlight_x(), 100.0);
assert_eq!(spotlight.spotlight_y(), 50.0);
assert!(spotlight.is_active());
}
#[test]
fn test_cursor_outside_bounds() {
let mut spotlight = SpotlightCard::new(400.0, 300.0);
spotlight.update_cursor(10.0, 10.0, 50.0, 50.0);
assert!(!spotlight.is_active());
}
#[test]
fn test_direct_position_set() {
let mut spotlight = SpotlightCard::new(400.0, 300.0);
spotlight.set_spotlight_position(200.0, 150.0);
assert_eq!(spotlight.spotlight_center(), (200.0, 150.0));
assert!(spotlight.is_active());
spotlight.set_spotlight_position(-10.0, 150.0);
assert!(!spotlight.is_active());
}
#[test]
fn test_normalized_position() {
let mut spotlight = SpotlightCard::new(400.0, 300.0);
spotlight.set_spotlight_position(200.0, 150.0);
let (nx, ny) = spotlight.normalized_position();
assert!((nx - 0.5).abs() < 0.01);
assert!((ny - 0.5).abs() < 0.01);
spotlight.set_spotlight_position(0.0, 0.0);
let (nx, ny) = spotlight.normalized_position();
assert!(nx < 0.01);
assert!(ny < 0.01);
spotlight.set_spotlight_position(400.0, 300.0);
let (nx, ny) = spotlight.normalized_position();
assert!((nx - 1.0).abs() < 0.01);
assert!((ny - 1.0).abs() < 0.01);
}
#[test]
fn test_gradient_params() {
let mut spotlight = SpotlightCard::new(400.0, 300.0)
.with_radius(150.0);
spotlight.set_spotlight_position(200.0, 150.0);
let (x, y, r, opacity) = spotlight.gradient_params();
assert_eq!(x, 200.0);
assert_eq!(y, 150.0);
assert_eq!(r, 150.0);
assert_eq!(opacity, 1.0);
spotlight.deactivate();
let (_, _, _, opacity) = spotlight.gradient_params();
assert_eq!(opacity, 0.0);
}
#[test]
fn test_spotlight_color() {
let white = SpotlightColor::white(0.5);
assert_eq!(white.r, 255);
assert_eq!(white.g, 255);
assert_eq!(white.b, 255);
assert!((white.a - 0.5).abs() < 0.01);
let custom = SpotlightColor::rgba(128, 64, 32, 0.75);
assert_eq!(custom.r, 128);
assert_eq!(custom.g, 64);
assert_eq!(custom.b, 32);
assert!((custom.a - 0.75).abs() < 0.01);
let css = white.to_css();
assert_eq!(css, "rgba(255, 255, 255, 0.5)");
}
#[test]
fn test_dimensions_update() {
let mut spotlight = SpotlightCard::new(400.0, 300.0);
spotlight.set_dimensions(800.0, 600.0);
assert_eq!(spotlight.width, 800.0);
assert_eq!(spotlight.height, 600.0);
}
}