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