Skip to main content

game_toolkit_gfx/
color.rs

1//! [`Color`] and [`Layer`]: small typed wrappers for the painter's colour and layer arguments.
2
3/// An RGBA colour with components in `0.0..=1.0`.
4///
5/// Every painter call takes `impl Into<Color>`, so a plain `[r, g, b, a]` array works as well
6/// as a `Color`.
7///
8/// Components are sRGB, the values a color picker or CSS shows: `Color::hex(0x2a3550)` puts
9/// exactly `#2a3550` on screen, on sprites, shapes, circles, text, meshes and the clear color
10/// alike. The renderer decodes them to linear for blending, so blends and the per-vertex
11/// gradients of filled shapes mix in sRGB, as CSS does.
12#[derive(Copy, Clone, Debug, Default, PartialEq)]
13pub struct Color {
14    pub r: f32,
15    pub g: f32,
16    pub b: f32,
17    pub a: f32,
18}
19
20impl Color {
21    pub const TRANSPARENT: Self = Self::rgba(0.0, 0.0, 0.0, 0.0);
22    pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
23    pub const WHITE: Self = Self::rgb(1.0, 1.0, 1.0);
24    pub const GRAY: Self = Self::rgb(0.5, 0.5, 0.5);
25    pub const RED: Self = Self::rgb(1.0, 0.0, 0.0);
26    pub const GREEN: Self = Self::rgb(0.0, 1.0, 0.0);
27    pub const BLUE: Self = Self::rgb(0.0, 0.0, 1.0);
28    pub const YELLOW: Self = Self::rgb(1.0, 1.0, 0.0);
29    pub const CYAN: Self = Self::rgb(0.0, 1.0, 1.0);
30    pub const MAGENTA: Self = Self::rgb(1.0, 0.0, 1.0);
31    pub const ORANGE: Self = Self::rgb(1.0, 0.5, 0.0);
32    pub const GOLD: Self = Self::rgb(1.0, 0.84, 0.0);
33
34    /// Opaque colour from components in `0.0..=1.0`.
35    pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
36        Self::rgba(r, g, b, 1.0)
37    }
38
39    /// Colour from components in `0.0..=1.0`.
40    pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
41        Self { r, g, b, a }
42    }
43
44    /// Opaque colour from 8-bit components.
45    pub const fn rgb8(r: u8, g: u8, b: u8) -> Self {
46        Self::rgba8(r, g, b, 255)
47    }
48
49    /// Colour from 8-bit components.
50    pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
51        Self::rgba(
52            r as f32 / 255.0,
53            g as f32 / 255.0,
54            b as f32 / 255.0,
55            a as f32 / 255.0,
56        )
57    }
58
59    /// Opaque colour from a `0xRRGGBB` literal, as in CSS: `Color::hex(0xff8800)`.
60    pub const fn hex(rgb: u32) -> Self {
61        Self::rgb8((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
62    }
63
64    /// Colour from a `0xRRGGBBAA` literal: `Color::hex_rgba(0xff880080)` is half-transparent.
65    pub const fn hex_rgba(rgba: u32) -> Self {
66        Self::rgba8(
67            (rgba >> 24) as u8,
68            (rgba >> 16) as u8,
69            (rgba >> 8) as u8,
70            rgba as u8,
71        )
72    }
73
74    /// This colour with alpha replaced by `a`.
75    pub const fn with_alpha(self, a: f32) -> Self {
76        Self { a, ..self }
77    }
78
79    /// Linear interpolation to `other`: `t = 0.0` is `self`, `1.0` is `other`.
80    pub fn lerp(self, other: Self, t: f32) -> Self {
81        let mix = |a: f32, b: f32| a + (b - a) * t;
82        Self::rgba(
83            mix(self.r, other.r),
84            mix(self.g, other.g),
85            mix(self.b, other.b),
86            mix(self.a, other.a),
87        )
88    }
89
90    /// As `[r, g, b, a]`.
91    pub const fn to_array(self) -> [f32; 4] {
92        [self.r, self.g, self.b, self.a]
93    }
94}
95
96impl From<[f32; 4]> for Color {
97    fn from([r, g, b, a]: [f32; 4]) -> Self {
98        Self { r, g, b, a }
99    }
100}
101
102impl From<[f32; 3]> for Color {
103    fn from([r, g, b]: [f32; 3]) -> Self {
104        Self::rgb(r, g, b)
105    }
106}
107
108impl From<Color> for [f32; 4] {
109    fn from(c: Color) -> Self {
110        c.to_array()
111    }
112}
113
114/// A 2D draw layer: lower layers draw under higher ones (see [`crate::Painter`]'s draw-order
115/// notes). Every painter call takes `impl Into<Layer>`, so a bare integer works too.
116///
117/// Name your layers as constants: `const HUD: Layer = Layer(3);`.
118#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
119pub struct Layer(pub i16);
120
121impl Layer {
122    /// Layer 0, where the calls without a layer argument draw.
123    pub const DEFAULT: Self = Self(0);
124    /// The lowest layer.
125    pub const BOTTOM: Self = Self(i16::MIN);
126    /// The highest layer.
127    pub const TOP: Self = Self(i16::MAX);
128}
129
130impl From<i16> for Layer {
131    fn from(layer: i16) -> Self {
132        Self(layer)
133    }
134}
135
136impl From<Layer> for i16 {
137    fn from(layer: Layer) -> Self {
138        layer.0
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn hex_and_rgba8() {
148        assert_eq!(Color::hex(0xff0000), Color::RED);
149        assert_eq!(
150            Color::hex_rgba(0x00ff0080),
151            Color::GREEN.with_alpha(128.0 / 255.0)
152        );
153        assert_eq!(Color::rgb8(0, 0, 255), Color::BLUE);
154    }
155
156    #[test]
157    fn array_conversions_roundtrip() {
158        let c: Color = [0.1, 0.2, 0.3, 0.4].into();
159        assert_eq!(<[f32; 4]>::from(c), [0.1, 0.2, 0.3, 0.4]);
160        assert_eq!(Color::from([0.1, 0.2, 0.3]).a, 1.0);
161    }
162
163    #[test]
164    fn lerp_endpoints() {
165        assert_eq!(Color::BLACK.lerp(Color::WHITE, 0.0), Color::BLACK);
166        assert_eq!(Color::BLACK.lerp(Color::WHITE, 1.0), Color::WHITE);
167        assert_eq!(Color::BLACK.lerp(Color::WHITE, 0.5), Color::GRAY);
168    }
169
170    #[test]
171    fn layer_from_literal() {
172        fn take(l: impl Into<Layer>) -> Layer {
173            l.into()
174        }
175        assert_eq!(take(3), Layer(3));
176        assert!(Layer::BOTTOM < Layer::DEFAULT && Layer::DEFAULT < Layer::TOP);
177    }
178}