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 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
58 self.stops.push(stop.into());
59 self
60 }
61
62 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 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 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
138 self.stops.push(stop.into());
139 self
140 }
141
142 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 pub fn new() -> Self {
200 Self::default()
201 }
202
203 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
205 self.stops.push(stop.into());
206 self
207 }
208
209 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 pub fn angle(mut self, angle: f32) -> Self {
220 self.angle = Some(angle);
221 self
222 }
223
224 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}