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<SkColor> = self.stops.iter().map(|stop| stop.color.into()).collect();
79 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
80
81 let (dy, dx) = (self.angle.to_radians() + FRAC_PI_2).sin_cos();
82 let farthest_corner = SkPoint::new(
83 if dx > 0.0 { bounds.width() } else { 0.0 },
84 if dy > 0.0 { bounds.height() } else { 0.0 },
85 );
86 let delta = farthest_corner - SkPoint::new(bounds.width(), bounds.height()) / 2.0;
87 let u = delta.x * dy - delta.y * dx;
88 let endpoint = farthest_corner + SkPoint::new(-u * dy, u * dx);
89
90 let origin = SkPoint::new(bounds.min_x(), bounds.min_y());
91 Shader::linear_gradient(
92 (
93 SkPoint::new(bounds.width(), bounds.height()) - endpoint + origin,
94 endpoint + origin,
95 ),
96 GradientShaderColors::Colors(&colors[..]),
97 Some(&offsets[..]),
98 TileMode::Clamp,
99 None,
100 None,
101 )
102 }
103}
104
105impl fmt::Display for LinearGradient {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107 write!(
108 f,
109 "linear-gradient({}deg, {})",
110 self.angle,
111 self.stops
112 .iter()
113 .map(|stop| stop.to_string())
114 .collect::<Vec<_>>()
115 .join(", ")
116 )
117 }
118}
119
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121#[derive(Clone, Debug, Default, PartialEq)]
122pub struct RadialGradient {
123 stops: Vec<GradientStop>,
124}
125
126impl RadialGradient {
127 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
134 self.stops.push(stop.into());
135 self
136 }
137
138 pub fn stops<I>(mut self, stops: I) -> Self
140 where
141 I: IntoIterator<Item = GradientStop>,
142 {
143 self.stops.extend(stops);
144 self
145 }
146
147 pub fn into_shader(&self, bounds: Area) -> Option<Shader> {
148 let colors: Vec<SkColor> = self.stops.iter().map(|stop| stop.color.into()).collect();
149 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
150
151 let center = bounds.center();
152
153 Shader::radial_gradient(
154 SkPoint::new(center.x, center.y),
155 bounds.width().max(bounds.height()) / 2.0,
156 GradientShaderColors::Colors(&colors[..]),
157 Some(&offsets[..]),
158 TileMode::Clamp,
159 None,
160 None,
161 )
162 }
163}
164
165impl fmt::Display for RadialGradient {
166 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167 write!(
168 f,
169 "radial-gradient({})",
170 self.stops
171 .iter()
172 .map(|stop| stop.to_string())
173 .collect::<Vec<_>>()
174 .join(", ")
175 )
176 }
177}
178
179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
180#[derive(Clone, Debug, Default, PartialEq)]
181pub struct ConicGradient {
182 stops: Vec<GradientStop>,
183 angles: Option<(f32, f32)>,
184 angle: Option<f32>,
185}
186
187impl ConicGradient {
188 pub fn new() -> Self {
190 Self::default()
191 }
192
193 pub fn stop(mut self, stop: impl Into<GradientStop>) -> Self {
195 self.stops.push(stop.into());
196 self
197 }
198
199 pub fn stops<I>(mut self, stops: I) -> Self
201 where
202 I: IntoIterator<Item = GradientStop>,
203 {
204 self.stops.extend(stops);
205 self
206 }
207
208 pub fn angle(mut self, angle: f32) -> Self {
210 self.angle = Some(angle);
211 self
212 }
213
214 pub fn angles(mut self, start: f32, end: f32) -> Self {
216 self.angles = Some((start, end));
217 self
218 }
219
220 pub fn into_shader(&self, bounds: Area) -> Option<Shader> {
221 let colors: Vec<SkColor> = self.stops.iter().map(|stop| stop.color.into()).collect();
222 let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
223
224 let center = bounds.center();
225
226 let matrix =
227 Matrix::rotate_deg_pivot(-90.0 + self.angle.unwrap_or(0.0), (center.x, center.y));
228
229 Shader::sweep_gradient(
230 (center.x, center.y),
231 GradientShaderColors::Colors(&colors[..]),
232 Some(&offsets[..]),
233 TileMode::Clamp,
234 self.angles,
235 None,
236 Some(&matrix),
237 )
238 }
239}
240
241impl fmt::Display for ConicGradient {
242 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
243 write!(f, "conic-gradient(")?;
244
245 if let Some(angle) = self.angle {
246 write!(f, "{angle}deg, ")?;
247 }
248
249 if let Some((start, end)) = self.angles {
250 write!(f, "from {start}deg to {end}deg, ")?;
251 }
252
253 write!(
254 f,
255 "{})",
256 self.stops
257 .iter()
258 .map(|stop| stop.to_string())
259 .collect::<Vec<_>>()
260 .join(", ")
261 )
262 }
263}