use ratatui_core::style::Color;
use super::PaintCx;
use crate::color::{Lift, Rgb, lift_apart};
use crate::geometry::Rect;
use crate::style::to_color;
const GROUND_SHARE: usize = 4;
#[derive(Debug, Default)]
struct Tally(Vec<(Rgb, usize)>);
impl Tally {
fn count(&mut self, color: Rgb) {
match self.0.iter_mut().find(|(seen, _)| *seen == color) {
Some((_, count)) => *count += 1,
None => self.0.push((color, 1)),
}
}
fn total(&self) -> usize {
self.0.iter().map(|(_, count)| count).sum()
}
fn grounds(&self) -> Vec<Rgb> {
let total = self.total();
self.0.iter().filter(|(_, count)| count * GROUND_SHARE >= total).map(|(color, _)| *color).collect()
}
fn dominant(&self) -> Option<Rgb> {
self.0.iter().max_by_key(|(_, count)| *count).map(|(color, _)| *color)
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Grounds(Vec<Rgb>);
impl PaintCx<'_> {
pub fn floating(&mut self, rect: Rect, paint: impl FnOnce(&mut Self)) {
let grounds = self.grounds_around(rect);
paint(self);
self.stand_apart(rect, &grounds, None);
}
pub(crate) fn grounds_around(&self, rect: Rect) -> Grounds {
if rect.is_empty() {
return Grounds::default();
}
let mut tally = Tally::default();
let (left, right, top, bottom) = (rect.x - 1, rect.right(), rect.y - 1, rect.bottom());
for x in left..=right {
self.tally_cell(&mut tally, x, top);
self.tally_cell(&mut tally, x, bottom);
}
for y in rect.y..rect.bottom() {
self.tally_cell(&mut tally, left, y);
self.tally_cell(&mut tally, right, y);
}
Grounds(tally.grounds())
}
pub(crate) fn stand_apart(&mut self, rect: Rect, grounds: &Grounds, background: Option<Rgb>) {
if let Some(lift) = self.lift_for(rect, grounds, background) {
self.lift(rect, lift);
}
}
pub(crate) fn lift_for(&self, rect: Rect, grounds: &Grounds, background: Option<Rgb>) -> Option<Lift> {
if grounds.0.is_empty() {
return None;
}
let background = background.or_else(|| self.dominant_background(rect))?;
lift_apart(background, &grounds.0, &[self.color("text"), self.color("canvas")])
}
pub(crate) fn lift(&mut self, rect: Rect, lift: Lift) {
self.each_cell(rect, |cell| {
if let Color::Rgb(r, g, b) = cell.bg {
cell.bg = to_color(lift.apply(Rgb::new(r, g, b)));
}
});
}
fn dominant_background(&self, rect: Rect) -> Option<Rgb> {
let area = rect.intersect(self.clip);
let mut tally = Tally::default();
for y in area.y..area.bottom() {
for x in area.x..area.right() {
self.tally_cell(&mut tally, x, y);
}
}
tally.dominant()
}
fn tally_cell(&self, tally: &mut Tally, x: i32, y: i32) {
let (Ok(x), Ok(y)) = (u16::try_from(x), u16::try_from(y)) else {
return;
};
if let Some(Color::Rgb(r, g, b)) = self.buf.cell((x, y)).map(|cell| cell.bg) {
tally.count(Rgb::new(r, g, b));
}
}
}