use ratatui::style::{Color, Style};
use terminal_colorsaurus::{QueryOptions, background_color};
use super::widget::theme;
type Rgb = (u8, u8, u8);
const SELECTION_MIX: f32 = 0.28;
const DARK_LUMINANCE_THRESHOLD: f32 = 0.5;
const WHITE: Rgb = (u8::MAX, u8::MAX, u8::MAX);
const BLACK: Rgb = (u8::MIN, u8::MIN, u8::MIN);
pub fn detect_selection_style() -> Style {
match background_color(QueryOptions::default()) {
Ok(background) => selection_style_for(background.scale_to_8bit()),
Err(_) => theme::selection_style(),
}
}
fn selection_style_for(background: Rgb) -> Style {
let target = if relative_luminance(background) < DARK_LUMINANCE_THRESHOLD {
WHITE
} else {
BLACK
};
let selected = mix_rgb(background, target, SELECTION_MIX);
let foreground = if relative_luminance(selected) < DARK_LUMINANCE_THRESHOLD {
Color::White
} else {
Color::Black
};
Style::default()
.fg(foreground)
.bg(Color::Rgb(selected.0, selected.1, selected.2))
}
fn mix_rgb(base: Rgb, target: Rgb, amount: f32) -> Rgb {
fn channel(base: u8, target: u8, amount: f32) -> u8 {
(f32::from(base) + (f32::from(target) - f32::from(base)) * amount).round() as u8
}
(
channel(base.0, target.0, amount),
channel(base.1, target.1, amount),
channel(base.2, target.2, amount),
)
}
fn relative_luminance(color: Rgb) -> f32 {
fn channel(value: u8) -> f32 {
let value = f32::from(value) / 255.0;
if value <= 0.03928 {
value / 12.92
} else {
((value + 0.055) / 1.055).powf(2.4)
}
}
0.2126 * channel(color.0) + 0.7152 * channel(color.1) + 0.0722 * channel(color.2)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dark_background_yields_lightened_selection() {
let style = selection_style_for((0, 0, 0));
let gray = mix_rgb(BLACK, WHITE, SELECTION_MIX).0;
assert_eq!(style.bg, Some(Color::Rgb(gray, gray, gray)));
assert_eq!(style.fg, Some(Color::White));
}
#[test]
fn light_background_yields_darkened_selection() {
let style = selection_style_for(WHITE);
let gray = mix_rgb(WHITE, BLACK, SELECTION_MIX).0;
assert_eq!(style.bg, Some(Color::Rgb(gray, gray, gray)));
assert_eq!(style.fg, Some(Color::White));
}
#[test]
fn dark_navy_background_mixes_toward_white() {
let style = selection_style_for((26, 29, 41));
assert_eq!(
style.bg,
Some(Color::Rgb(
mix_rgb((26, 29, 41), WHITE, SELECTION_MIX).0,
mix_rgb((26, 29, 41), WHITE, SELECTION_MIX).1,
mix_rgb((26, 29, 41), WHITE, SELECTION_MIX).2
))
);
assert_eq!(style.fg, Some(Color::White));
}
}