| use crate::Color; |
| use dyn_any::DynAny; |
| use glam::{DAffine2, DVec2}; |
|
|
| #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, DynAny, specta::Type, node_macro::ChoiceType)] |
| #[widget(Radio)] |
| pub enum GradientType { |
| #[default] |
| Linear, |
| Radial, |
| } |
|
|
| |
| |
| |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, specta::Type)] |
| pub struct GradientStops(pub Vec<(f64, Color)>); |
|
|
| impl std::hash::Hash for GradientStops { |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| self.0.len().hash(state); |
| self.0.iter().for_each(|(position, color)| { |
| position.to_bits().hash(state); |
| color.hash(state); |
| }); |
| } |
| } |
|
|
| impl Default for GradientStops { |
| fn default() -> Self { |
| Self(vec![(0., Color::BLACK), (1., Color::WHITE)]) |
| } |
| } |
|
|
| impl IntoIterator for GradientStops { |
| type Item = (f64, Color); |
| type IntoIter = std::vec::IntoIter<(f64, Color)>; |
|
|
| fn into_iter(self) -> Self::IntoIter { |
| self.0.into_iter() |
| } |
| } |
|
|
| impl<'a> IntoIterator for &'a GradientStops { |
| type Item = &'a (f64, Color); |
| type IntoIter = std::slice::Iter<'a, (f64, Color)>; |
|
|
| fn into_iter(self) -> Self::IntoIter { |
| self.0.iter() |
| } |
| } |
|
|
| impl std::ops::Index<usize> for GradientStops { |
| type Output = (f64, Color); |
|
|
| fn index(&self, index: usize) -> &Self::Output { |
| &self.0[index] |
| } |
| } |
|
|
| impl std::ops::Deref for GradientStops { |
| type Target = Vec<(f64, Color)>; |
|
|
| fn deref(&self) -> &Self::Target { |
| &self.0 |
| } |
| } |
|
|
| impl std::ops::DerefMut for GradientStops { |
| fn deref_mut(&mut self) -> &mut Self::Target { |
| &mut self.0 |
| } |
| } |
|
|
| impl GradientStops { |
| pub fn new(stops: Vec<(f64, Color)>) -> Self { |
| let mut stops = Self(stops); |
| stops.sort(); |
| stops |
| } |
|
|
| pub fn evaluate(&self, t: f64) -> Color { |
| if self.0.is_empty() { |
| return Color::BLACK; |
| } |
|
|
| if t <= self.0[0].0 { |
| return self.0[0].1; |
| } |
| if t >= self.0[self.0.len() - 1].0 { |
| return self.0[self.0.len() - 1].1; |
| } |
|
|
| for i in 0..self.0.len() - 1 { |
| let (t1, c1) = self.0[i]; |
| let (t2, c2) = self.0[i + 1]; |
| if t >= t1 && t <= t2 { |
| let normalized_t = (t - t1) / (t2 - t1); |
| return c1.lerp(&c2, normalized_t as f32); |
| } |
| } |
|
|
| Color::BLACK |
| } |
|
|
| pub fn sort(&mut self) { |
| self.0.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); |
| } |
|
|
| pub fn reversed(&self) -> Self { |
| Self(self.0.iter().rev().map(|(position, color)| (1. - position, *color)).collect()) |
| } |
|
|
| pub fn map_colors<F: Fn(&Color) -> Color>(&self, f: F) -> Self { |
| Self(self.0.iter().map(|(position, color)| (*position, f(color))).collect()) |
| } |
| } |
|
|
| |
| |
| |
| #[repr(C)] |
| #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny, specta::Type)] |
| pub struct Gradient { |
| pub stops: GradientStops, |
| pub gradient_type: GradientType, |
| pub start: DVec2, |
| pub end: DVec2, |
| pub transform: DAffine2, |
| } |
|
|
| impl Default for Gradient { |
| fn default() -> Self { |
| Self { |
| stops: GradientStops::default(), |
| gradient_type: GradientType::Linear, |
| start: DVec2::new(0., 0.5), |
| end: DVec2::new(1., 0.5), |
| transform: DAffine2::IDENTITY, |
| } |
| } |
| } |
|
|
| impl std::hash::Hash for Gradient { |
| fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| self.stops.0.len().hash(state); |
| [].iter() |
| .chain(self.start.to_array().iter()) |
| .chain(self.end.to_array().iter()) |
| .chain(self.transform.to_cols_array().iter()) |
| .chain(self.stops.0.iter().map(|(position, _)| position)) |
| .for_each(|x| x.to_bits().hash(state)); |
| self.stops.0.iter().for_each(|(_, color)| color.hash(state)); |
| self.gradient_type.hash(state); |
| } |
| } |
|
|
| impl std::fmt::Display for Gradient { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| let round = |x: f64| (x * 1e3).round() / 1e3; |
| let stops = self |
| .stops |
| .0 |
| .iter() |
| .map(|(position, color)| format!("[{}%: #{}]", round(position * 100.), color.to_rgba_hex_srgb())) |
| .collect::<Vec<_>>() |
| .join(", "); |
| write!(f, "{} Gradient: {stops}", self.gradient_type) |
| } |
| } |
|
|
| impl Gradient { |
| |
| pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, transform: DAffine2, gradient_type: GradientType) -> Self { |
| Gradient { |
| start, |
| end, |
| stops: GradientStops::new(vec![(0., start_color.to_gamma_srgb()), (1., end_color.to_gamma_srgb())]), |
| transform, |
| gradient_type, |
| } |
| } |
|
|
| pub fn lerp(&self, other: &Self, time: f64) -> Self { |
| let start = self.start + (other.start - self.start) * time; |
| let end = self.end + (other.end - self.end) * time; |
| let transform = self.transform; |
| let stops = self |
| .stops |
| .0 |
| .iter() |
| .zip(other.stops.0.iter()) |
| .map(|((a_pos, a_color), (b_pos, b_color))| { |
| let position = a_pos + (b_pos - a_pos) * time; |
| let color = a_color.lerp(b_color, time as f32); |
| (position, color) |
| }) |
| .collect::<Vec<_>>(); |
| let stops = GradientStops::new(stops); |
| let gradient_type = if time < 0.5 { self.gradient_type } else { other.gradient_type }; |
|
|
| Self { |
| start, |
| end, |
| transform, |
| stops, |
| gradient_type, |
| } |
| } |
|
|
| |
| pub fn insert_stop(&mut self, mouse: DVec2, transform: DAffine2) -> Option<usize> { |
| |
| let (start, end) = (transform.transform_point2(self.start), transform.transform_point2(self.end)); |
|
|
| |
| let new_position = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end); |
|
|
| |
| if !(0. ..=1.).contains(&new_position) { |
| return None; |
| } |
|
|
| |
| let get_color = |index: usize, time: f64| match (self.stops.0[index].1, self.stops.0.get(index + 1).map(|(_, c)| *c)) { |
| |
| (a, Some(b)) => a.lerp( |
| &b, |
| ((time - self.stops.0[index].0) / self.stops.0.get(index + 1).map(|end| end.0 - self.stops.0[index].0).unwrap_or_default()) as f32, |
| ), |
| |
| (v, _) => v, |
| }; |
|
|
| |
| let mut index = 0; |
| while self.stops.0.len() > index && self.stops.0[index].0 <= new_position { |
| index += 1; |
| } |
|
|
| let new_color = get_color(index - 1, new_position); |
|
|
| |
| self.stops.0.insert(index, (new_position, new_color)); |
|
|
| Some(index) |
| } |
| } |
|
|