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}