dioxus_motion/animations/
colors.rs

1//! Color module for animation support
2//!
3//! Provides RGBA color representation and animation interpolation.
4//! Supports both normalized (0.0-1.0) and byte (0-255) color values.
5
6use crate::animations::core::Animatable;
7use wide::f32x4;
8
9/// Represents an RGBA color with normalized components
10///
11/// Each component (r,g,b,a) is stored as a float between 0.0 and 1.0
12#[derive(Debug, Copy, Clone, PartialEq)]
13pub struct Color {
14    /// Red component (0.0-1.0)
15    pub r: f32,
16    /// Green component (0.0-1.0)
17    pub g: f32,
18    /// Blue component (0.0-1.0)
19    pub b: f32,
20    /// Alpha component (0.0-1.0)
21    pub a: f32,
22}
23
24impl Color {
25    /// Creates a new color with normalized components
26    ///
27    /// # Examples
28    /// ```
29    /// use dioxus_motion::prelude::Color;
30    /// let color = Color::new(1.0, 0.5, 0.0, 1.0); // Orange color
31    /// ```
32    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
33        Self {
34            r: r.clamp(0.0, 1.0),
35            g: g.clamp(0.0, 1.0),
36            b: b.clamp(0.0, 1.0),
37            a: a.clamp(0.0, 1.0),
38        }
39    }
40
41    /// Creates a color from 8-bit RGBA values
42    ///
43    /// # Examples
44    /// ```
45    /// use dioxus_motion::prelude::Color;
46    /// let color = Color::from_rgba(255, 128, 0, 255); // Orange color
47    /// ```
48    pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
49        Color::new(
50            r as f32 / 255.0,
51            g as f32 / 255.0,
52            b as f32 / 255.0,
53            a as f32 / 255.0,
54        )
55    }
56
57    /// Converts color to 8-bit RGBA values
58    ///
59    /// # Returns
60    /// Tuple of (r,g,b,a) with values from 0-255
61    pub fn to_rgba(&self) -> (u8, u8, u8, u8) {
62        (
63            (self.r * 255.0 + 0.5) as u8,
64            (self.g * 255.0 + 0.5) as u8,
65            (self.b * 255.0 + 0.5) as u8,
66            (self.a * 255.0 + 0.5) as u8,
67        )
68    }
69}
70
71impl Default for Color {
72    fn default() -> Self {
73        Color::new(0.0, 0.0, 0.0, 1.0) // Black with full opacity
74    }
75}
76
77impl std::ops::Add for Color {
78    type Output = Self;
79
80    fn add(self, other: Self) -> Self {
81        Color::new(
82            (self.r + other.r).clamp(0.0, 1.0),
83            (self.g + other.g).clamp(0.0, 1.0),
84            (self.b + other.b).clamp(0.0, 1.0),
85            (self.a + other.a).clamp(0.0, 1.0),
86        )
87    }
88}
89
90impl std::ops::Sub for Color {
91    type Output = Self;
92
93    fn sub(self, other: Self) -> Self {
94        Color::new(
95            (self.r - other.r).clamp(0.0, 1.0),
96            (self.g - other.g).clamp(0.0, 1.0),
97            (self.b - other.b).clamp(0.0, 1.0),
98            (self.a - other.a).clamp(0.0, 1.0),
99        )
100    }
101}
102
103impl std::ops::Mul<f32> for Color {
104    type Output = Self;
105
106    fn mul(self, factor: f32) -> Self {
107        Color::new(
108            (self.r * factor).clamp(0.0, 1.0),
109            (self.g * factor).clamp(0.0, 1.0),
110            (self.b * factor).clamp(0.0, 1.0),
111            (self.a * factor).clamp(0.0, 1.0),
112        )
113    }
114}
115
116/// Implementation of Animatable for Color
117/// Much simpler with the new trait design - uses standard operators
118impl Animatable for Color {
119    fn interpolate(&self, target: &Self, t: f32) -> Self {
120        let a = [self.r, self.g, self.b, self.a];
121        let b = [target.r, target.g, target.b, target.a];
122        let va = f32x4::new(a);
123        let vb = f32x4::new(b);
124        let vt = f32x4::splat(t.clamp(0.0, 1.0));
125        let result = va + (vb - va) * vt;
126        let out = result.to_array();
127        Color::new(out[0], out[1], out[2], out[3])
128    }
129
130    fn magnitude(&self) -> f32 {
131        (self.r * self.r + self.g * self.g + self.b * self.b + self.a * self.a).sqrt()
132    }
133
134    // Uses default epsilon of 0.01 from the trait - no need for COLOR_EPSILON
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_color_new() {
143        let color = Color::new(1.0, 0.5, 0.0, 1.0);
144        assert_eq!(color.r, 1.0);
145        assert_eq!(color.g, 0.5);
146        assert_eq!(color.b, 0.0);
147        assert_eq!(color.a, 1.0);
148    }
149
150    #[test]
151    fn test_color_from_rgba() {
152        let color = Color::from_rgba(255, 128, 0, 255);
153        assert!((color.r - 1.0).abs() < f32::EPSILON);
154        assert!((color.g - 0.5019608).abs() < 0.000001);
155        assert!((color.b - 0.0).abs() < f32::EPSILON);
156        assert!((color.a - 1.0).abs() < f32::EPSILON);
157    }
158
159    #[test]
160    fn test_color_lerp() {
161        let start = Color::new(0.0, 0.0, 0.0, 1.0);
162        let end = Color::new(1.0, 1.0, 1.0, 1.0);
163        let mid = start.interpolate(&end, 0.5);
164
165        assert!((mid.r - 0.5).abs() < f32::EPSILON);
166        assert!((mid.g - 0.5).abs() < f32::EPSILON);
167        assert!((mid.b - 0.5).abs() < f32::EPSILON);
168        assert!((mid.a - 1.0).abs() < f32::EPSILON);
169    }
170
171    #[test]
172    fn test_color_to_rgba() {
173        let color = Color::new(1.0, 0.5, 0.0, 1.0);
174        let (r, g, b, a) = color.to_rgba();
175        assert_eq!(r, 255);
176        assert_eq!(g, 128);
177        assert_eq!(b, 0);
178        assert_eq!(a, 255);
179    }
180}