freya_core/style/
gradient.rs

1use std::{
2    f32::consts::FRAC_PI_2,
3    fmt::{
4        self,
5        Debug,
6    },
7};
8
9use freya_engine::prelude::*;
10use torin::prelude::Area;
11
12use crate::style::color::Color;
13
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[derive(Clone, Debug, Default, PartialEq)]
16pub struct GradientStop {
17    color: Color,
18    offset: f32,
19}
20
21impl fmt::Display for GradientStop {
22    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23        _ = self.color.fmt(f);
24        write!(f, " {}%", self.offset * 100.0)
25    }
26}
27
28impl GradientStop {
29    pub fn new(color: impl Into<Color>, offset: f32) -> Self {
30        Self {
31            color: color.into(),
32            offset: offset / 100.,
33        }
34    }
35}
36
37impl<C: Into<Color>> From<(C, f32)> for GradientStop {
38    fn from((color, offset): (C, f32)) -> Self {
39        GradientStop::new(color, offset)
40    }
41}
42
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[derive(Clone, Debug, Default, PartialEq)]
45pub struct LinearGradient {
46    stops: Vec<GradientStop>,
47    angle: f32,
48}
49
50impl LinearGradient {
51    /// Create an empty [LinearGradient] with defaults.
52    pub fn new() -> Self {
53        Self::default()
54    }
55
56    /// Add a single stop.
57    pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
58        self.stops.push(stop.into());
59        self
60    }
61
62    /// Add multiple stops.
63    pub fn stops<I>(mut self, stops: I) -> Self
64    where
65        I: IntoIterator<Item = GradientStop>,
66    {
67        self.stops.extend(stops);
68        self
69    }
70
71    /// Set angle (degrees).
72    pub fn angle(mut self, angle: f32) -> Self {
73        self.angle = angle;
74        self
75    }
76
77    pub fn into_shader(&self, bounds: Area) -> Option<Shader> {
78        let colors: Vec<SkColor4f> = self
79            .stops
80            .iter()
81            .map(|stop| SkColor4f::from(stop.color))
82            .collect();
83        let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
84
85        let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
86        let grad = Gradient::new(grad_colors, Flags::default());
87
88        let (dy, dx) = (self.angle.to_radians() + FRAC_PI_2).sin_cos();
89        let farthest_corner = SkPoint::new(
90            if dx > 0.0 { bounds.width() } else { 0.0 },
91            if dy > 0.0 { bounds.height() } else { 0.0 },
92        );
93        let delta = farthest_corner - SkPoint::new(bounds.width(), bounds.height()) / 2.0;
94        let u = delta.x * dy - delta.y * dx;
95        let endpoint = farthest_corner + SkPoint::new(-u * dy, u * dx);
96
97        let origin = SkPoint::new(bounds.min_x(), bounds.min_y());
98        shaders::linear_gradient(
99            (
100                SkPoint::new(bounds.width(), bounds.height()) - endpoint + origin,
101                endpoint + origin,
102            ),
103            &grad,
104            None,
105        )
106    }
107}
108
109impl fmt::Display for LinearGradient {
110    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111        write!(
112            f,
113            "linear-gradient({}deg, {})",
114            self.angle,
115            self.stops
116                .iter()
117                .map(|stop| stop.to_string())
118                .collect::<Vec<_>>()
119                .join(", ")
120        )
121    }
122}
123
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[derive(Clone, Debug, Default, PartialEq)]
126pub struct RadialGradient {
127    stops: Vec<GradientStop>,
128}
129
130impl RadialGradient {
131    /// Create an empty [RadialGradient] with defaults.
132    pub fn new() -> Self {
133        Self::default()
134    }
135
136    /// Add a single stop.
137    pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
138        self.stops.push(stop.into());
139        self
140    }
141
142    /// Add multiple stops.
143    pub fn stops<I>(mut self, stops: I) -> Self
144    where
145        I: IntoIterator<Item = GradientStop>,
146    {
147        self.stops.extend(stops);
148        self
149    }
150
151    pub fn into_shader(&self, bounds: Area) -> Option<Shader> {
152        let colors: Vec<SkColor4f> = self
153            .stops
154            .iter()
155            .map(|stop| SkColor4f::from(stop.color))
156            .collect();
157        let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
158
159        let center = bounds.center();
160
161        let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
162        let grad = Gradient::new(grad_colors, Flags::default());
163
164        shaders::radial_gradient(
165            (
166                SkPoint::new(center.x, center.y),
167                bounds.width().max(bounds.height()) / 2.0,
168            ),
169            &grad,
170            None,
171        )
172    }
173}
174
175impl fmt::Display for RadialGradient {
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        write!(
178            f,
179            "radial-gradient({})",
180            self.stops
181                .iter()
182                .map(|stop| stop.to_string())
183                .collect::<Vec<_>>()
184                .join(", ")
185        )
186    }
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190#[derive(Clone, Debug, Default, PartialEq)]
191pub struct ConicGradient {
192    stops: Vec<GradientStop>,
193    angles: Option<(f32, f32)>,
194    angle: Option<f32>,
195}
196
197impl ConicGradient {
198    /// Create an empty [ConicGradient] with defaults.
199    pub fn new() -> Self {
200        Self::default()
201    }
202
203    /// Add a single stop.
204    pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
205        self.stops.push(stop.into());
206        self
207    }
208
209    /// Add multiple stops.
210    pub fn stops<I>(mut self, stops: I) -> Self
211    where
212        I: IntoIterator<Item = GradientStop>,
213    {
214        self.stops.extend(stops);
215        self
216    }
217
218    /// Set explicit angle (degrees) for the gradient.
219    pub fn angle(mut self, angle: f32) -> Self {
220        self.angle = Some(angle);
221        self
222    }
223
224    /// Set start/end angles (degrees).
225    pub fn angles(mut self, start: f32, end: f32) -> Self {
226        self.angles = Some((start, end));
227        self
228    }
229
230    pub fn into_shader(&self, bounds: Area) -> Option<Shader> {
231        let colors: Vec<SkColor4f> = self
232            .stops
233            .iter()
234            .map(|stop| SkColor4f::from(stop.color))
235            .collect();
236        let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
237
238        let center = bounds.center();
239
240        let matrix =
241            Matrix::rotate_deg_pivot(-90.0 + self.angle.unwrap_or(0.0), (center.x, center.y));
242
243        let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
244        let grad = Gradient::new(grad_colors, Flags::default());
245
246        let (start_angle, end_angle) = self.angles.unwrap_or((0.0, 360.0));
247
248        shaders::sweep_gradient(
249            SkPoint::new(center.x, center.y),
250            (start_angle, end_angle),
251            &grad,
252            Some(&matrix),
253        )
254    }
255}
256
257impl fmt::Display for ConicGradient {
258    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259        write!(f, "conic-gradient(")?;
260
261        if let Some(angle) = self.angle {
262            write!(f, "{angle}deg, ")?;
263        }
264
265        if let Some((start, end)) = self.angles {
266            write!(f, "from {start}deg to {end}deg, ")?;
267        }
268
269        write!(
270            f,
271            "{})",
272            self.stops
273                .iter()
274                .map(|stop| stop.to_string())
275                .collect::<Vec<_>>()
276                .join(", ")
277        )
278    }
279}