tessera_ui/
color.rs

1//! Color utilities for the Tessera UI framework.
2//!
3//! This module provides the [`Color`] struct and related utilities for working with colors
4//! in the linear sRGB color space. The color representation is optimized for GPU rendering
5//! and shader compatibility.
6//!
7//! # Color Space
8//!
9//! All colors are represented in the linear sRGB color space, which is the standard for
10//! modern graphics rendering. This ensures consistent color reproduction across different
11//! devices and platforms.
12//!
13//! # Usage
14//!
15//! ```
16//! use tessera_ui::Color;
17//!
18//! // Create colors using predefined constants
19//! let red = Color::RED;
20//! let transparent = Color::TRANSPARENT;
21//!
22//! // Create colors from f32 values (0.0 to 1.0)
23//! let custom_color = Color::new(0.5, 0.3, 0.8, 1.0);
24//! let opaque_color = Color::from_rgb(0.2, 0.7, 0.4);
25//!
26//! // Create colors from u8 values (0 to 255)
27//! let from_bytes = Color::from_rgba_u8(128, 64, 192, 255);
28//! let from_rgb_bytes = Color::from_rgb_u8(100, 150, 200);
29//!
30//! // Convert from arrays
31//! let from_array: Color = [0.1, 0.2, 0.3, 0.4].into();
32//! let to_array: [f32; 4] = custom_color.into();
33//! ```
34
35use bytemuck::{Pod, Zeroable};
36
37/// A color in the linear sRGB color space with an alpha component.
38///
39/// This struct represents a color using four floating-point components: red, green, blue,
40/// and alpha (transparency). Values are typically in the range `[0.0, 1.0]`, where:
41/// - `0.0` represents no intensity (black for RGB, fully transparent for alpha)
42/// - `1.0` represents full intensity (full color for RGB, fully opaque for alpha)
43///
44/// The struct is designed to be GPU-friendly with a C-compatible memory layout,
45/// making it suitable for direct use in shaders and graphics pipelines.
46///
47/// # Memory Layout
48///
49/// The struct uses `#[repr(C)]` to ensure a predictable memory layout that matches
50/// the expected format for GPU buffers and shader uniforms.
51///
52/// # Examples
53///
54/// ```
55/// use tessera_ui::Color;
56///
57/// // Using predefined colors
58/// let red = Color::RED;
59/// let white = Color::WHITE;
60/// let transparent = Color::TRANSPARENT;
61///
62/// // Creating custom colors
63/// let purple = Color::new(0.5, 0.0, 0.5, 1.0);
64/// let semi_transparent_blue = Color::new(0.0, 0.0, 1.0, 0.5);
65/// ```
66#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
67#[repr(C)] // Ensures C-compatible memory layout for WGPU
68pub struct Color {
69    /// Red component (0.0 to 1.0)
70    pub r: f32,
71    /// Green component (0.0 to 1.0)
72    pub g: f32,
73    /// Blue component (0.0 to 1.0)
74    pub b: f32,
75    /// Alpha (transparency) component (0.0 = fully transparent, 1.0 = fully opaque)
76    pub a: f32,
77}
78
79impl Color {
80    // --- Common Colors ---
81
82    /// Fully transparent color (0, 0, 0, 0).
83    ///
84    /// This color is completely invisible and is often used as a default
85    /// or for creating transparent backgrounds.
86    pub const TRANSPARENT: Color = Color::new(0.0, 0.0, 0.0, 0.0);
87
88    /// Pure black color (0, 0, 0, 1).
89    ///
90    /// Represents the absence of all light, fully opaque.
91    pub const BLACK: Color = Color::new(0.0, 0.0, 0.0, 1.0);
92
93    /// Pure white color (1, 1, 1, 1).
94    ///
95    /// Represents the presence of all light at full intensity, fully opaque.
96    pub const WHITE: Color = Color::new(1.0, 1.0, 1.0, 1.0);
97
98    /// Pure red color (1, 0, 0, 1).
99    ///
100    /// Full intensity red with no green or blue components, fully opaque.
101    pub const RED: Color = Color::new(1.0, 0.0, 0.0, 1.0);
102
103    /// Pure green color (0, 1, 0, 1).
104    ///
105    /// Full intensity green with no red or blue components, fully opaque.
106    pub const GREEN: Color = Color::new(0.0, 1.0, 0.0, 1.0);
107
108    /// Pure blue color (0, 0, 1, 1).
109    ///
110    /// Full intensity blue with no red or green components, fully opaque.
111    pub const BLUE: Color = Color::new(0.0, 0.0, 1.0, 1.0);
112
113    /// Creates a new `Color` from four `f32` values (red, green, blue, alpha).
114    ///
115    /// # Parameters
116    ///
117    /// * `r` - Red component, typically in range [0.0, 1.0]
118    /// * `g` - Green component, typically in range [0.0, 1.0]
119    /// * `b` - Blue component, typically in range [0.0, 1.0]
120    /// * `a` - Alpha (transparency) component, typically in range [0.0, 1.0]
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use tessera_ui::Color;
126    ///
127    /// let red = Color::new(1.0, 0.0, 0.0, 1.0);
128    /// let semi_transparent_blue = Color::new(0.0, 0.0, 1.0, 0.5);
129    /// let custom_color = Color::new(0.3, 0.7, 0.2, 0.8);
130    /// ```
131    #[inline]
132    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
133        Self { r, g, b, a }
134    }
135
136    /// Creates a new opaque `Color` from three `f32` values (red, green, blue).
137    ///
138    /// The alpha component is automatically set to 1.0 (fully opaque).
139    ///
140    /// # Parameters
141    ///
142    /// * `r` - Red component, typically in range [0.0, 1.0]
143    /// * `g` - Green component, typically in range [0.0, 1.0]
144    /// * `b` - Blue component, typically in range [0.0, 1.0]
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use tessera_ui::Color;
150    ///
151    /// let purple = Color::from_rgb(0.5, 0.0, 0.5);
152    /// let orange = Color::from_rgb(1.0, 0.5, 0.0);
153    /// ```
154    #[inline]
155    pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
156        Self { r, g, b, a: 1.0 }
157    }
158
159    /// Creates a new `Color` from four `u8` values (red, green, blue, alpha).
160    ///
161    /// This is convenient for working with traditional 8-bit color values
162    /// commonly used in image formats and color pickers.
163    ///
164    /// # Parameters
165    ///
166    /// * `r` - Red component in range [0, 255]
167    /// * `g` - Green component in range [0, 255]
168    /// * `b` - Blue component in range [0, 255]
169    /// * `a` - Alpha component in range [0, 255] (0 = transparent, 255 = opaque)
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// use tessera_ui::Color;
175    ///
176    /// let red = Color::from_rgba_u8(255, 0, 0, 255);
177    /// let semi_transparent_blue = Color::from_rgba_u8(0, 0, 255, 128);
178    /// let custom_color = Color::from_rgba_u8(76, 178, 51, 204);
179    /// ```
180    #[inline]
181    pub fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
182        Self {
183            r: r as f32 / 255.0,
184            g: g as f32 / 255.0,
185            b: b as f32 / 255.0,
186            a: a as f32 / 255.0,
187        }
188    }
189
190    /// Creates a new opaque `Color` from three `u8` values (red, green, blue).
191    ///
192    /// The alpha component is automatically set to 255 (fully opaque).
193    /// This is convenient for working with traditional RGB color values.
194    ///
195    /// # Parameters
196    ///
197    /// * `r` - Red component in range [0, 255]
198    /// * `g` - Green component in range [0, 255]
199    /// * `b` - Blue component in range [0, 255]
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use tessera_ui::Color;
205    ///
206    /// let purple = Color::from_rgb_u8(128, 0, 128);
207    /// let orange = Color::from_rgb_u8(255, 165, 0);
208    /// let dark_green = Color::from_rgb_u8(0, 100, 0);
209    /// ```
210    #[inline]
211    pub fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self {
212        Self::from_rgba_u8(r, g, b, 255)
213    }
214
215    /// Converts the color to an array of `[f32; 4]`.
216    ///
217    /// This is useful for interfacing with graphics APIs and shaders that
218    /// expect color data in array format.
219    ///
220    /// # Returns
221    ///
222    /// An array `[r, g, b, a]` where each component is an `f32` value.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use tessera_ui::Color;
228    ///
229    /// let color = Color::new(0.5, 0.3, 0.8, 1.0);
230    /// let array = color.to_array();
231    /// assert_eq!(array, [0.5, 0.3, 0.8, 1.0]);
232    /// ```
233    #[inline]
234    pub fn to_array(self) -> [f32; 4] {
235        [self.r, self.g, self.b, self.a]
236    }
237
238    /// Sets the alpha (transparency) component of the color.
239    ///
240    /// # Returns
241    ///
242    /// A new `Color` instance with the updated alpha value.
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use tessera_ui::Color;
248    ///
249    /// let color = Color::new(0.5, 0.3, 0.8, 1.0);
250    /// let semi_transparent_color = color.with_alpha(0.5);
251    ///
252    /// assert_eq!(semi_transparent_color.a, 0.5);
253    /// ```
254    #[inline]
255    pub fn with_alpha(self, alpha: f32) -> Self {
256        Self {
257            r: self.r,
258            g: self.g,
259            b: self.b,
260            a: alpha,
261        }
262    }
263    /// Linearly interpolates between two colors.
264    ///
265    /// # Arguments
266    ///
267    /// * `other` - The target color to interpolate towards.
268    /// * `t` - The interpolation factor, typically in the range `[0.0, 1.0]`.
269    ///
270    /// # Returns
271    ///
272    /// A new `Color` that is the result of the interpolation.
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use tessera_ui::Color;
278    ///
279    /// let color1 = Color::new(1.0, 0.0, 0.0, 1.0); // Red
280    /// let color2 = Color::new(0.0, 0.0, 1.0, 1.0); // Blue
281    /// let interpolated = color1.lerp(&color2, 0.5);
282    ///
283    /// assert_eq!(interpolated, Color::new(0.5, 0.0, 0.5, 1.0)); // Purple
284    /// ```
285    #[inline]
286    pub fn lerp(&self, other: &Self, t: f32) -> Self {
287        let t = t.clamp(0.0, 1.0);
288        Self {
289            r: self.r + (other.r - self.r) * t,
290            g: self.g + (other.g - self.g) * t,
291            b: self.b + (other.b - self.b) * t,
292            a: self.a + (other.a - self.a) * t,
293        }
294    }
295}
296
297/// The default color is fully transparent.
298///
299/// This implementation returns [`Color::TRANSPARENT`], which is often
300/// the most sensible default for UI elements that may not have an
301/// explicit color specified.
302///
303/// # Examples
304///
305/// ```
306/// use tessera_ui::Color;
307///
308/// let default_color = Color::default();
309/// assert_eq!(default_color, Color::TRANSPARENT);
310/// ```
311impl Default for Color {
312    #[inline]
313    fn default() -> Self {
314        Self::TRANSPARENT
315    }
316}
317
318// --- From Conversions ---
319
320/// Converts from a 4-element `f32` array `[r, g, b, a]` to a `Color`.
321///
322/// # Examples
323///
324/// ```
325/// use tessera_ui::Color;
326///
327/// let color: Color = [0.5, 0.3, 0.8, 1.0].into();
328/// assert_eq!(color, Color::new(0.5, 0.3, 0.8, 1.0));
329/// ```
330impl From<[f32; 4]> for Color {
331    #[inline]
332    fn from([r, g, b, a]: [f32; 4]) -> Self {
333        Self { r, g, b, a }
334    }
335}
336
337/// Converts from a `Color` to a 4-element `f32` array `[r, g, b, a]`.
338///
339/// # Examples
340///
341/// ```
342/// use tessera_ui::Color;
343///
344/// let color = Color::new(0.5, 0.3, 0.8, 1.0);
345/// let array: [f32; 4] = color.into();
346/// assert_eq!(array, [0.5, 0.3, 0.8, 1.0]);
347/// ```
348impl From<Color> for [f32; 4] {
349    #[inline]
350    fn from(color: Color) -> Self {
351        [color.r, color.g, color.b, color.a]
352    }
353}
354
355/// Converts from a 3-element `f32` array `[r, g, b]` to an opaque `Color`.
356///
357/// The alpha component is automatically set to 1.0 (fully opaque).
358///
359/// # Examples
360///
361/// ```
362/// use tessera_ui::Color;
363///
364/// let color: Color = [0.5, 0.3, 0.8].into();
365/// assert_eq!(color, Color::new(0.5, 0.3, 0.8, 1.0));
366/// ```
367impl From<[f32; 3]> for Color {
368    #[inline]
369    fn from([r, g, b]: [f32; 3]) -> Self {
370        Self { r, g, b, a: 1.0 }
371    }
372}
373
374/// Converts from a 4-element `u8` array `[r, g, b, a]` to a `Color`.
375///
376/// Each component is converted from the range [0, 255] to [0.0, 1.0].
377///
378/// # Examples
379///
380/// ```
381/// use tessera_ui::Color;
382///
383/// let color: Color = [255, 128, 64, 255].into();
384/// assert_eq!(color, Color::from_rgba_u8(255, 128, 64, 255));
385/// ```
386impl From<[u8; 4]> for Color {
387    #[inline]
388    fn from([r, g, b, a]: [u8; 4]) -> Self {
389        Self::from_rgba_u8(r, g, b, a)
390    }
391}
392
393/// Converts from a 3-element `u8` array `[r, g, b]` to an opaque `Color`.
394///
395/// Each component is converted from the range [0, 255] to [0.0, 1.0].
396/// The alpha component is automatically set to 1.0 (fully opaque).
397///
398/// # Examples
399///
400/// ```
401/// use tessera_ui::Color;
402///
403/// let color: Color = [255, 128, 64].into();
404/// assert_eq!(color, Color::from_rgb_u8(255, 128, 64));
405/// ```
406impl From<[u8; 3]> for Color {
407    #[inline]
408    fn from([r, g, b]: [u8; 3]) -> Self {
409        Self::from_rgb_u8(r, g, b)
410    }
411}