use mcu_contrast::Contrast;
use mcu_hct::Hct;
use mcu_utils::clamp_double;
use crate::color_calculation::ColorCalculationDelegate;
use crate::{DeltaConstraint, DynamicColor, DynamicScheme, TonePolarity};
#[derive(Debug, Clone, Copy, Default)]
pub struct ColorCalculationDelegate2025;
impl ColorCalculationDelegate for ColorCalculationDelegate2025 {
fn get_hct(&self, scheme: &DynamicScheme, color: &DynamicColor) -> Hct {
let palette = (color.palette)(scheme);
let tone = self.get_tone(scheme, color);
let hue = palette.hue();
let chroma = palette.chroma()
* color
.chroma_multiplier
.as_ref()
.map(|f| f(scheme))
.unwrap_or(1.0);
Hct::from(hue, chroma, tone)
}
fn get_tone(&self, scheme: &DynamicScheme, color: &DynamicColor) -> f64 {
let tone_delta_pair = color.tone_delta_pair.as_ref().and_then(|f| f(scheme));
if let Some(pair) = tone_delta_pair {
let role_a = &pair.role_a;
let role_b = &pair.role_b;
let polarity = pair.polarity;
let constraint = pair.constraint;
let absolute_delta = if matches!(polarity, TonePolarity::Darker)
|| (matches!(polarity, TonePolarity::RelativeLighter) && scheme.is_dark)
|| (matches!(polarity, TonePolarity::RelativeDarker) && !scheme.is_dark)
{
-pair.delta
} else {
pair.delta
};
let am_role_a = color.name == role_a.name;
let self_role = if am_role_a { role_a } else { role_b };
let ref_role = if am_role_a { role_b } else { role_a };
let mut self_tone = (self_role.tone)(scheme);
let ref_tone = (ref_role.tone)(scheme);
let relative_delta = absolute_delta * if am_role_a { 1.0 } else { -1.0 };
match constraint {
DeltaConstraint::Exact => {
self_tone = clamp_double(0.0, 100.0, ref_tone + relative_delta);
}
DeltaConstraint::Nearer => {
if relative_delta > 0.0 {
self_tone = clamp_double(
0.0,
100.0,
clamp_double(ref_tone, ref_tone + relative_delta, self_tone),
);
} else {
self_tone = clamp_double(
0.0,
100.0,
clamp_double(ref_tone + relative_delta, ref_tone, self_tone),
);
}
}
DeltaConstraint::Farther => {
if relative_delta > 0.0 {
self_tone = clamp_double(ref_tone + relative_delta, 100.0, self_tone);
} else {
self_tone = clamp_double(0.0, ref_tone + relative_delta, self_tone);
}
}
}
if let (Some(bg_fn), Some(curve_fn)) = (&color.background, &color.contrast_curve) {
if let (Some(bg), Some(curve)) = (bg_fn(scheme), curve_fn(scheme)) {
let bg_tone = bg.get_tone(scheme);
let self_contrast = curve.get(scheme.contrast_level);
if Contrast::ratio_of_tones(bg_tone, self_tone) < self_contrast
|| scheme.contrast_level < 0.0
{
self_tone = DynamicColor::foreground_tone(bg_tone, self_contrast);
}
}
}
if color.is_background && !color.name.ends_with("_fixed_dim") {
if self_tone >= 57.0 {
self_tone = clamp_double(65.0, 100.0, self_tone);
} else {
self_tone = clamp_double(0.0, 49.0, self_tone);
}
}
return self_tone;
}
let mut answer = (color.tone)(scheme);
let bg_opt = color.background.as_ref().and_then(|f| f(scheme));
let curve_opt = color.contrast_curve.as_ref().and_then(|f| f(scheme));
if bg_opt.is_none() || curve_opt.is_none() {
return answer;
}
let bg = bg_opt.unwrap();
let curve = curve_opt.unwrap();
let bg_tone = bg.get_tone(scheme);
let desired_ratio = curve.get(scheme.contrast_level);
if Contrast::ratio_of_tones(bg_tone, answer) < desired_ratio || scheme.contrast_level < 0.0
{
answer = DynamicColor::foreground_tone(bg_tone, desired_ratio);
}
if color.is_background && !color.name.ends_with("_fixed_dim") {
if answer >= 57.0 {
answer = clamp_double(65.0, 100.0, answer);
} else {
answer = clamp_double(0.0, 49.0, answer);
}
}
let second_bg_opt = color.second_background.as_ref().and_then(|f| f(scheme));
if second_bg_opt.is_none() {
return answer;
}
let bg1_tone = bg_tone;
let bg2_tone = second_bg_opt.unwrap().get_tone(scheme);
let upper = bg1_tone.max(bg2_tone);
let lower = bg1_tone.min(bg2_tone);
if Contrast::ratio_of_tones(upper, answer) >= desired_ratio
&& Contrast::ratio_of_tones(lower, answer) >= desired_ratio
{
return answer;
}
let light_option = Contrast::lighter(upper, desired_ratio);
let dark_option = Contrast::darker(lower, desired_ratio);
let prefers_light = DynamicColor::tone_prefers_light_foreground(bg1_tone)
|| DynamicColor::tone_prefers_light_foreground(bg2_tone);
if prefers_light {
if light_option < 0.0 {
100.0
} else {
light_option
}
} else if dark_option >= 0.0 {
dark_option
} else {
0.0
}
}
}