1use std::{
2 f32::consts::FRAC_PI_2,
3 fmt::{
4 self,
5 Debug,
6 },
7 hash::{
8 Hash,
9 Hasher,
10 },
11};
12
13use freya_engine::prelude::*;
14use torin::prelude::Area;
15
16use crate::style::color::Color;
17
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[derive(Clone, Debug, Default, PartialEq)]
28pub struct GradientStop {
29 color: Color,
30 offset: f32,
31}
32
33impl Hash for GradientStop {
34 fn hash<H: Hasher>(&self, state: &mut H) {
35 self.color.hash(state);
36 self.offset.to_bits().hash(state);
37 }
38}
39
40impl fmt::Display for GradientStop {
41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 _ = self.color.fmt(f);
43 write!(f, " {}%", self.offset * 100.0)
44 }
45}
46
47impl GradientStop {
48 pub fn new(color: impl Into<Color>, offset: f32) -> Self {
50 Self {
51 color: color.into(),
52 offset: offset / 100.,
53 }
54 }
55}
56
57impl<C: Into<Color>> From<(C, f32)> for GradientStop {
58 fn from((color, offset): (C, f32)) -> Self {
59 GradientStop::new(color, offset)
60 }
61}
62
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75#[derive(Clone, Debug, Default, PartialEq)]
76pub struct LinearGradient {
77 stops: Vec<GradientStop>,
78 angle: f32,
79}
80
81impl Hash for LinearGradient {
82 fn hash<H: Hasher>(&self, state: &mut H) {
83 self.stops.hash(state);
84 self.angle.to_bits().hash(state);
85 }
86}
87
88impl LinearGradient {
89 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
96 self.stops.push(stop.into());
97 self
98 }
99
100 pub fn stops<I>(mut self, stops: I) -> Self
102 where
103 I: IntoIterator<Item = GradientStop>,
104 {
105 self.stops.extend(stops);
106 self
107 }
108
109 pub fn angle(mut self, angle: f32) -> Self {
111 self.angle = angle;
112 self
113 }
114
115 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
116 let colors: Vec<SkColor4f> = self
117 .stops
118 .iter()
119 .map(|stop| SkColor4f::from(stop.color))
120 .collect();
121 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
122
123 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
124 let grad = Gradient::new(grad_colors, Interpolation::default());
125
126 let (dy, dx) = (self.angle.to_radians() + FRAC_PI_2).sin_cos();
127 let farthest_corner = SkPoint::new(
128 if dx > 0.0 { bounds.width() } else { 0.0 },
129 if dy > 0.0 { bounds.height() } else { 0.0 },
130 );
131 let delta = farthest_corner - SkPoint::new(bounds.width(), bounds.height()) / 2.0;
132 let u = delta.x * dy - delta.y * dx;
133 let endpoint = farthest_corner + SkPoint::new(-u * dy, u * dx);
134
135 let origin = SkPoint::new(bounds.min_x(), bounds.min_y());
136 shaders::linear_gradient(
137 (
138 SkPoint::new(bounds.width(), bounds.height()) - endpoint + origin,
139 endpoint + origin,
140 ),
141 &grad,
142 None,
143 )
144 }
145}
146
147impl fmt::Display for LinearGradient {
148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
149 write!(
150 f,
151 "linear-gradient({}deg, {})",
152 self.angle,
153 self.stops
154 .iter()
155 .map(|stop| stop.to_string())
156 .collect::<Vec<_>>()
157 .join(", ")
158 )
159 }
160}
161
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173#[derive(Clone, Debug, Default, PartialEq)]
174pub struct RadialGradient {
175 stops: Vec<GradientStop>,
176}
177
178impl Hash for RadialGradient {
179 fn hash<H: Hasher>(&self, state: &mut H) {
180 self.stops.hash(state);
181 }
182}
183
184impl RadialGradient {
185 pub fn new() -> Self {
187 Self::default()
188 }
189
190 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
192 self.stops.push(stop.into());
193 self
194 }
195
196 pub fn stops<I>(mut self, stops: I) -> Self
198 where
199 I: IntoIterator<Item = GradientStop>,
200 {
201 self.stops.extend(stops);
202 self
203 }
204
205 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
206 let colors: Vec<SkColor4f> = self
207 .stops
208 .iter()
209 .map(|stop| SkColor4f::from(stop.color))
210 .collect();
211 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
212
213 let center = bounds.center();
214
215 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
216 let grad = Gradient::new(grad_colors, Interpolation::default());
217
218 shaders::radial_gradient(
219 (
220 SkPoint::new(center.x, center.y),
221 bounds.width().max(bounds.height()) / 2.0,
222 ),
223 &grad,
224 None,
225 )
226 }
227}
228
229impl fmt::Display for RadialGradient {
230 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231 write!(
232 f,
233 "radial-gradient({})",
234 self.stops
235 .iter()
236 .map(|stop| stop.to_string())
237 .collect::<Vec<_>>()
238 .join(", ")
239 )
240 }
241}
242
243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256#[derive(Clone, Debug, Default, PartialEq)]
257pub struct ConicGradient {
258 stops: Vec<GradientStop>,
259 angles: Option<(f32, f32)>,
260 angle: Option<f32>,
261}
262
263impl Hash for ConicGradient {
264 fn hash<H: Hasher>(&self, state: &mut H) {
265 self.stops.hash(state);
266 if let Some((start, end)) = self.angles {
267 start.to_bits().hash(state);
268 end.to_bits().hash(state);
269 }
270 if let Some(angle) = self.angle {
271 angle.to_bits().hash(state);
272 }
273 }
274}
275
276impl ConicGradient {
277 pub fn new() -> Self {
279 Self::default()
280 }
281
282 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
284 self.stops.push(stop.into());
285 self
286 }
287
288 pub fn stops<I>(mut self, stops: I) -> Self
290 where
291 I: IntoIterator<Item = GradientStop>,
292 {
293 self.stops.extend(stops);
294 self
295 }
296
297 pub fn angle(mut self, angle: f32) -> Self {
299 self.angle = Some(angle);
300 self
301 }
302
303 pub fn angles(mut self, start: f32, end: f32) -> Self {
305 self.angles = Some((start, end));
306 self
307 }
308
309 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
310 let colors: Vec<SkColor4f> = self
311 .stops
312 .iter()
313 .map(|stop| SkColor4f::from(stop.color))
314 .collect();
315 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
316
317 let center = bounds.center();
318
319 let matrix =
320 Matrix::rotate_deg_pivot(-90.0 + self.angle.unwrap_or(0.0), (center.x, center.y));
321
322 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
323 let grad = Gradient::new(grad_colors, Interpolation::default());
324
325 let (start_angle, end_angle) = self.angles.unwrap_or((0.0, 360.0));
326
327 shaders::sweep_gradient(
328 SkPoint::new(center.x, center.y),
329 (start_angle, end_angle),
330 &grad,
331 Some(&matrix),
332 )
333 }
334}
335
336impl fmt::Display for ConicGradient {
337 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
338 write!(f, "conic-gradient(")?;
339
340 if let Some(angle) = self.angle {
341 write!(f, "{angle}deg, ")?;
342 }
343
344 if let Some((start, end)) = self.angles {
345 write!(f, "from {start}deg to {end}deg, ")?;
346 }
347
348 write!(
349 f,
350 "{})",
351 self.stops
352 .iter()
353 .map(|stop| stop.to_string())
354 .collect::<Vec<_>>()
355 .join(", ")
356 )
357 }
358}