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))]
19#[derive(Clone, Debug, Default, PartialEq)]
20pub struct GradientStop {
21 color: Color,
22 offset: f32,
23}
24
25impl Hash for GradientStop {
26 fn hash<H: Hasher>(&self, state: &mut H) {
27 self.color.hash(state);
28 self.offset.to_bits().hash(state);
29 }
30}
31
32impl fmt::Display for GradientStop {
33 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 _ = self.color.fmt(f);
35 write!(f, " {}%", self.offset * 100.0)
36 }
37}
38
39impl GradientStop {
40 pub fn new(color: impl Into<Color>, offset: f32) -> Self {
41 Self {
42 color: color.into(),
43 offset: offset / 100.,
44 }
45 }
46}
47
48impl<C: Into<Color>> From<(C, f32)> for GradientStop {
49 fn from((color, offset): (C, f32)) -> Self {
50 GradientStop::new(color, offset)
51 }
52}
53
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[derive(Clone, Debug, Default, PartialEq)]
56pub struct LinearGradient {
57 stops: Vec<GradientStop>,
58 angle: f32,
59}
60
61impl Hash for LinearGradient {
62 fn hash<H: Hasher>(&self, state: &mut H) {
63 self.stops.hash(state);
64 self.angle.to_bits().hash(state);
65 }
66}
67
68impl LinearGradient {
69 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
76 self.stops.push(stop.into());
77 self
78 }
79
80 pub fn stops<I>(mut self, stops: I) -> Self
82 where
83 I: IntoIterator<Item = GradientStop>,
84 {
85 self.stops.extend(stops);
86 self
87 }
88
89 pub fn angle(mut self, angle: f32) -> Self {
91 self.angle = angle;
92 self
93 }
94
95 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
96 let colors: Vec<SkColor4f> = self
97 .stops
98 .iter()
99 .map(|stop| SkColor4f::from(stop.color))
100 .collect();
101 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
102
103 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
104 let grad = Gradient::new(grad_colors, Interpolation::default());
105
106 let (dy, dx) = (self.angle.to_radians() + FRAC_PI_2).sin_cos();
107 let farthest_corner = SkPoint::new(
108 if dx > 0.0 { bounds.width() } else { 0.0 },
109 if dy > 0.0 { bounds.height() } else { 0.0 },
110 );
111 let delta = farthest_corner - SkPoint::new(bounds.width(), bounds.height()) / 2.0;
112 let u = delta.x * dy - delta.y * dx;
113 let endpoint = farthest_corner + SkPoint::new(-u * dy, u * dx);
114
115 let origin = SkPoint::new(bounds.min_x(), bounds.min_y());
116 shaders::linear_gradient(
117 (
118 SkPoint::new(bounds.width(), bounds.height()) - endpoint + origin,
119 endpoint + origin,
120 ),
121 &grad,
122 None,
123 )
124 }
125}
126
127impl fmt::Display for LinearGradient {
128 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129 write!(
130 f,
131 "linear-gradient({}deg, {})",
132 self.angle,
133 self.stops
134 .iter()
135 .map(|stop| stop.to_string())
136 .collect::<Vec<_>>()
137 .join(", ")
138 )
139 }
140}
141
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143#[derive(Clone, Debug, Default, PartialEq)]
144pub struct RadialGradient {
145 stops: Vec<GradientStop>,
146}
147
148impl Hash for RadialGradient {
149 fn hash<H: Hasher>(&self, state: &mut H) {
150 self.stops.hash(state);
151 }
152}
153
154impl RadialGradient {
155 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
162 self.stops.push(stop.into());
163 self
164 }
165
166 pub fn stops<I>(mut self, stops: I) -> Self
168 where
169 I: IntoIterator<Item = GradientStop>,
170 {
171 self.stops.extend(stops);
172 self
173 }
174
175 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
176 let colors: Vec<SkColor4f> = self
177 .stops
178 .iter()
179 .map(|stop| SkColor4f::from(stop.color))
180 .collect();
181 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
182
183 let center = bounds.center();
184
185 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
186 let grad = Gradient::new(grad_colors, Interpolation::default());
187
188 shaders::radial_gradient(
189 (
190 SkPoint::new(center.x, center.y),
191 bounds.width().max(bounds.height()) / 2.0,
192 ),
193 &grad,
194 None,
195 )
196 }
197}
198
199impl fmt::Display for RadialGradient {
200 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
201 write!(
202 f,
203 "radial-gradient({})",
204 self.stops
205 .iter()
206 .map(|stop| stop.to_string())
207 .collect::<Vec<_>>()
208 .join(", ")
209 )
210 }
211}
212
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214#[derive(Clone, Debug, Default, PartialEq)]
215pub struct ConicGradient {
216 stops: Vec<GradientStop>,
217 angles: Option<(f32, f32)>,
218 angle: Option<f32>,
219}
220
221impl Hash for ConicGradient {
222 fn hash<H: Hasher>(&self, state: &mut H) {
223 self.stops.hash(state);
224 if let Some((start, end)) = self.angles {
225 start.to_bits().hash(state);
226 end.to_bits().hash(state);
227 }
228 if let Some(angle) = self.angle {
229 angle.to_bits().hash(state);
230 }
231 }
232}
233
234impl ConicGradient {
235 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
242 self.stops.push(stop.into());
243 self
244 }
245
246 pub fn stops<I>(mut self, stops: I) -> Self
248 where
249 I: IntoIterator<Item = GradientStop>,
250 {
251 self.stops.extend(stops);
252 self
253 }
254
255 pub fn angle(mut self, angle: f32) -> Self {
257 self.angle = Some(angle);
258 self
259 }
260
261 pub fn angles(mut self, start: f32, end: f32) -> Self {
263 self.angles = Some((start, end));
264 self
265 }
266
267 pub fn prepare_shader(&self, bounds: Area) -> Option<Shader> {
268 let colors: Vec<SkColor4f> = self
269 .stops
270 .iter()
271 .map(|stop| SkColor4f::from(stop.color))
272 .collect();
273 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
274
275 let center = bounds.center();
276
277 let matrix =
278 Matrix::rotate_deg_pivot(-90.0 + self.angle.unwrap_or(0.0), (center.x, center.y));
279
280 let grad_colors = Colors::new(&colors[..], Some(&offsets[..]), TileMode::Clamp, None);
281 let grad = Gradient::new(grad_colors, Interpolation::default());
282
283 let (start_angle, end_angle) = self.angles.unwrap_or((0.0, 360.0));
284
285 shaders::sweep_gradient(
286 SkPoint::new(center.x, center.y),
287 (start_angle, end_angle),
288 &grad,
289 Some(&matrix),
290 )
291 }
292}
293
294impl fmt::Display for ConicGradient {
295 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296 write!(f, "conic-gradient(")?;
297
298 if let Some(angle) = self.angle {
299 write!(f, "{angle}deg, ")?;
300 }
301
302 if let Some((start, end)) = self.angles {
303 write!(f, "from {start}deg to {end}deg, ")?;
304 }
305
306 write!(
307 f,
308 "{})",
309 self.stops
310 .iter()
311 .map(|stop| stop.to_string())
312 .collect::<Vec<_>>()
313 .join(", ")
314 )
315 }
316}