use crate::{UiPosition, Val};
use bevy_color::{Color, Srgba};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::Vec2;
use bevy_reflect::prelude::*;
use bevy_utils::default;
use core::{f32, f32::consts::TAU};
#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
#[reflect(Default, PartialEq, Debug)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct ColorStop {
pub color: Color,
pub point: Val,
pub hint: f32,
}
impl ColorStop {
pub fn new(color: impl Into<Color>, point: Val) -> Self {
Self {
color: color.into(),
point,
hint: 0.5,
}
}
pub fn auto(color: impl Into<Color>) -> Self {
Self {
color: color.into(),
point: Val::Auto,
hint: 0.5,
}
}
pub fn px(color: impl Into<Color>, px: f32) -> Self {
Self {
color: color.into(),
point: Val::Px(px),
hint: 0.5,
}
}
pub fn percent(color: impl Into<Color>, percent: f32) -> Self {
Self {
color: color.into(),
point: Val::Percent(percent),
hint: 0.5,
}
}
pub const fn with_hint(mut self, hint: f32) -> Self {
self.hint = hint;
self
}
}
impl From<(Color, Val)> for ColorStop {
fn from((color, stop): (Color, Val)) -> Self {
Self {
color,
point: stop,
hint: 0.5,
}
}
}
impl From<Color> for ColorStop {
fn from(color: Color) -> Self {
Self {
color,
point: Val::Auto,
hint: 0.5,
}
}
}
impl From<Srgba> for ColorStop {
fn from(color: Srgba) -> Self {
Self {
color: color.into(),
point: Val::Auto,
hint: 0.5,
}
}
}
impl Default for ColorStop {
fn default() -> Self {
Self {
color: Color::WHITE,
point: Val::Auto,
hint: 0.5,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
#[reflect(Default, PartialEq, Debug)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct AngularColorStop {
pub color: Color,
pub angle: Option<f32>,
pub hint: f32,
}
impl AngularColorStop {
pub fn new(color: impl Into<Color>, angle: f32) -> Self {
Self {
color: color.into(),
angle: Some(angle),
hint: 0.5,
}
}
pub fn auto(color: impl Into<Color>) -> Self {
Self {
color: color.into(),
angle: None,
hint: 0.5,
}
}
pub const fn with_hint(mut self, hint: f32) -> Self {
self.hint = hint;
self
}
}
impl From<(Color, f32)> for AngularColorStop {
fn from((color, angle): (Color, f32)) -> Self {
Self {
color,
angle: Some(angle),
hint: 0.5,
}
}
}
impl From<Color> for AngularColorStop {
fn from(color: Color) -> Self {
Self {
color,
angle: None,
hint: 0.5,
}
}
}
impl From<Srgba> for AngularColorStop {
fn from(color: Srgba) -> Self {
Self {
color: color.into(),
angle: None,
hint: 0.5,
}
}
}
impl Default for AngularColorStop {
fn default() -> Self {
Self {
color: Color::WHITE,
angle: None,
hint: 0.5,
}
}
}
#[derive(Default, Clone, PartialEq, Debug, Reflect)]
#[reflect(PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct LinearGradient {
pub color_space: InterpolationColorSpace,
pub angle: f32,
pub stops: Vec<ColorStop>,
}
impl LinearGradient {
pub const TO_TOP: f32 = 0.;
pub const TO_TOP_RIGHT: f32 = TAU / 8.;
pub const TO_RIGHT: f32 = 2. * Self::TO_TOP_RIGHT;
pub const TO_BOTTOM_RIGHT: f32 = 3. * Self::TO_TOP_RIGHT;
pub const TO_BOTTOM: f32 = 4. * Self::TO_TOP_RIGHT;
pub const TO_BOTTOM_LEFT: f32 = 5. * Self::TO_TOP_RIGHT;
pub const TO_LEFT: f32 = 6. * Self::TO_TOP_RIGHT;
pub const TO_TOP_LEFT: f32 = 7. * Self::TO_TOP_RIGHT;
pub fn new(angle: f32, stops: Vec<ColorStop>) -> Self {
Self {
angle,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_top(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_TOP,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_top_right(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_TOP_RIGHT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_right(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_RIGHT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_bottom_right(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_BOTTOM_RIGHT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_bottom(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_BOTTOM,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_bottom_left(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_BOTTOM_LEFT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_left(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_LEFT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn to_top_left(stops: Vec<ColorStop>) -> Self {
Self {
angle: Self::TO_TOP_LEFT,
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn degrees(degrees: f32, stops: Vec<ColorStop>) -> Self {
Self {
angle: degrees.to_radians(),
stops,
color_space: InterpolationColorSpace::default(),
}
}
pub fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct RadialGradient {
pub color_space: InterpolationColorSpace,
pub position: UiPosition,
pub shape: RadialGradientShape,
pub stops: Vec<ColorStop>,
}
impl RadialGradient {
pub fn new(position: UiPosition, shape: RadialGradientShape, stops: Vec<ColorStop>) -> Self {
Self {
color_space: default(),
position,
shape,
stops,
}
}
pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}
impl Default for RadialGradient {
fn default() -> Self {
Self {
position: UiPosition::CENTER,
shape: RadialGradientShape::ClosestCorner,
stops: Vec::new(),
color_space: default(),
}
}
}
#[derive(Default, Clone, PartialEq, Debug, Reflect)]
#[reflect(PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct ConicGradient {
pub color_space: InterpolationColorSpace,
pub start: f32,
pub position: UiPosition,
pub stops: Vec<AngularColorStop>,
}
impl ConicGradient {
pub fn new(position: UiPosition, stops: Vec<AngularColorStop>) -> Self {
Self {
color_space: default(),
start: 0.,
position,
stops,
}
}
pub const fn with_start(mut self, start: f32) -> Self {
self.start = start;
self
}
pub const fn with_position(mut self, position: UiPosition) -> Self {
self.position = position;
self
}
pub const fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum Gradient {
Linear(LinearGradient),
Radial(RadialGradient),
Conic(ConicGradient),
}
impl Gradient {
pub const fn is_empty(&self) -> bool {
match self {
Gradient::Linear(gradient) => gradient.stops.is_empty(),
Gradient::Radial(gradient) => gradient.stops.is_empty(),
Gradient::Conic(gradient) => gradient.stops.is_empty(),
}
}
pub fn get_single(&self) -> Option<Color> {
match self {
Gradient::Linear(gradient) => gradient
.stops
.first()
.and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
Gradient::Radial(gradient) => gradient
.stops
.first()
.and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
Gradient::Conic(gradient) => gradient
.stops
.first()
.and_then(|stop| (gradient.stops.len() == 1).then_some(stop.color)),
}
}
}
impl From<LinearGradient> for Gradient {
fn from(value: LinearGradient) -> Self {
Self::Linear(value)
}
}
impl From<RadialGradient> for Gradient {
fn from(value: RadialGradient) -> Self {
Self::Radial(value)
}
}
impl From<ConicGradient> for Gradient {
fn from(value: ConicGradient) -> Self {
Self::Conic(value)
}
}
#[derive(Component, Clone, PartialEq, Debug, Default, Reflect)]
#[reflect(Component, Default, PartialEq, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BackgroundGradient(pub Vec<Gradient>);
impl<T: Into<Gradient>> From<T> for BackgroundGradient {
fn from(value: T) -> Self {
Self(vec![value.into()])
}
}
#[derive(Component, Clone, PartialEq, Debug, Default, Reflect)]
#[reflect(Component, Default, PartialEq, Debug, Clone)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct BorderGradient(pub Vec<Gradient>);
impl<T: Into<Gradient>> From<T> for BorderGradient {
fn from(value: T) -> Self {
Self(vec![value.into()])
}
}
#[derive(Default, Copy, Clone, PartialEq, Debug, Reflect)]
#[reflect(PartialEq, Default)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum RadialGradientShape {
ClosestSide,
FarthestSide,
#[default]
ClosestCorner,
FarthestCorner,
Circle(Val),
Ellipse(Val, Val),
}
const fn close_side(p: f32, h: f32) -> f32 {
(-h - p).abs().min((h - p).abs())
}
const fn far_side(p: f32, h: f32) -> f32 {
(-h - p).abs().max((h - p).abs())
}
const fn close_side2(p: Vec2, h: Vec2) -> f32 {
close_side(p.x, h.x).min(close_side(p.y, h.y))
}
const fn far_side2(p: Vec2, h: Vec2) -> f32 {
far_side(p.x, h.x).max(far_side(p.y, h.y))
}
impl RadialGradientShape {
pub fn resolve(
self,
position: Vec2,
scale_factor: f32,
physical_size: Vec2,
physical_target_size: Vec2,
) -> Vec2 {
let half_size = 0.5 * physical_size;
match self {
RadialGradientShape::ClosestSide => Vec2::splat(close_side2(position, half_size)),
RadialGradientShape::FarthestSide => Vec2::splat(far_side2(position, half_size)),
RadialGradientShape::ClosestCorner => Vec2::new(
close_side(position.x, half_size.x),
close_side(position.y, half_size.y),
),
RadialGradientShape::FarthestCorner => Vec2::new(
far_side(position.x, half_size.x),
far_side(position.y, half_size.y),
),
RadialGradientShape::Circle(radius) => Vec2::splat(
radius
.resolve(scale_factor, physical_size.x, physical_target_size)
.unwrap_or(0.),
),
RadialGradientShape::Ellipse(x, y) => Vec2::new(
x.resolve(scale_factor, physical_size.x, physical_target_size)
.unwrap_or(0.),
y.resolve(scale_factor, physical_size.y, physical_target_size)
.unwrap_or(0.),
),
}
}
}
#[derive(Default, Copy, Clone, Hash, Debug, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub enum InterpolationColorSpace {
#[default]
Oklaba,
Oklcha,
OklchaLong,
Srgba,
LinearRgba,
Hsla,
HslaLong,
Hsva,
HsvaLong,
}
pub trait InColorSpace: Sized {
fn in_color_space(self, color_space: InterpolationColorSpace) -> Self;
fn in_oklaba(self) -> Self {
self.in_color_space(InterpolationColorSpace::Oklaba)
}
fn in_oklch(self) -> Self {
self.in_color_space(InterpolationColorSpace::Oklcha)
}
fn in_oklch_long(self) -> Self {
self.in_color_space(InterpolationColorSpace::OklchaLong)
}
fn in_srgb(self) -> Self {
self.in_color_space(InterpolationColorSpace::Srgba)
}
fn in_linear_rgb(self) -> Self {
self.in_color_space(InterpolationColorSpace::LinearRgba)
}
}
impl InColorSpace for LinearGradient {
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}
impl InColorSpace for RadialGradient {
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}
impl InColorSpace for ConicGradient {
fn in_color_space(mut self, color_space: InterpolationColorSpace) -> Self {
self.color_space = color_space;
self
}
}