use crate::animation::{AnimationProperty, AnimationTarget};
use crate::graphics::renderer::Color;
use crate::graphics::{GraphicsContext, Rectangle};
use crate::ui::Component;
use anyhow::Result;
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum Position {
TopLeft,
TopRight,
BottomLeft,
#[allow(dead_code)]
BottomRight,
Custom {
x: i16,
y: i16,
},
}
pub struct StatusIndicator {
position: Position,
size: u16,
color: Color,
screen_width: u16,
screen_height: u16,
#[allow(dead_code)]
alpha: f32,
#[allow(dead_code)]
scale: f32,
}
impl StatusIndicator {
pub fn new(
position: Position,
size: u16,
color: Color,
screen_width: u16,
screen_height: u16,
) -> Self {
Self {
position,
size,
color,
screen_width,
screen_height,
alpha: 1.0,
scale: 1.0,
}
}
#[allow(dead_code)]
pub fn green_square(screen_width: u16, screen_height: u16) -> Self {
Self::new(
Position::TopRight,
50,
Color::GREEN,
screen_width,
screen_height,
)
}
fn calculate_position(&self) -> (i16, i16) {
match self.position {
Position::TopLeft => (0, 0),
Position::TopRight => ((self.screen_width - self.size) as i16, 0),
Position::BottomLeft => (0, (self.screen_height - self.size) as i16),
Position::BottomRight => (
(self.screen_width - self.size) as i16,
(self.screen_height - self.size) as i16,
),
Position::Custom { x, y } => (x, y),
}
}
}
impl Component for StatusIndicator {
fn render(&self, graphics: &mut GraphicsContext) -> Result<()> {
let (x, y) = self.calculate_position();
let rect = Rectangle {
x,
y,
width: self.size,
height: self.size,
};
graphics.renderer().fill_rectangle(rect, self.color)
}
fn bounds(&self) -> Rectangle {
let (x, y) = self.calculate_position();
Rectangle {
x,
y,
width: self.size,
height: self.size,
}
}
fn update(&mut self, _delta_time: f64) -> bool {
false }
}
impl AnimationTarget for StatusIndicator {
fn set_property(&mut self, property: AnimationProperty) -> Result<()> {
match property {
AnimationProperty::Position { x, y } => {
self.position = Position::Custom { x, y };
}
AnimationProperty::Alpha(alpha) => {
self.alpha = alpha.clamp(0.0, 1.0);
}
AnimationProperty::Scale(scale) => {
self.scale = scale.max(0.0);
}
AnimationProperty::Color { r, g, b } => {
self.color = Color::new((self.alpha * 255.0) as u8, r, g, b);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_indicator_creation() {
let indicator = StatusIndicator::new(Position::TopLeft, 50, Color::RED, 1920, 1080);
assert_eq!(indicator.size, 50);
assert_eq!(indicator.screen_width, 1920);
assert_eq!(indicator.screen_height, 1080);
assert_eq!(indicator.alpha, 1.0);
assert_eq!(indicator.scale, 1.0);
assert_eq!(indicator.color.argb, Color::RED.argb);
}
#[test]
fn test_green_square_factory() {
let indicator = StatusIndicator::green_square(1920, 1080);
assert_eq!(indicator.size, 50);
assert_eq!(indicator.color.argb, Color::GREEN.argb);
let (x, y) = indicator.calculate_position();
assert_eq!(x, 1920 - 50);
assert_eq!(y, 0);
}
#[test]
fn test_position_calculation() {
let screen_width = 1920;
let screen_height = 1080;
let size = 50;
let top_left = StatusIndicator::new(
Position::TopLeft,
size,
Color::RED,
screen_width,
screen_height,
);
assert_eq!(top_left.calculate_position(), (0, 0));
let top_right = StatusIndicator::new(
Position::TopRight,
size,
Color::RED,
screen_width,
screen_height,
);
assert_eq!(top_right.calculate_position(), (1870, 0));
let bottom_left = StatusIndicator::new(
Position::BottomLeft,
size,
Color::RED,
screen_width,
screen_height,
);
assert_eq!(bottom_left.calculate_position(), (0, 1030));
let bottom_right = StatusIndicator::new(
Position::BottomRight,
size,
Color::RED,
screen_width,
screen_height,
);
assert_eq!(bottom_right.calculate_position(), (1870, 1030));
let custom = StatusIndicator::new(
Position::Custom { x: 100, y: 200 },
size,
Color::RED,
screen_width,
screen_height,
);
assert_eq!(custom.calculate_position(), (100, 200));
}
#[test]
fn test_bounds_calculation() {
let indicator = StatusIndicator::new(
Position::Custom { x: 100, y: 200 },
50,
Color::RED,
1920,
1080,
);
let bounds = indicator.bounds();
assert_eq!(bounds.x, 100);
assert_eq!(bounds.y, 200);
assert_eq!(bounds.width, 50);
assert_eq!(bounds.height, 50);
}
#[test]
fn test_animation_property_setters() {
let mut indicator = StatusIndicator::new(Position::TopLeft, 50, Color::RED, 1920, 1080);
indicator
.set_property(AnimationProperty::Position { x: 100, y: 200 })
.unwrap();
assert_eq!(indicator.calculate_position(), (100, 200));
indicator
.set_property(AnimationProperty::Alpha(0.5))
.unwrap();
assert_eq!(indicator.alpha, 0.5);
indicator
.set_property(AnimationProperty::Alpha(1.5))
.unwrap();
assert_eq!(indicator.alpha, 1.0);
indicator
.set_property(AnimationProperty::Alpha(-0.5))
.unwrap();
assert_eq!(indicator.alpha, 0.0);
indicator
.set_property(AnimationProperty::Scale(2.0))
.unwrap();
assert_eq!(indicator.scale, 2.0);
indicator
.set_property(AnimationProperty::Scale(-1.0))
.unwrap();
assert_eq!(indicator.scale, 0.0);
indicator
.set_property(AnimationProperty::Color {
r: 100,
g: 150,
b: 200,
})
.unwrap();
let expected_color = Color::new((indicator.alpha * 255.0) as u8, 100, 150, 200);
assert_eq!(indicator.color.argb, expected_color.argb);
}
#[test]
fn test_position_enum_debug() {
let positions = [
Position::TopLeft,
Position::TopRight,
Position::BottomLeft,
Position::BottomRight,
Position::Custom { x: 10, y: 20 },
];
for pos in &positions {
let debug_str = format!("{:?}", pos);
assert!(!debug_str.is_empty());
}
}
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_position_calculation_bounds(
screen_width in 100u16..5000,
screen_height in 100u16..5000,
size in 1u16..100 ) {
prop_assume!(size < screen_width && size < screen_height);
let indicator = StatusIndicator::new(
Position::TopRight,
size,
Color::RED,
screen_width,
screen_height,
);
let (x, y) = indicator.calculate_position();
assert!(x >= 0);
assert!(y >= 0);
assert!(x + size as i16 <= screen_width as i16);
assert!(y + size as i16 <= screen_height as i16);
}
#[test]
fn test_alpha_clamping_property(alpha_input in -10.0f32..20.0) {
let mut indicator = StatusIndicator::new(
Position::TopLeft,
50,
Color::RED,
1920,
1080,
);
indicator.set_property(AnimationProperty::Alpha(alpha_input)).unwrap();
assert!(indicator.alpha >= 0.0);
assert!(indicator.alpha <= 1.0);
}
#[test]
fn test_scale_minimum_property(scale_input in -100.0f32..100.0) {
let mut indicator = StatusIndicator::new(
Position::TopLeft,
50,
Color::RED,
1920,
1080,
);
indicator.set_property(AnimationProperty::Scale(scale_input)).unwrap();
assert!(indicator.scale >= 0.0);
}
#[test]
fn test_custom_position_property(x: i16, y: i16) {
let mut indicator = StatusIndicator::new(
Position::TopLeft,
50,
Color::RED,
1920,
1080,
);
indicator.set_property(AnimationProperty::Position { x, y }).unwrap();
assert_eq!(indicator.calculate_position(), (x, y));
}
}
}
}