iced_graphics/
gradient.rs1use crate::color;
5use crate::core::gradient::ColorStop;
6use crate::core::{self, Color, Point, Rectangle};
7
8use bytemuck::{Pod, Zeroable};
9use half::f16;
10use std::cmp::Ordering;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum Gradient {
17 Linear(Linear),
20}
21
22impl From<Linear> for Gradient {
23 fn from(gradient: Linear) -> Self {
24 Self::Linear(gradient)
25 }
26}
27
28impl Gradient {
29 pub fn pack(&self) -> Packed {
31 match self {
32 Gradient::Linear(linear) => linear.pack(),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
39pub struct Linear {
40 pub start: Point,
42
43 pub end: Point,
45
46 pub stops: [Option<ColorStop>; 8],
48}
49
50impl Linear {
51 pub fn new(start: Point, end: Point) -> Self {
53 Self {
54 start,
55 end,
56 stops: [None; 8],
57 }
58 }
59
60 pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
66 if offset.is_finite() && (0.0..=1.0).contains(&offset) {
67 let (Ok(index) | Err(index)) = self.stops.binary_search_by(|stop| match stop {
68 None => Ordering::Greater,
69 Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
70 });
71
72 if index < 8 {
73 self.stops[index] = Some(ColorStop { offset, color });
74 }
75 } else {
76 log::warn!("Gradient: ColorStop must be within 0.0..=1.0 range.");
77 };
78
79 self
80 }
81
82 pub fn add_stops(mut self, stops: impl IntoIterator<Item = ColorStop>) -> Self {
86 for stop in stops {
87 self = self.add_stop(stop.offset, stop.color);
88 }
89
90 self
91 }
92
93 pub fn pack(&self) -> Packed {
95 let mut colors = [[0u32; 2]; 8];
96 let mut offsets = [f16::from(0u8); 8];
97
98 for (index, stop) in self.stops.iter().enumerate() {
99 let [r, g, b, a] = color::pack(stop.map_or(Color::default(), |s| s.color)).components();
100
101 colors[index] = [
102 pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
103 pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
104 ];
105
106 offsets[index] = stop.map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
107 }
108
109 let offsets = [
110 pack_f16s([offsets[0], offsets[1]]),
111 pack_f16s([offsets[2], offsets[3]]),
112 pack_f16s([offsets[4], offsets[5]]),
113 pack_f16s([offsets[6], offsets[7]]),
114 ];
115
116 let direction = [self.start.x, self.start.y, self.end.x, self.end.y];
117
118 Packed {
119 colors,
120 offsets,
121 direction,
122 }
123 }
124}
125
126#[derive(Debug, Copy, Clone, PartialEq, Zeroable, Pod)]
128#[repr(C)]
129pub struct Packed {
130 colors: [[u32; 2]; 8],
132 offsets: [u32; 4],
134 direction: [f32; 4],
135}
136
137pub fn pack(gradient: &core::Gradient, bounds: Rectangle) -> Packed {
139 match gradient {
140 core::Gradient::Linear(linear) => {
141 let mut colors = [[0u32; 2]; 8];
142 let mut offsets = [f16::from(0u8); 8];
143
144 for (index, stop) in linear.stops.iter().enumerate() {
145 let [r, g, b, a] =
146 color::pack(stop.map_or(Color::default(), |s| s.color)).components();
147
148 colors[index] = [
149 pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
150 pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
151 ];
152
153 offsets[index] = stop.map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
154 }
155
156 let offsets = [
157 pack_f16s([offsets[0], offsets[1]]),
158 pack_f16s([offsets[2], offsets[3]]),
159 pack_f16s([offsets[4], offsets[5]]),
160 pack_f16s([offsets[6], offsets[7]]),
161 ];
162
163 let (start, end) = linear.angle.to_distance(&bounds);
164
165 let direction = [start.x, start.y, end.x, end.y];
166
167 Packed {
168 colors,
169 offsets,
170 direction,
171 }
172 }
173 }
174}
175
176fn pack_f16s(f: [f16; 2]) -> u32 {
178 let one = (f[0].to_bits() as u32) << 16;
179 let two = f[1].to_bits() as u32;
180
181 one | two
182}