1use half::f16;
6use std::cmp::Ordering;
7use std::f32::consts::FRAC_PI_2;
8
9use super::color::PackedSrgb;
10use crate::math::{Angle, Point, Rect};
11
12pub const MAX_STOPS: usize = 8;
13
14#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum Gradient {
18 Linear(LinearGradient),
20}
21
22impl Gradient {
23 pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
25 match &mut self {
26 Gradient::Linear(linear) => {
27 for stop in linear.stops.iter_mut().flatten() {
28 *stop.color.a_mut() *= alpha_multiplier;
29 }
30 }
31 }
32
33 self
34 }
35
36 pub fn packed(&self, bounds: Rect) -> PackedGradient {
37 PackedGradient::new(self, bounds)
38 }
39}
40
41impl From<LinearGradient> for Gradient {
42 fn from(gradient: LinearGradient) -> Self {
43 Self::Linear(gradient)
44 }
45}
46
47impl Default for Gradient {
48 fn default() -> Self {
49 Gradient::Linear(LinearGradient::new(Angle::default()))
50 }
51}
52
53#[derive(Debug, Default, Clone, Copy, PartialEq)]
57pub struct ColorStop {
58 pub offset: f32,
60
61 pub color: PackedSrgb,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq)]
69pub struct LinearGradient {
70 pub angle: Angle,
72 pub stops: [Option<ColorStop>; MAX_STOPS],
74}
75
76impl LinearGradient {
77 pub const fn new(angle: Angle) -> Self {
79 Self {
80 angle: angle,
81 stops: [None; 8],
82 }
83 }
84
85 pub fn add_stop(mut self, offset: f32, color: impl Into<PackedSrgb>) -> Self {
91 if offset.is_finite() && (0.0..=1.0).contains(&offset) {
92 let (Ok(index) | Err(index)) = self.stops.binary_search_by(|stop| match stop {
93 None => Ordering::Greater,
94 Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
95 });
96
97 if index < 8 {
98 self.stops[index] = Some(ColorStop {
99 offset,
100 color: color.into(),
101 });
102 }
103 } else {
104 log::warn!("Gradient color stop must be within 0.0..=1.0 range.");
105 };
106
107 self
108 }
109
110 pub fn add_stops(mut self, stops: impl IntoIterator<Item = ColorStop>) -> Self {
114 for stop in stops {
115 self = self.add_stop(stop.offset, stop.color);
116 }
117
118 self
119 }
120}
121
122#[repr(C)]
124#[derive(Default, Debug, Copy, Clone, PartialEq, bytemuck::Zeroable, bytemuck::Pod)]
125pub struct PackedGradient {
126 pub colors: [[u32; 2]; 8],
128 pub offsets: [u32; 4],
130 pub direction: [f32; 4],
132}
133
134impl PackedGradient {
135 pub fn new(gradient: &Gradient, bounds: Rect) -> Self {
136 match gradient {
137 Gradient::Linear(linear) => {
138 let mut colors = [[0u32; 2]; 8];
139 let mut offsets = [f16::from(0u8); 8];
140
141 for (index, stop) in linear.stops.iter().enumerate() {
142 let packed_color = stop.map(|s| s.color).unwrap_or(PackedSrgb::default());
143
144 colors[index] = [
145 pack_f16s([
146 f16::from_f32(packed_color.r()),
147 f16::from_f32(packed_color.g()),
148 ]),
149 pack_f16s([
150 f16::from_f32(packed_color.b()),
151 f16::from_f32(packed_color.a()),
152 ]),
153 ];
154
155 offsets[index] = f16::from_f32(stop.map(|s| s.offset).unwrap_or(2.0));
156 }
157
158 let offsets = [
159 pack_f16s([offsets[0], offsets[1]]),
160 pack_f16s([offsets[2], offsets[3]]),
161 pack_f16s([offsets[4], offsets[5]]),
162 pack_f16s([offsets[6], offsets[7]]),
163 ];
164
165 let (start, end) = to_distance(linear.angle, &bounds);
166
167 let direction = [start.x, start.y, end.x, end.y];
168
169 PackedGradient {
170 colors,
171 offsets,
172 direction,
173 }
174 }
175 }
176 }
177}
178
179fn to_distance(angle: Angle, bounds: &Rect) -> (Point, Point) {
181 let angle = angle - Angle { radians: FRAC_PI_2 };
182
183 let r = Point::new(f32::cos(angle.radians), f32::sin(angle.radians));
184 let bounds_center = bounds.center();
185
186 let distance_to_rect = f32::max(
187 f32::abs(r.x * bounds.size.width / 2.0),
188 f32::abs(r.y * bounds.size.height / 2.0),
189 );
190
191 let start = Point::new(
192 bounds_center.x - (r.x * distance_to_rect),
193 bounds_center.y - (r.y * distance_to_rect),
194 );
195 let end = Point::new(
196 bounds_center.x + (r.x * distance_to_rect),
197 bounds_center.y + (r.y * distance_to_rect),
198 );
199
200 (start, end)
201}
202
203fn pack_f16s(f: [f16; 2]) -> u32 {
205 let one = (f[0].to_bits() as u32) << 16;
206 let two = f[1].to_bits() as u32;
207
208 one | two
209}