use std::fmt;
use freya_engine::prelude::*;
use torin::{
prelude::Measure,
size::Rect,
};
use crate::{
DisplayColor,
ExtSplit,
Parse,
ParseError,
};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GradientStop {
pub color: Color,
pub offset: f32,
}
impl Parse for GradientStop {
fn parse(value: &str) -> Result<Self, ParseError> {
let mut split = value.split_ascii_whitespace_excluding_group('(', ')');
let color_str = split.next().ok_or(ParseError)?;
let offset_str = split.next().ok_or(ParseError)?.trim();
if !offset_str.ends_with('%') || split.next().is_some() {
return Err(ParseError);
}
let offset = offset_str
.replacen('%', "", 1)
.parse::<f32>()
.map_err(|_| ParseError)?
/ 100.0;
Ok(GradientStop {
color: Color::parse(color_str).map_err(|_| ParseError)?,
offset,
})
}
}
impl fmt::Display for GradientStop {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
_ = self.color.fmt_rgb(f);
write!(f, " {}%", self.offset * 100.0)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LinearGradient {
pub stops: Vec<GradientStop>,
pub angle: f32,
}
impl LinearGradient {
pub fn into_shader(&self, bounds: Rect<f32, Measure>) -> Option<Shader> {
let colors: Vec<Color> = self.stops.iter().map(|stop| stop.color).collect();
let offsets: Vec<f32> = self.stops.iter().map(|stop| stop.offset).collect();
let (dx, dy) = (-self.angle).sin_cos();
let farthest_corner = Point::new(
if dx > 0.0 { bounds.width() } else { 0.0 },
if dy > 0.0 { bounds.height() } else { 0.0 },
);
let delta = farthest_corner - Point::new(bounds.width(), bounds.height()) / 2.0;
let u = delta.x * dy - delta.y * dx;
let endpoint = farthest_corner + Point::new(-u * dy, u * dx);
let origin = Point::new(bounds.min_x(), bounds.min_y());
Shader::linear_gradient(
(
Point::new(bounds.width(), bounds.height()) - endpoint + origin,
endpoint + origin,
),
GradientShaderColors::Colors(&colors[..]),
Some(&offsets[..]),
TileMode::Clamp,
None,
None,
)
}
}
impl Parse for LinearGradient {
fn parse(value: &str) -> Result<Self, ParseError> {
if !value.starts_with("linear-gradient(") || !value.ends_with(')') {
return Err(ParseError);
}
let mut gradient = LinearGradient::default();
let mut value = value.replacen("linear-gradient(", "", 1);
value.remove(value.rfind(')').ok_or(ParseError)?);
let mut split = value.split_excluding_group(',', '(', ')');
let angle_or_first_stop = split.next().ok_or(ParseError)?.trim();
if angle_or_first_stop.ends_with("deg") {
if let Ok(angle) = angle_or_first_stop.replacen("deg", "", 1).parse::<f32>() {
gradient.angle = angle.to_radians();
}
} else {
gradient
.stops
.push(GradientStop::parse(angle_or_first_stop).map_err(|_| ParseError)?);
}
for stop in split {
gradient
.stops
.push(GradientStop::parse(stop).map_err(|_| ParseError)?);
}
Ok(gradient)
}
}
impl fmt::Display for LinearGradient {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"linear-gradient({}deg, {})",
self.angle.to_degrees(),
self.stops
.iter()
.map(|stop| stop.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
}