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
104#[derive(Clone, Copy, Debug, PartialEq)]
105pub struct PaintTransform {
106    pub scale: f32,
107    pub translate_x: i32,
108    pub translate_y: i32,
109}
110
111impl PaintTransform {
112    pub const IDENTITY: Self = Self {
113        scale: 1.0,
114        translate_x: 0,
115        translate_y: 0,
116    };
117
118    pub const fn new(scale: f32, translate_x: i32, translate_y: i32) -> Self {
119        Self {
120            scale,
121            translate_x,
122            translate_y,
123        }
124    }
125
126    pub const fn scale(scale: f32) -> Self {
127        Self {
128            scale,
129            ..Self::IDENTITY
130        }
131    }
132
133    pub const fn translate(translate_x: i32, translate_y: i32) -> Self {
134        Self {
135            translate_x,
136            translate_y,
137            ..Self::IDENTITY
138        }
139    }
140
141    pub fn compose(self, next: Self) -> Self {
142        let scale = sanitize_scale(self.scale) * sanitize_scale(next.scale);
143        Self {
144            scale,
145            translate_x: scale_i32_f32(next.translate_x, sanitize_scale(self.scale))
146                .saturating_add(self.translate_x),
147            translate_y: scale_i32_f32(next.translate_y, sanitize_scale(self.scale))
148                .saturating_add(self.translate_y),
149        }
150    }
151
152    pub fn is_identity(self) -> bool {
153        self.scale == 1.0 && self.translate_x == 0 && self.translate_y == 0
154    }
155}
156
157impl Default for PaintTransform {
158    fn default() -> Self {
159        Self::IDENTITY
160    }
161}
162
163pub(in crate::ui) fn sanitize_scale(scale: f32) -> f32 {
164    if scale.is_finite() {
165        scale.max(0.0)
166    } else {
167        0.0
168    }
169}
170
171pub(in crate::ui) fn scale_i32_f32(value: i32, scale: f32) -> i32 {
172    let scaled = f64::from(value) * f64::from(scale);
173    if !scaled.is_finite() {
174        return if scaled.is_sign_negative() {
175            i32::MIN
176        } else {
177            i32::MAX
178        };
179    }
180    scaled
181        .round()
182        .clamp(f64::from(i32::MIN), f64::from(i32::MAX)) as i32
183}
184
185pub(in crate::ui::types) fn interpolate_color(
186    from: Color,
187    to: Color,
188    position: u64,
189    max_position: u64,
190) -> Color {
191    Color::rgba(
192        interpolate_component(from.red, to.red, position, max_position),
193        interpolate_component(from.green, to.green, position, max_position),
194        interpolate_component(from.blue, to.blue, position, max_position),
195        interpolate_component(from.alpha, to.alpha, position, max_position),
196    )
197}
198
199fn interpolate_component(from: u8, to: u8, position: u64, max_position: u64) -> u8 {
200    let from = u64::from(from);
201    let to = u64::from(to);
202    if to >= from {
203        (from + (to - from) * position / max_position) as u8
204    } else {
205        (from - (from - to) * position / max_position) as u8
206    }
207}
208
209#[derive(Clone, Debug, PartialEq, Eq)]
210pub enum Paint {
211    Solid(Color),
212    Gradient(Arc<[Color]>),
213}
214
215impl Paint {
216    pub fn solid(color: impl Into<Color>) -> Self {
217        Self::Solid(color.into())
218    }
219
220    pub fn gradient(colors: impl Into<Arc<[Color]>>) -> Self {
221        let colors = colors.into();
222        if colors.is_empty() {
223            Self::Solid(Color::TRANSPARENT)
224        } else {
225            Self::Gradient(colors)
226        }
227    }
228
229    pub fn first(&self) -> Color {
230        match self {
231            Self::Solid(color) => *color,
232            Self::Gradient(colors) => colors[0],
233        }
234    }
235
236    pub fn at(
237        &self,
238        x: u32,
239        y: u32,
240        width: u32,
241        height: u32,
242        direction: GradientDirection,
243    ) -> Color {
244        let colors = match self {
245            Self::Solid(color) => return *color,
246            Self::Gradient(colors) if colors.len() == 1 => return colors[0],
247            Self::Gradient(colors) => colors,
248        };
249
250        let (position, max_position) = match direction {
251            GradientDirection::Horizontal => (x, width.saturating_sub(1)),
252            GradientDirection::Vertical => (y, height.saturating_sub(1)),
253            GradientDirection::Diagonal => (
254                x.saturating_add(y),
255                width
256                    .saturating_sub(1)
257                    .saturating_add(height.saturating_sub(1)),
258            ),
259            GradientDirection::DiagonalReverse => (
260                width.saturating_sub(1).saturating_sub(x).saturating_add(y),
261                width
262                    .saturating_sub(1)
263                    .saturating_add(height.saturating_sub(1)),
264            ),
265        };
266        if max_position == 0 {
267            return colors[0];
268        }
269
270        let stop_count = colors.len() - 1;
271        let scaled = u64::from(position.min(max_position)) * stop_count as u64;
272        let max_position = u64::from(max_position);
273        let index = (scaled / max_position) as usize;
274        if index >= stop_count {
275            return colors[stop_count];
276        }
277
278        let remainder = scaled % max_position;
279        interpolate_color(colors[index], colors[index + 1], remainder, max_position)
280    }
281}
282
283impl Default for Paint {
284    fn default() -> Self {
285        Self::Solid(Color::TRANSPARENT)
286    }
287}
288
289impl From<Color> for Paint {
290    fn from(color: Color) -> Self {
291        Self::Solid(color)
292    }
293}
294
295impl From<[u8; 4]> for Paint {
296    fn from(color: [u8; 4]) -> Self {
297        Self::Solid(color.into())
298    }
299}
300
301impl From<[u8; 3]> for Paint {
302    fn from(color: [u8; 3]) -> Self {
303        Self::Solid(color.into())
304    }
305}