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    /// Palette index `i` as a painter colour, for an indexed canvas (feature `indexed`, see
45    /// [`Graphics::enable_indexed`](crate::Graphics::enable_indexed)): the index rides in the
46    /// red channel and is turned into the palette's colour at the end of the frame. Draw
47    /// indices only with full opacity: blending (anti-aliased circle edges, alpha below 1)
48    /// mixes indices, not colours.
49    #[cfg(feature = "indexed")]
50    pub const fn index(i: u8) -> Self {
51        Self::rgba(i as f32 / 255.0, 0.0, 0.0, 1.0)
52    }
53
54    /// Opaque colour from 8-bit components.
55    pub const fn rgb8(r: u8, g: u8, b: u8) -> Self {
56        Self::rgba8(r, g, b, 255)
57    }
58
59    /// Colour from 8-bit components.
60    pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
61        Self::rgba(
62            r as f32 / 255.0,
63            g as f32 / 255.0,
64            b as f32 / 255.0,
65            a as f32 / 255.0,
66        )
67    }
68
69    /// Opaque colour from a `0xRRGGBB` literal, as in CSS: `Color::hex(0xff8800)`.
70    pub const fn hex(rgb: u32) -> Self {
71        Self::rgb8((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)
72    }
73
74    /// Colour from a `0xRRGGBBAA` literal: `Color::hex_rgba(0xff880080)` is half-transparent.
75    pub const fn hex_rgba(rgba: u32) -> Self {
76        Self::rgba8(
77            (rgba >> 24) as u8,
78            (rgba >> 16) as u8,
79            (rgba >> 8) as u8,
80            rgba as u8,
81        )
82    }
83
84    /// This colour with alpha replaced by `a`.
85    pub const fn with_alpha(self, a: f32) -> Self {
86        Self { a, ..self }
87    }
88
89    /// Linear interpolation to `other`: `t = 0.0` is `self`, `1.0` is `other`.
90    pub fn lerp(self, other: Self, t: f32) -> Self {
91        let mix = |a: f32, b: f32| a + (b - a) * t;
92        Self::rgba(
93            mix(self.r, other.r),
94            mix(self.g, other.g),
95            mix(self.b, other.b),
96            mix(self.a, other.a),
97        )
98    }
99
100    /// As `[r, g, b, a]`.
101    pub const fn to_array(self) -> [f32; 4] {
102        [self.r, self.g, self.b, self.a]
103    }
104}
105
106impl From<[f32; 4]> for Color {
107    fn from([r, g, b, a]: [f32; 4]) -> Self {
108        Self { r, g, b, a }
109    }
110}
111
112impl From<[f32; 3]> for Color {
113    fn from([r, g, b]: [f32; 3]) -> Self {
114        Self::rgb(r, g, b)
115    }
116}
117
118impl From<Color> for [f32; 4] {
119    fn from(c: Color) -> Self {
120        c.to_array()
121    }
122}
123
124/// A 2D draw layer: lower layers draw under higher ones (see [`crate::Painter`]'s draw-order
125/// notes). Every painter call takes `impl Into<Layer>`, so a bare integer works too.
126///
127/// Name your layers as constants: `const HUD: Layer = Layer(3);`.
128#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
129pub struct Layer(pub i16);
130
131impl Layer {
132    /// Layer 0, where the calls without a layer argument draw.
133    pub const DEFAULT: Self = Self(0);
134    /// The lowest layer.
135    pub const BOTTOM: Self = Self(i16::MIN);
136    /// The highest layer.
137    pub const TOP: Self = Self(i16::MAX);
138}
139
140impl From<i16> for Layer {
141    fn from(layer: i16) -> Self {
142        Self(layer)
143    }
144}
145
146impl From<Layer> for i16 {
147    fn from(layer: Layer) -> Self {
148        layer.0
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn hex_and_rgba8() {
158        assert_eq!(Color::hex(0xff0000), Color::RED);
159        assert_eq!(
160            Color::hex_rgba(0x00ff0080),
161            Color::GREEN.with_alpha(128.0 / 255.0)
162        );
163        assert_eq!(Color::rgb8(0, 0, 255), Color::BLUE);
164    }
165
166    #[test]
167    fn array_conversions_roundtrip() {
168        let c: Color = [0.1, 0.2, 0.3, 0.4].into();
169        assert_eq!(<[f32; 4]>::from(c), [0.1, 0.2, 0.3, 0.4]);
170        assert_eq!(Color::from([0.1, 0.2, 0.3]).a, 1.0);
171    }
172
173    #[test]
174    fn lerp_endpoints() {
175        assert_eq!(Color::BLACK.lerp(Color::WHITE, 0.0), Color::BLACK);
176        assert_eq!(Color::BLACK.lerp(Color::WHITE, 1.0), Color::WHITE);
177        assert_eq!(Color::BLACK.lerp(Color::WHITE, 0.5), Color::GRAY);
178    }
179
180    #[test]
181    fn layer_from_literal() {
182        fn take(l: impl Into<Layer>) -> Layer {
183            l.into()
184        }
185        assert_eq!(take(3), Layer(3));
186        assert!(Layer::BOTTOM < Layer::DEFAULT && Layer::DEFAULT < Layer::TOP);
187    }
188}