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: Self = Self::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: Self = Self::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: Self = Self::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: Self = Self::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: Self = Self::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: Self = Self::new(0.0, 0.0, 1.0, 1.0);
112
113    /// Gray color (0.12, 0.12, 0.12, 1).
114    pub const GRAY: Self = Self::new(0.12, 0.12, 0.12, 1.0);
115
116    /// Alias for [`Color::GRAY`].
117    pub const GREY: Self = Self::GRAY;
118
119    /// Teal color (0, 0.5, 0.5, 1).
120    pub const TEAL: Self = Self::new(0.0, 0.5, 0.5, 1.0);
121
122    /// Orange color (1, 0.5, 0, 1).
123    pub const ORANGE: Self = Self::new(1.0, 0.5, 0.0, 1.0);
124
125    /// Creates a new `Color` from four `f32` values (red, green, blue, alpha).
126    ///
127    /// # Parameters
128    ///
129    /// * `r` - Red component, typically in range [0.0, 1.0]
130    /// * `g` - Green component, typically in range [0.0, 1.0]
131    /// * `b` - Blue component, typically in range [0.0, 1.0]
132    /// * `a` - Alpha (transparency) component, typically in range [0.0, 1.0]
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use tessera_ui::Color;
138    ///
139    /// let red = Color::new(1.0, 0.0, 0.0, 1.0);
140    /// let semi_transparent_blue = Color::new(0.0, 0.0, 1.0, 0.5);
141    /// let custom_color = Color::new(0.3, 0.7, 0.2, 0.8);
142    /// ```
143    #[inline]
144    #[must_use]
145    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
146        Self { r, g, b, a }
147    }
148
149    /// Creates a new opaque `Color` from three `f32` values (red, green, blue).
150    ///
151    /// The alpha component is automatically set to 1.0 (fully opaque).
152    ///
153    /// # Parameters
154    ///
155    /// * `r` - Red component, typically in range [0.0, 1.0]
156    /// * `g` - Green component, typically in range [0.0, 1.0]
157    /// * `b` - Blue component, typically in range [0.0, 1.0]
158    ///
159    /// # Examples
160    ///
161    /// ```
162    /// use tessera_ui::Color;
163    ///
164    /// let purple = Color::from_rgb(0.5, 0.0, 0.5);
165    /// let orange = Color::from_rgb(1.0, 0.5, 0.0);
166    /// ```
167    #[inline]
168    #[must_use]
169    pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
170        Self { r, g, b, a: 1.0 }
171    }
172
173    /// Creates a new `Color` from four `u8` values (red, green, blue, alpha).
174    ///
175    /// This is convenient for working with traditional 8-bit color values
176    /// commonly used in image formats and color pickers.
177    ///
178    /// # Parameters
179    ///
180    /// * `r` - Red component in range [0, 255]
181    /// * `g` - Green component in range [0, 255]
182    /// * `b` - Blue component in range [0, 255]
183    /// * `a` - Alpha component in range [0, 255] (0 = transparent, 255 = opaque)
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use tessera_ui::Color;
189    ///
190    /// let red = Color::from_rgba_u8(255, 0, 0, 255);
191    /// let semi_transparent_blue = Color::from_rgba_u8(0, 0, 255, 128);
192    /// let custom_color = Color::from_rgba_u8(76, 178, 51, 204);
193    /// ```
194    #[inline]
195    #[must_use]
196    pub const fn from_rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
197        Self {
198            r: r as f32 / 255.0,
199            g: g as f32 / 255.0,
200            b: b as f32 / 255.0,
201            a: a as f32 / 255.0,
202        }
203    }
204
205    /// Creates a new opaque `Color` from three `u8` values (red, green, blue).
206    ///
207    /// The alpha component is automatically set to 255 (fully opaque).
208    /// This is convenient for working with traditional RGB color values.
209    ///
210    /// # Parameters
211    ///
212    /// * `r` - Red component in range [0, 255]
213    /// * `g` - Green component in range [0, 255]
214    /// * `b` - Blue component in range [0, 255]
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use tessera_ui::Color;
220    ///
221    /// let purple = Color::from_rgb_u8(128, 0, 128);
222    /// let orange = Color::from_rgb_u8(255, 165, 0);
223    /// let dark_green = Color::from_rgb_u8(0, 100, 0);
224    /// ```
225    #[inline]
226    #[must_use]
227    pub const fn from_rgb_u8(r: u8, g: u8, b: u8) -> Self {
228        Self::from_rgba_u8(r, g, b, 255)
229    }
230
231    /// Converts the color to an array of `[f32; 4]`.
232    ///
233    /// This is useful for interfacing with graphics APIs and shaders that
234    /// expect color data in array format.
235    ///
236    /// # Returns
237    ///
238    /// An array `[r, g, b, a]` where each component is an `f32` value.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// use tessera_ui::Color;
244    ///
245    /// let color = Color::new(0.5, 0.3, 0.8, 1.0);
246    /// let array = color.to_array();
247    /// assert_eq!(array, [0.5, 0.3, 0.8, 1.0]);
248    /// ```
249    #[inline]
250    #[must_use]
251    pub const fn to_array(self) -> [f32; 4] {
252        [self.r, self.g, self.b, self.a]
253    }
254
255    /// Sets the alpha (transparency) component of the color.
256    ///
257    /// # Returns
258    ///
259    /// A new `Color` instance with the updated alpha value.
260    ///
261    /// # Examples
262    ///
263    /// ```
264    /// use tessera_ui::Color;
265    ///
266    /// let color = Color::new(0.5, 0.3, 0.8, 1.0);
267    /// let semi_transparent_color = color.with_alpha(0.5);
268    ///
269    /// assert_eq!(semi_transparent_color.a, 0.5);
270    /// ```
271    #[inline]
272    #[must_use]
273    pub const fn with_alpha(self, alpha: f32) -> Self {
274        Self {
275            r: self.r,
276            g: self.g,
277            b: self.b,
278            a: alpha,
279        }
280    }
281    /// Linearly interpolates between two colors.
282    ///
283    /// # Arguments
284    ///
285    /// * `other` - The target color to interpolate towards.
286    /// * `t` - The interpolation factor, typically in the range `[0.0, 1.0]`.
287    ///
288    /// # Returns
289    ///
290    /// A new `Color` that is the result of the interpolation.
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use tessera_ui::Color;
296    ///
297    /// let color1 = Color::new(1.0, 0.0, 0.0, 1.0); // Red
298    /// let color2 = Color::new(0.0, 0.0, 1.0, 1.0); // Blue
299    /// let interpolated = color1.lerp(&color2, 0.5);
300    ///
301    /// assert_eq!(interpolated, Color::new(0.5, 0.0, 0.5, 1.0)); // Purple
302    /// ```
303    #[inline]
304    #[must_use]
305    pub fn lerp(&self, other: &Self, t: f32) -> Self {
306        let t = t.clamp(0.0, 1.0);
307        Self {
308            r: (other.r - self.r).mul_add(t, self.r),
309            g: (other.g - self.g).mul_add(t, self.g),
310            b: (other.b - self.b).mul_add(t, self.b),
311            a: (other.a - self.a).mul_add(t, self.a),
312        }
313    }
314}
315
316/// The default color is fully transparent.
317///
318/// This implementation returns [`Color::TRANSPARENT`], which is often
319/// the most sensible default for UI elements that may not have an
320/// explicit color specified.
321///
322/// # Examples
323///
324/// ```
325/// use tessera_ui::Color;
326///
327/// let default_color = Color::default();
328/// assert_eq!(default_color, Color::TRANSPARENT);
329/// ```
330impl Default for Color {
331    #[inline]
332    fn default() -> Self {
333        Self::TRANSPARENT
334    }
335}
336
337// --- From Conversions ---
338
339/// Converts from a 4-element `f32` array `[r, g, b, a]` to a `Color`.
340///
341/// # Examples
342///
343/// ```
344/// use tessera_ui::Color;
345///
346/// let color: Color = [0.5, 0.3, 0.8, 1.0].into();
347/// assert_eq!(color, Color::new(0.5, 0.3, 0.8, 1.0));
348/// ```
349impl From<[f32; 4]> for Color {
350    #[inline]
351    fn from([r, g, b, a]: [f32; 4]) -> Self {
352        Self { r, g, b, a }
353    }
354}
355
356/// Converts from a `Color` to a 4-element `f32` array `[r, g, b, a]`.
357///
358/// # Examples
359///
360/// ```
361/// use tessera_ui::Color;
362///
363/// let color = Color::new(0.5, 0.3, 0.8, 1.0);
364/// let array: [f32; 4] = color.into();
365/// assert_eq!(array, [0.5, 0.3, 0.8, 1.0]);
366/// ```
367impl From<Color> for [f32; 4] {
368    #[inline]
369    fn from(color: Color) -> Self {
370        [color.r, color.g, color.b, color.a]
371    }
372}
373
374/// Converts from a 3-element `f32` array `[r, g, b]` to an opaque `Color`.
375///
376/// The alpha component is automatically set to 1.0 (fully opaque).
377///
378/// # Examples
379///
380/// ```
381/// use tessera_ui::Color;
382///
383/// let color: Color = [0.5, 0.3, 0.8].into();
384/// assert_eq!(color, Color::new(0.5, 0.3, 0.8, 1.0));
385/// ```
386impl From<[f32; 3]> for Color {
387    #[inline]
388    fn from([r, g, b]: [f32; 3]) -> Self {
389        Self { r, g, b, a: 1.0 }
390    }
391}
392
393/// Converts from a 4-element `u8` array `[r, g, b, a]` to a `Color`.
394///
395/// Each component is converted from the range [0, 255] to [0.0, 1.0].
396///
397/// # Examples
398///
399/// ```
400/// use tessera_ui::Color;
401///
402/// let color: Color = [255, 128, 64, 255].into();
403/// assert_eq!(color, Color::from_rgba_u8(255, 128, 64, 255));
404/// ```
405impl From<[u8; 4]> for Color {
406    #[inline]
407    fn from([r, g, b, a]: [u8; 4]) -> Self {
408        Self::from_rgba_u8(r, g, b, a)
409    }
410}
411
412/// Converts from a 3-element `u8` array `[r, g, b]` to an opaque `Color`.
413///
414/// Each component is converted from the range [0, 255] to [0.0, 1.0].
415/// The alpha component is automatically set to 1.0 (fully opaque).
416///
417/// # Examples
418///
419/// ```
420/// use tessera_ui::Color;
421///
422/// let color: Color = [255, 128, 64].into();
423/// assert_eq!(color, Color::from_rgb_u8(255, 128, 64));
424/// ```
425impl From<[u8; 3]> for Color {
426    #[inline]
427    fn from([r, g, b]: [u8; 3]) -> Self {
428        Self::from_rgb_u8(r, g, b)
429    }
430}