Skip to main content

simple_render/ui/types/
paint.rs

1use super::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
4pub struct Color {
5    pub red: u8,
6    pub green: u8,
7    pub blue: u8,
8    pub alpha: u8,
9}
10
11impl Color {
12    pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
13    pub const BLACK: Self = Self::rgb(0, 0, 0);
14    pub const WHITE: Self = Self::rgb(255, 255, 255);
15
16    pub const fn rgb(red: u8, green: u8, blue: u8) -> Self {
17        Self::rgba(red, green, blue, 255)
18    }
19
20    pub const fn rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
21        Self {
22            red,
23            green,
24            blue,
25            alpha,
26        }
27    }
28
29    pub const fn to_rgba(self) -> [u8; 4] {
30        [self.red, self.green, self.blue, self.alpha]
31    }
32
33    pub fn from_hex(value: &str) -> Result<Self, ColorParseError> {
34        value.parse()
35    }
36}
37
38impl From<[u8; 4]> for Color {
39    fn from([red, green, blue, alpha]: [u8; 4]) -> Self {
40        Self::rgba(red, green, blue, alpha)
41    }
42}
43
44impl From<[u8; 3]> for Color {
45    fn from([red, green, blue]: [u8; 3]) -> Self {
46        Self::rgb(red, green, blue)
47    }
48}
49
50impl From<Color> for [u8; 4] {
51    fn from(color: Color) -> Self {
52        color.to_rgba()
53    }
54}
55
56impl FromStr for Color {
57    type Err = ColorParseError;
58
59    fn from_str(value: &str) -> Result<Self, Self::Err> {
60        let hex = value.strip_prefix('#').unwrap_or(value);
61        if !hex.is_ascii() || !matches!(hex.len(), 6 | 8) {
62            return Err(ColorParseError);
63        }
64
65        let parse =
66            |offset| u8::from_str_radix(&hex[offset..offset + 2], 16).map_err(|_| ColorParseError);
67        let red = parse(0)?;
68        let green = parse(2)?;
69        let blue = parse(4)?;
70        let alpha = if hex.len() == 8 { parse(6)? } else { 255 };
71
72        Ok(Self::rgba(red, green, blue, alpha))
73    }
74}
75
76impl TryFrom<&str> for Color {
77    type Error = ColorParseError;
78
79    fn try_from(value: &str) -> Result<Self, Self::Error> {
80        value.parse()
81    }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct ColorParseError;
86
87impl fmt::Display for ColorParseError {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        formatter.write_str("expected #RRGGBB or #RRGGBBAA color")
90    }
91}
92
93impl Error for ColorParseError {}
94
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
96pub enum GradientDirection {
97    #[default]
98    Horizontal,
99    Vertical,
100    Diagonal,
101    DiagonalReverse,
102}
103
104pub(in crate::ui::types) fn interpolate_color(
105    from: Color,
106    to: Color,
107    position: u64,
108    max_position: u64,
109) -> Color {
110    Color::rgba(
111        interpolate_component(from.red, to.red, position, max_position),
112        interpolate_component(from.green, to.green, position, max_position),
113        interpolate_component(from.blue, to.blue, position, max_position),
114        interpolate_component(from.alpha, to.alpha, position, max_position),
115    )
116}
117
118fn interpolate_component(from: u8, to: u8, position: u64, max_position: u64) -> u8 {
119    let from = u64::from(from);
120    let to = u64::from(to);
121    if to >= from {
122        (from + (to - from) * position / max_position) as u8
123    } else {
124        (from - (from - to) * position / max_position) as u8
125    }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub enum Paint {
130    Solid(Color),
131    Gradient(Arc<[Color]>),
132}
133
134impl Paint {
135    pub fn solid(color: impl Into<Color>) -> Self {
136        Self::Solid(color.into())
137    }
138
139    pub fn gradient(colors: impl Into<Arc<[Color]>>) -> Self {
140        let colors = colors.into();
141        if colors.is_empty() {
142            Self::Solid(Color::TRANSPARENT)
143        } else {
144            Self::Gradient(colors)
145        }
146    }
147
148    pub fn first(&self) -> Color {
149        match self {
150            Self::Solid(color) => *color,
151            Self::Gradient(colors) => colors[0],
152        }
153    }
154
155    pub fn at(
156        &self,
157        x: u32,
158        y: u32,
159        width: u32,
160        height: u32,
161        direction: GradientDirection,
162    ) -> Color {
163        let colors = match self {
164            Self::Solid(color) => return *color,
165            Self::Gradient(colors) if colors.len() == 1 => return colors[0],
166            Self::Gradient(colors) => colors,
167        };
168
169        let (position, max_position) = match direction {
170            GradientDirection::Horizontal => (x, width.saturating_sub(1)),
171            GradientDirection::Vertical => (y, height.saturating_sub(1)),
172            GradientDirection::Diagonal => (
173                x.saturating_add(y),
174                width
175                    .saturating_sub(1)
176                    .saturating_add(height.saturating_sub(1)),
177            ),
178            GradientDirection::DiagonalReverse => (
179                width.saturating_sub(1).saturating_sub(x).saturating_add(y),
180                width
181                    .saturating_sub(1)
182                    .saturating_add(height.saturating_sub(1)),
183            ),
184        };
185        if max_position == 0 {
186            return colors[0];
187        }
188
189        let stop_count = colors.len() - 1;
190        let scaled = u64::from(position.min(max_position)) * stop_count as u64;
191        let max_position = u64::from(max_position);
192        let index = (scaled / max_position) as usize;
193        if index >= stop_count {
194            return colors[stop_count];
195        }
196
197        let remainder = scaled % max_position;
198        interpolate_color(colors[index], colors[index + 1], remainder, max_position)
199    }
200}
201
202impl Default for Paint {
203    fn default() -> Self {
204        Self::Solid(Color::TRANSPARENT)
205    }
206}
207
208impl From<Color> for Paint {
209    fn from(color: Color) -> Self {
210        Self::Solid(color)
211    }
212}
213
214impl From<[u8; 4]> for Paint {
215    fn from(color: [u8; 4]) -> Self {
216        Self::Solid(color.into())
217    }
218}
219
220impl From<[u8; 3]> for Paint {
221    fn from(color: [u8; 3]) -> Self {
222        Self::Solid(color.into())
223    }
224}