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 #[cfg_attr(feature = "serde", serde(default))]
78 pub(crate) dither: bool,
79 stops: Vec<GradientStop>,
80 angle: f32,
81}
82
83impl Hash for LinearGradient {
84 fn hash<H: Hasher>(&self, state: &mut H) {
85 self.dither.hash(state);
86 self.stops.hash(state);
87 self.angle.to_bits().hash(state);
88 }
89}
90
91impl LinearGradient {
92 pub fn new() -> Self {
94 Self::default()
95 }
96
97 pub fn dither(mut self, dither: bool) -> Self {
99 self.dither = dither;
100 self
101 }
102
103 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
105 self.stops.push(stop.into());
106 self
107 }
108
109 pub fn stops<I>(mut self, stops: I) -> Self
111 where
112 I: IntoIterator<Item = GradientStop>,
113 {
114 self.stops.extend(stops);
115 self
116 }
117
118 pub fn angle(mut self, angle: f32) -> Self {
120 self.angle = angle;
121 self
122 }
123
124 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
125 let colors: Vec<SkColor4f> = self
126 .stops
127 .iter()
128 .map(|stop| SkColor4f::from(stop.color))
129 .collect();
130 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
131
132 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
133 let grad = Gradient::new(grad_colors, Interpolation::default());
134
135 let (dy, dx) = (self.angle.to_radians() + FRAC_PI_2).sin_cos();
136 let farthest_corner = SkPoint::new(
137 if dx > 0.0 { bounds.width() } else { 0.0 },
138 if dy > 0.0 { bounds.height() } else { 0.0 },
139 );
140 let delta = farthest_corner - SkPoint::new(bounds.width(), bounds.height()) / 2.0;
141 let u = delta.x * dy - delta.y * dx;
142 let endpoint = farthest_corner + SkPoint::new(-u * dy, u * dx);
143
144 let origin = SkPoint::new(bounds.min_x(), bounds.min_y());
145 shaders::linear_gradient(
146 (
147 SkPoint::new(bounds.width(), bounds.height()) - endpoint + origin,
148 endpoint + origin,
149 ),
150 &grad,
151 None,
152 )
153 }
154}
155
156impl fmt::Display for LinearGradient {
157 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
158 write!(
159 f,
160 "linear-gradient({}deg, {})",
161 self.angle,
162 self.stops
163 .iter()
164 .map(|stop| stop.to_string())
165 .collect::<Vec<_>>()
166 .join(", ")
167 )
168 }
169}
170
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
182#[derive(Clone, Debug, Default, PartialEq)]
183pub struct RadialGradient {
184 #[cfg_attr(feature = "serde", serde(default))]
185 pub(crate) dither: bool,
186 stops: Vec<GradientStop>,
187}
188
189impl Hash for RadialGradient {
190 fn hash<H: Hasher>(&self, state: &mut H) {
191 self.dither.hash(state);
192 self.stops.hash(state);
193 }
194}
195
196impl RadialGradient {
197 pub fn new() -> Self {
199 Self::default()
200 }
201
202 pub fn dither(mut self, dither: bool) -> Self {
204 self.dither = dither;
205 self
206 }
207
208 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
210 self.stops.push(stop.into());
211 self
212 }
213
214 pub fn stops<I>(mut self, stops: I) -> Self
216 where
217 I: IntoIterator<Item = GradientStop>,
218 {
219 self.stops.extend(stops);
220 self
221 }
222
223 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
224 let colors: Vec<SkColor4f> = self
225 .stops
226 .iter()
227 .map(|stop| SkColor4f::from(stop.color))
228 .collect();
229 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
230
231 let center = bounds.center();
232
233 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
234 let grad = Gradient::new(grad_colors, Interpolation::default());
235
236 shaders::radial_gradient(
237 (
238 SkPoint::new(center.x, center.y),
239 bounds.width().max(bounds.height()) / 2.0,
240 ),
241 &grad,
242 None,
243 )
244 }
245}
246
247impl fmt::Display for RadialGradient {
248 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
249 write!(
250 f,
251 "radial-gradient({})",
252 self.stops
253 .iter()
254 .map(|stop| stop.to_string())
255 .collect::<Vec<_>>()
256 .join(", ")
257 )
258 }
259}
260
261#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
274#[derive(Clone, Debug, Default, PartialEq)]
275pub struct ConicGradient {
276 #[cfg_attr(feature = "serde", serde(default))]
277 pub(crate) dither: bool,
278 stops: Vec<GradientStop>,
279 angles: Option<(f32, f32)>,
280 angle: Option<f32>,
281}
282
283impl Hash for ConicGradient {
284 fn hash<H: Hasher>(&self, state: &mut H) {
285 self.dither.hash(state);
286 self.stops.hash(state);
287 if let Some((start, end)) = self.angles {
288 start.to_bits().hash(state);
289 end.to_bits().hash(state);
290 }
291 if let Some(angle) = self.angle {
292 angle.to_bits().hash(state);
293 }
294 }
295}
296
297impl ConicGradient {
298 pub fn new() -> Self {
300 Self::default()
301 }
302
303 pub fn dither(mut self, dither: bool) -> Self {
305 self.dither = dither;
306 self
307 }
308
309 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
311 self.stops.push(stop.into());
312 self
313 }
314
315 pub fn stops<I>(mut self, stops: I) -> Self
317 where
318 I: IntoIterator<Item = GradientStop>,
319 {
320 self.stops.extend(stops);
321 self
322 }
323
324 pub fn angle(mut self, angle: f32) -> Self {
326 self.angle = Some(angle);
327 self
328 }
329
330 pub fn angles(mut self, start: f32, end: f32) -> Self {
332 self.angles = Some((start, end));
333 self
334 }
335
336 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
337 let colors: Vec<SkColor4f> = self
338 .stops
339 .iter()
340 .map(|stop| SkColor4f::from(stop.color))
341 .collect();
342 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
343
344 let center = bounds.center();
345
346 let matrix =
347 Matrix::rotate_deg_pivot(-90.0 + self.angle.unwrap_or(0.0), (center.x, center.y));
348
349 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
350 let grad = Gradient::new(grad_colors, Interpolation::default());
351
352 let (start_angle, end_angle) = self.angles.unwrap_or((0.0, 360.0));
353
354 shaders::sweep_gradient(
355 SkPoint::new(center.x, center.y),
356 (start_angle, end_angle),
357 &grad,
358 Some(&matrix),
359 )
360 }
361}
362
363impl fmt::Display for ConicGradient {
364 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
365 write!(f, "conic-gradient(")?;
366
367 if let Some(angle) = self.angle {
368 write!(f, "{angle}deg, ")?;
369 }
370
371 if let Some((start, end)) = self.angles {
372 write!(f, "from {start}deg to {end}deg, ")?;
373 }
374
375 write!(
376 f,
377 "{})",
378 self.stops
379 .iter()
380 .map(|stop| stop.to_string())
381 .collect::<Vec<_>>()
382 .join(", ")
383 )
384 }
385}