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}