use nalgebra_glm::Vec4;
use crate::ecs::ui::state::{UiBase, UiStateTrait};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hsla {
pub hue: f32,
pub saturation: f32,
pub lightness: f32,
pub alpha: f32,
}
impl Hsla {
pub fn from_rgba(rgba: Vec4) -> Self {
let red = rgba.x;
let green = rgba.y;
let blue = rgba.z;
let alpha = rgba.w;
let max = red.max(green).max(blue);
let min = red.min(green).min(blue);
let delta = max - min;
let lightness = (max + min) / 2.0;
if delta < f32::EPSILON {
return Self {
hue: 0.0,
saturation: 0.0,
lightness,
alpha,
};
}
let saturation = if lightness > 0.5 {
delta / (2.0 - max - min)
} else {
delta / (max + min)
};
let hue = if (max - red).abs() < f32::EPSILON {
let segment = (green - blue) / delta;
if segment < 0.0 {
segment + 6.0
} else {
segment
}
} else if (max - green).abs() < f32::EPSILON {
(blue - red) / delta + 2.0
} else {
(red - green) / delta + 4.0
} * 60.0;
Self {
hue,
saturation,
lightness,
alpha,
}
}
pub fn to_rgba(&self) -> Vec4 {
if self.saturation < f32::EPSILON {
return Vec4::new(self.lightness, self.lightness, self.lightness, self.alpha);
}
let chroma = (1.0 - (2.0 * self.lightness - 1.0).abs()) * self.saturation;
let hue_segment = self.hue / 60.0;
let secondary = chroma * (1.0 - (hue_segment % 2.0 - 1.0).abs());
let (red, green, blue) = match hue_segment as u32 {
0 => (chroma, secondary, 0.0),
1 => (secondary, chroma, 0.0),
2 => (0.0, chroma, secondary),
3 => (0.0, secondary, chroma),
4 => (secondary, 0.0, chroma),
_ => (chroma, 0.0, secondary),
};
let match_value = self.lightness - chroma / 2.0;
Vec4::new(
red + match_value,
green + match_value,
blue + match_value,
self.alpha,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hsva {
pub hue: f32,
pub saturation: f32,
pub value: f32,
pub alpha: f32,
}
impl Hsva {
pub fn from_rgba(rgba: Vec4) -> Self {
let red = rgba.x;
let green = rgba.y;
let blue = rgba.z;
let alpha = rgba.w;
let max = red.max(green).max(blue);
let min = red.min(green).min(blue);
let delta = max - min;
let value = max;
if delta < f32::EPSILON {
return Self {
hue: 0.0,
saturation: 0.0,
value,
alpha,
};
}
let saturation = delta / max;
let hue = if (max - red).abs() < f32::EPSILON {
let segment = (green - blue) / delta;
if segment < 0.0 {
segment + 6.0
} else {
segment
}
} else if (max - green).abs() < f32::EPSILON {
(blue - red) / delta + 2.0
} else {
(red - green) / delta + 4.0
} * 60.0;
Self {
hue,
saturation,
value,
alpha,
}
}
pub fn to_rgba(&self) -> Vec4 {
if self.saturation < f32::EPSILON {
return Vec4::new(self.value, self.value, self.value, self.alpha);
}
let chroma = self.value * self.saturation;
let hue_segment = self.hue / 60.0;
let secondary = chroma * (1.0 - (hue_segment % 2.0 - 1.0).abs());
let (red, green, blue) = match hue_segment as u32 {
0 => (chroma, secondary, 0.0),
1 => (secondary, chroma, 0.0),
2 => (0.0, chroma, secondary),
3 => (0.0, secondary, chroma),
4 => (secondary, 0.0, chroma),
_ => (chroma, 0.0, secondary),
};
let match_value = self.value - chroma;
Vec4::new(
red + match_value,
green + match_value,
blue + match_value,
self.alpha,
)
}
}
fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
let diff = b - a;
let adjusted = if diff > 180.0 {
diff - 360.0
} else if diff < -180.0 {
diff + 360.0
} else {
diff
};
(a + adjusted * t).rem_euclid(360.0)
}
pub fn blend_state_colors(colors: &[Option<Vec4>], weights: &[f32]) -> Vec4 {
let base_color = colors[UiBase::INDEX].unwrap_or(Vec4::new(1.0, 1.0, 1.0, 1.0));
let blend_count = colors.len().min(weights.len());
let total_non_base_weight: f32 = (1..blend_count)
.filter(|&index| colors[index].is_some())
.map(|index| weights[index])
.sum();
if total_non_base_weight < f32::EPSILON {
return base_color;
}
let base_hsla = Hsla::from_rgba(base_color);
let base_weight = (1.0 - total_non_base_weight).max(0.0);
let mut blended_hue = base_hsla.hue;
let mut blended_saturation = base_hsla.saturation * base_weight;
let mut blended_lightness = base_hsla.lightness * base_weight;
let mut blended_alpha = base_hsla.alpha * base_weight;
let total_weight = base_weight + total_non_base_weight;
for index in 1..blend_count {
let weight = weights[index];
if weight < f32::EPSILON {
continue;
}
let state_color = match colors[index] {
Some(color) => color,
None => continue,
};
let state_hsla = Hsla::from_rgba(state_color);
let normalized_weight = weight / total_weight;
blended_hue = lerp_hue(blended_hue, state_hsla.hue, normalized_weight);
blended_saturation += state_hsla.saturation * normalized_weight;
blended_lightness += state_hsla.lightness * normalized_weight;
blended_alpha += state_hsla.alpha * normalized_weight;
}
let result_hsla = Hsla {
hue: blended_hue,
saturation: blended_saturation.clamp(0.0, 1.0),
lightness: blended_lightness.clamp(0.0, 1.0),
alpha: blended_alpha.clamp(0.0, 1.0),
};
result_hsla.to_rgba()
}