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}