use std::f64::consts::TAU;
use vello::kurbo::{Point, Rect};
use vello::peniko::{
InterpolationAlphaSpace, LinearGradientPosition, RadialGradientPosition, SweepGradientPosition,
};
use crate::peniko::color::{ColorSpaceTag, HueDirection};
use crate::peniko::{ColorStops, ColorStopsSource, Extend};
use crate::properties::types::UnitPoint;
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum GradientShape {
Linear {
angle: f64,
},
Radial {
center: UnitPoint,
shape: RadialGradientShape,
},
Sweep {
center: UnitPoint,
start_angle: f64,
end_angle: f64,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Gradient {
pub shape: GradientShape,
pub extend: Extend,
pub interpolation_cs: ColorSpaceTag,
pub hue_direction: HueDirection,
pub stops: ColorStops,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RadialGradientShape {
CircleTo(RadialGradientExtent),
FixedCircle(f64),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RadialGradientExtent {
ClosestCorner,
ClosestSide,
FarthestSide,
FarthestCorner,
}
impl Gradient {
pub fn new(shape: GradientShape) -> Self {
Self {
shape,
extend: Extend::default(),
interpolation_cs: ColorSpaceTag::Srgb,
hue_direction: HueDirection::default(),
stops: ColorStops::default(),
}
}
pub fn new_linear(angle: f64) -> Self {
let shape = GradientShape::Linear { angle };
Self::new(shape)
}
pub fn new_radial(center: UnitPoint) -> Self {
let shape = RadialGradientShape::CircleTo(RadialGradientExtent::FarthestCorner);
let shape = GradientShape::Radial { center, shape };
Self::new(shape)
}
pub fn new_radial_with(center: UnitPoint, shape: RadialGradientShape) -> Self {
let shape = GradientShape::Radial { center, shape };
Self::new(shape)
}
pub fn new_sweep(center: UnitPoint, start_angle: f64, end_angle: f64) -> Self {
let shape = GradientShape::Sweep {
center,
start_angle,
end_angle,
};
Self::new(shape)
}
pub fn new_full_sweep(center: UnitPoint, angle: f64) -> Self {
let start_angle = angle;
let end_angle = angle + TAU;
let shape = GradientShape::Sweep {
center,
start_angle,
end_angle,
};
Self::new(shape)
}
pub fn with_stops(mut self, stops: impl ColorStopsSource) -> Self {
self.stops.clear();
stops.collect_stops(&mut self.stops);
self
}
pub fn get_peniko_gradient_for_rect(&self, rect: Rect) -> crate::peniko::Gradient {
crate::peniko::Gradient {
kind: self.shape.get_peniko_kind_for_rect(rect),
extend: self.extend,
interpolation_cs: self.interpolation_cs,
hue_direction: self.hue_direction,
stops: self.stops.clone(),
interpolation_alpha_space: InterpolationAlphaSpace::default(),
}
}
}
#[expect(
clippy::cast_possible_truncation,
reason = "no other way to go from f64 to f32"
)]
impl GradientShape {
pub fn get_peniko_kind_for_rect(&self, rect: Rect) -> crate::peniko::GradientKind {
match self {
Self::Linear { angle } => Self::get_peniko_linear_for_rect(*angle, rect),
Self::Radial { center, shape } => {
Self::get_peniko_radial_for_rect(*center, *shape, rect)
}
Self::Sweep {
center,
start_angle,
end_angle,
} => Self::get_peniko_sweep_for_rect(*center, *start_angle, *end_angle, rect),
}
}
fn get_peniko_linear_for_rect(angle: f64, rect: Rect) -> crate::peniko::GradientKind {
let size = rect.size();
let sin_a = angle.sin();
let cos_a = angle.cos();
let gradient_line_length = (size.width * sin_a).abs() + (size.height * cos_a).abs();
let center = rect.center();
let x = sin_a * gradient_line_length / 2.0;
let y = cos_a * gradient_line_length / 2.0;
let start = Point::new(center.x - x, center.y - y);
let end = Point::new(center.x + x, center.y + y);
LinearGradientPosition { start, end }.into()
}
fn get_peniko_radial_for_rect(
center: UnitPoint,
shape: RadialGradientShape,
rect: Rect,
) -> crate::peniko::GradientKind {
let center = center.resolve(rect);
let radius = Self::get_gradient_radius(rect, center, shape);
RadialGradientPosition {
start_center: center,
start_radius: 0.,
end_center: center,
end_radius: radius as f32,
}
.into()
}
fn get_gradient_radius(rect: Rect, center: Point, shape: RadialGradientShape) -> f64 {
let center_offset = center - rect.origin();
let dist_to_sides = [
f64::abs(center_offset.x),
f64::abs(center_offset.y),
f64::abs(rect.size().width - center_offset.x),
f64::abs(rect.size().height - center_offset.y),
];
let dist_to_corners = [
center.distance(Point::new(rect.x0, rect.y0)),
center.distance(Point::new(rect.x1, rect.y0)),
center.distance(Point::new(rect.x0, rect.y1)),
center.distance(Point::new(rect.x1, rect.y1)),
];
match shape {
RadialGradientShape::CircleTo(RadialGradientExtent::ClosestCorner) => {
dist_to_corners.into_iter().reduce(f64::min).unwrap()
}
RadialGradientShape::CircleTo(RadialGradientExtent::ClosestSide) => {
dist_to_sides.into_iter().reduce(f64::min).unwrap()
}
RadialGradientShape::CircleTo(RadialGradientExtent::FarthestSide) => {
dist_to_sides.into_iter().reduce(f64::max).unwrap()
}
RadialGradientShape::CircleTo(RadialGradientExtent::FarthestCorner) => {
dist_to_corners.into_iter().reduce(f64::max).unwrap()
}
RadialGradientShape::FixedCircle(radius) => radius,
}
}
fn get_peniko_sweep_for_rect(
center: UnitPoint,
start_angle: f64,
end_angle: f64,
rect: Rect,
) -> crate::peniko::GradientKind {
let center = center.resolve(rect);
#[cfg(false)]
crate::peniko::GradientKind::Sweep {
center,
start_angle: (-start_angle - FRAC_PI_2) as f32,
end_angle: (-end_angle - FRAC_PI_2) as f32,
};
SweepGradientPosition {
center,
start_angle: start_angle as f32,
end_angle: end_angle as f32,
}
.into()
}
}
impl Default for RadialGradientShape {
fn default() -> Self {
Self::CircleTo(RadialGradientExtent::FarthestCorner)
}
}