use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use kurbo::{Point, Rect, Size, Vec2};
use crate::{IntoBrush, RenderContext};
use crate::Color;
#[derive(Debug, Clone)]
pub struct FixedLinearGradient {
pub start: Point,
pub end: Point,
pub stops: Vec<GradientStop>,
}
#[derive(Debug, Clone)]
pub struct FixedRadialGradient {
pub center: Point,
pub origin_offset: Vec2,
pub radius: f64,
pub stops: Vec<GradientStop>,
}
#[derive(Debug, Clone)]
pub enum FixedGradient {
Linear(FixedLinearGradient),
Radial(FixedRadialGradient),
}
#[derive(Debug, Clone)]
pub struct GradientStop {
pub pos: f32,
pub color: Color,
}
pub trait GradientStops {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop>;
}
#[derive(Debug, Clone)]
pub struct LinearGradient {
start: UnitPoint,
end: UnitPoint,
stops: Vec<GradientStop>,
}
#[derive(Debug, Clone)]
pub struct RadialGradient {
center: UnitPoint,
origin: UnitPoint,
radius: f64,
stops: Vec<GradientStop>,
scale_mode: ScaleMode,
}
#[derive(Debug, Clone)]
pub enum ScaleMode {
Fit,
Fill,
}
#[derive(Debug, Clone, Copy)]
pub struct UnitPoint {
u: f64,
v: f64,
}
impl GradientStops for Vec<GradientStop> {
fn to_vec(self) -> Vec<GradientStop> {
self
}
}
impl<'a> GradientStops for &'a [GradientStop] {
fn to_vec(self) -> Vec<GradientStop> {
self.to_owned()
}
}
impl<'a> GradientStops for &'a [Color] {
fn to_vec(self) -> Vec<GradientStop> {
if self.is_empty() {
Vec::new()
} else {
let denom = (self.len() - 1).max(1) as f32;
self.iter()
.enumerate()
.map(|(i, c)| GradientStop {
pos: (i as f32) / denom,
color: c.to_owned(),
})
.collect()
}
}
}
impl GradientStops for (Color, Color) {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop> {
let stops: &[Color] = &[self.0, self.1];
GradientStops::to_vec(stops)
}
}
impl GradientStops for (Color, Color, Color) {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop> {
let stops: &[Color] = &[self.0, self.1, self.2];
GradientStops::to_vec(stops)
}
}
impl GradientStops for (Color, Color, Color, Color) {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop> {
let stops: &[Color] = &[self.0, self.1, self.2, self.3];
GradientStops::to_vec(stops)
}
}
impl GradientStops for (Color, Color, Color, Color, Color) {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop> {
let stops: &[Color] = &[self.0, self.1, self.2, self.3, self.4];
GradientStops::to_vec(stops)
}
}
impl GradientStops for (Color, Color, Color, Color, Color, Color) {
#[allow(clippy::wrong_self_convention)]
fn to_vec(self) -> Vec<GradientStop> {
let stops: &[Color] = &[self.0, self.1, self.2, self.3, self.4, self.5];
GradientStops::to_vec(stops)
}
}
impl UnitPoint {
pub const TOP_LEFT: UnitPoint = UnitPoint::new(0.0, 0.0);
pub const TOP: UnitPoint = UnitPoint::new(0.5, 0.0);
pub const TOP_RIGHT: UnitPoint = UnitPoint::new(1.0, 0.0);
pub const LEFT: UnitPoint = UnitPoint::new(0.0, 0.5);
pub const CENTER: UnitPoint = UnitPoint::new(0.5, 0.5);
pub const RIGHT: UnitPoint = UnitPoint::new(1.0, 0.5);
pub const BOTTOM_LEFT: UnitPoint = UnitPoint::new(0.0, 1.0);
pub const BOTTOM: UnitPoint = UnitPoint::new(0.5, 1.0);
pub const BOTTOM_RIGHT: UnitPoint = UnitPoint::new(1.0, 1.0);
pub const fn new(u: f64, v: f64) -> UnitPoint {
UnitPoint { u, v }
}
pub fn resolve(self, rect: Rect) -> Point {
Point::new(
rect.x0 + self.u * (rect.x1 - rect.x0),
rect.y0 + self.v * (rect.y1 - rect.y0),
)
}
}
impl LinearGradient {
pub fn new(start: UnitPoint, end: UnitPoint, stops: impl GradientStops) -> LinearGradient {
LinearGradient {
start,
end,
stops: stops.to_vec(),
}
}
fn resolve(&self, rect: Rect) -> FixedLinearGradient {
FixedLinearGradient {
start: self.start.resolve(rect),
end: self.end.resolve(rect),
stops: self.stops.clone(),
}
}
}
impl RadialGradient {
pub fn new(radius: f64, stops: impl GradientStops) -> Self {
RadialGradient {
center: UnitPoint::CENTER,
origin: UnitPoint::CENTER,
radius,
stops: stops.to_vec(),
scale_mode: ScaleMode::Fill,
}
}
pub fn with_center(mut self, center: UnitPoint) -> Self {
self.center = center;
self
}
pub fn with_origin(mut self, origin: UnitPoint) -> Self {
self.origin = origin;
self
}
pub fn with_scale_mode(mut self, scale_mode: ScaleMode) -> Self {
self.scale_mode = scale_mode;
self
}
fn resolve(&self, rect: Rect) -> FixedRadialGradient {
let scale_len = match self.scale_mode {
ScaleMode::Fill => rect.width().max(rect.height()),
ScaleMode::Fit => rect.width().min(rect.height()),
};
let rect = equalize_sides_preserving_center(rect, scale_len);
let center = self.center.resolve(rect);
let origin = self.origin.resolve(rect);
let origin_offset = origin - center;
let radius = self.radius * scale_len;
FixedRadialGradient {
center,
origin_offset,
radius,
stops: self.stops.clone(),
}
}
}
impl From<FixedLinearGradient> for FixedGradient {
fn from(src: FixedLinearGradient) -> FixedGradient {
FixedGradient::Linear(src)
}
}
impl From<FixedRadialGradient> for FixedGradient {
fn from(src: FixedRadialGradient) -> FixedGradient {
FixedGradient::Radial(src)
}
}
impl<P: RenderContext> IntoBrush<P> for FixedGradient {
fn make_brush<'a>(&'a self, piet: &mut P, _bbox: impl FnOnce() -> Rect) -> Cow<'a, P::Brush> {
Cow::Owned(
piet.gradient(self.to_owned())
.expect("error creating gradient"),
)
}
}
impl<P: RenderContext> IntoBrush<P> for LinearGradient {
fn make_brush<'a>(&'a self, piet: &mut P, bbox: impl FnOnce() -> Rect) -> Cow<'a, P::Brush> {
let rect = bbox();
let gradient = self.resolve(rect);
Cow::Owned(piet.gradient(gradient).expect("error creating gradient"))
}
}
impl<P: RenderContext> IntoBrush<P> for RadialGradient {
fn make_brush<'a>(&'a self, piet: &mut P, bbox: impl FnOnce() -> Rect) -> Cow<'a, P::Brush> {
let rect = bbox();
let gradient = self.resolve(rect);
Cow::Owned(piet.gradient(gradient).expect("error creating gradient"))
}
}
fn equalize_sides_preserving_center(rect: Rect, new_len: f64) -> Rect {
let size = Size::new(new_len, new_len);
let origin = rect.center() - size.to_vec2() / 2.;
Rect::from_origin_size(origin, size)
}
impl PartialEq for GradientStop {
fn eq(&self, other: &GradientStop) -> bool {
self.color == other.color && self.pos.to_bits() == other.pos.to_bits()
}
}
impl Eq for GradientStop {}
impl Hash for GradientStop {
fn hash<H: Hasher>(&self, state: &mut H) {
self.color.hash(state);
self.pos.to_bits().hash(state);
}
}