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}