use ratatui::{style::Style, text::Span};
use rho_tools::tool_card::DiffRowKind;
use super::{
is_light_background, scheme_ansi, AnsiColor, BlockColor, ColorScheme, Palette, Rgb,
TerminalPalette, Theme, USER_BACKGROUND_ALPHA,
};
const GITHUB_DIFF_LINE_ALPHA: f32 = 0x26 as f32 / 255.0;
const MAX_DIFF_WASH_ALPHA: f32 = 0.35;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::tui) struct DiffRowChrome {
pub(in crate::tui) sign: Style,
wash: Option<Style>,
}
impl DiffRowChrome {
fn new(sign_base: Style, wash: Option<Style>) -> Self {
let chrome = Self {
sign: sign_base,
wash,
};
Self {
sign: chrome.washed(sign_base),
wash,
}
}
pub(in crate::tui) fn washed(self, base: Style) -> Style {
match self.wash {
Some(wash) => base.patch(wash),
None => base,
}
}
pub(in crate::tui) fn plain(self) -> Style {
if self.wash.is_some() {
self.washed(Theme::text())
} else {
self.sign
}
}
pub(in crate::tui) fn paint_content(self, spans: &mut [Span<'static>]) {
if self.wash.is_none() {
return;
}
for span in spans {
span.style = self.washed(span.style);
}
}
}
impl Theme {
pub(in crate::tui) fn tool_diff_chrome(kind: DiffRowKind) -> DiffRowChrome {
let palette = Palette::current();
let (wash_fill, sign_fg) = match kind {
DiffRowKind::Added => (palette.diff_add_wash, Some(palette.success)),
DiffRowKind::Removed => (palette.diff_del_wash, Some(palette.error)),
DiffRowKind::Context | DiffRowKind::File | DiffRowKind::Skip | DiffRowKind::Meta => {
(None, None)
}
};
let wash = wash_fill.map(|block| Style::default().bg(block.color));
let sign_base = sign_fg.map_or(Self::text(), |fg| Style::default().fg(fg));
DiffRowChrome::new(sign_base, wash)
}
}
pub(super) fn terminal_diff_washes(
terminal: Option<&TerminalPalette>,
) -> (Option<BlockColor>, Option<BlockColor>) {
(
terminal.and_then(|terminal| sampled_diff_wash(terminal, AnsiColor::Green)),
terminal.and_then(|terminal| sampled_diff_wash(terminal, AnsiColor::Red)),
)
}
pub(super) fn scheme_diff_washes(scheme: &ColorScheme) -> (Option<BlockColor>, Option<BlockColor>) {
(
Some(visible_diff_wash(
scheme.background,
scheme_ansi(scheme, AnsiColor::Green),
)),
Some(visible_diff_wash(
scheme.background,
scheme_ansi(scheme, AnsiColor::Red),
)),
)
}
fn sampled_diff_wash(terminal: &TerminalPalette, color: AnsiColor) -> Option<BlockColor> {
let tint = *terminal.ansi.get(&color)?;
Some(visible_diff_wash(terminal.background, tint))
}
fn visible_diff_wash(background: Rgb, tint: Rgb) -> BlockColor {
let mixed = background.blend_toward(tint, diff_wash_alpha(background, tint));
BlockColor::from_rgb(separate_from_surface(background, tint, mixed))
}
fn separate_from_surface(background: Rgb, tint: Rgb, mixed: Rgb) -> Rgb {
if mixed != background {
return mixed;
}
let dr = i16::from(tint.red) - i16::from(background.red);
let dg = i16::from(tint.green) - i16::from(background.green);
let db = i16::from(tint.blue) - i16::from(background.blue);
let abs_r = dr.unsigned_abs();
let abs_g = dg.unsigned_abs();
let abs_b = db.unsigned_abs();
if abs_r >= abs_g && abs_r >= abs_b && dr != 0 {
return Rgb::new(
step_toward(background.red, tint.red),
background.green,
background.blue,
);
}
if abs_g >= abs_b && dg != 0 {
return Rgb::new(
background.red,
step_toward(background.green, tint.green),
background.blue,
);
}
if db != 0 {
return Rgb::new(
background.red,
background.green,
step_toward(background.blue, tint.blue),
);
}
mixed
}
fn step_toward(from: u8, to: u8) -> u8 {
if to > from {
from.saturating_add(1)
} else if to < from {
from.saturating_sub(1)
} else {
from
}
}
fn diff_wash_alpha(background: Rgb, tint: Rgb) -> f32 {
let panel_ink = if is_light_background(background.luminance()) {
Rgb::new(0, 0, 0)
} else {
Rgb::new(255, 255, 255)
};
let panel = background.blend_toward(panel_ink, USER_BACKGROUND_ALPHA);
let target_delta = (panel.luminance() - background.luminance()).abs();
let tint_span = (tint.luminance() - background.luminance()).abs();
let matched = if tint_span == 0.0 {
GITHUB_DIFF_LINE_ALPHA
} else {
target_delta / tint_span
};
matched.clamp(GITHUB_DIFF_LINE_ALPHA, MAX_DIFF_WASH_ALPHA)
}