use std::collections::HashMap;
use std::io::Write;
use std::time::{Duration, Instant};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Color;
use super::Placeholder;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Part {
pub kid: u32,
pub placement: u32,
pub area: Rect,
pub col: u16,
pub row: u16,
}
pub fn take_parts(buf: &mut Buffer, within: Rect) -> Vec<Part> {
let area = buf.area;
let mut done = Vec::new();
let mut open: Vec<Part> = Vec::new();
for y in area.top()..area.bottom() {
let mut runs: Vec<Part> = Vec::new();
for x in area.left()..area.right() {
let cell = &mut buf[(x, y)];
let Some(p) = Placeholder::of_symbol(cell.symbol(), cell.fg) else { continue };
let placement = color_id(cell.underline_color);
cell.set_symbol(" ");
cell.fg = Color::Reset;
cell.underline_color = Color::Reset;
if !within.contains((x, y).into()) {
continue;
}
let (col, row) = (p.col.min(usize::from(u16::MAX)) as u16, p.row.min(usize::from(u16::MAX)) as u16);
match runs.last_mut() {
Some(r)
if r.kid == p.id
&& r.placement == placement
&& r.area.right() == x
&& r.row == row
&& r.col.checked_add(r.area.width) == Some(col) =>
{
r.area.width += 1
}
_ => runs.push(Part { kid: p.id, placement, area: Rect::new(x, y, 1, 1), col, row }),
}
}
let mut next = Vec::with_capacity(runs.len());
for run in runs {
let above = open.iter().position(|o| {
(o.kid, o.placement, o.area.x, o.area.width, o.col)
== (run.kid, run.placement, run.area.x, run.area.width, run.col)
&& o.area.bottom() == y
&& o.row.checked_add(o.area.height) == Some(run.row)
});
match above {
Some(i) => {
let mut o = open.swap_remove(i);
o.area.height += 1;
next.push(o);
}
None => next.push(run),
}
}
done.append(&mut open);
open = next;
}
done.append(&mut open);
done
}
fn color_id(c: Color) -> u32 {
match c {
Color::Rgb(r, g, b) => u32::from(r) << 16 | u32::from(g) << 8 | u32::from(b),
Color::Indexed(i) => u32::from(i),
_ => 0,
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Drawn {
pub part: Part,
pub picture: usize,
pub cells: (u16, u16),
pub cell: (u16, u16),
}
#[derive(Default)]
pub struct Painted {
drawn: Vec<Drawn>,
}
impl Painted {
pub fn clear(&mut self) {
self.drawn.clear();
}
pub fn stale(&self, wanted: &[Drawn]) -> Vec<Rect> {
self.drawn.iter().filter(|d| !wanted.contains(d)).map(|d| d.part.area).collect()
}
pub fn update(&mut self, wanted: Vec<Drawn>, rewritten: impl Fn(Rect) -> bool) -> Vec<Drawn> {
let draw = wanted.iter().filter(|d| !self.drawn.contains(d) || rewritten(d.part.area)).cloned().collect();
self.drawn = wanted;
draw
}
pub fn forget(&mut self, part: &Drawn) {
self.drawn.retain(|d| d != part);
}
pub fn is_empty(&self) -> bool {
self.drawn.is_empty()
}
}
#[derive(Default)]
pub struct Motion {
origins: HashMap<u32, Vec<(i32, i32)>>,
moved: HashMap<u32, Instant>,
}
impl Motion {
pub fn track(&mut self, wanted: &[Drawn], now: Instant, still: Duration) -> HashMap<u32, Instant> {
let mut origins: HashMap<u32, Vec<(i32, i32)>> = HashMap::new();
for d in wanted {
let p = &d.part;
let at = (i32::from(p.area.x) - i32::from(p.col), i32::from(p.area.y) - i32::from(p.row));
origins.entry(p.kid).or_default().push(at);
}
for (kid, at) in &mut origins {
at.sort_unstable();
at.dedup();
if self.origins.get(kid).is_some_and(|before| before != at) {
self.moved.insert(*kid, now);
}
}
self.moved.retain(|kid, since| origins.contains_key(kid) && now.duration_since(*since) < still);
self.origins = origins;
self.moved.iter().map(|(kid, since)| (*kid, *since + still)).collect()
}
}
pub fn erase(area: Rect, out: &mut Vec<u8>) {
for y in area.top()..area.bottom() {
let _ = write!(out, "\x1b[{};{}H\x1b[{}X", y + 1, area.x + 1, area.width);
}
}
pub fn pixels(part: &Part, cell: (u16, u16)) -> (u32, u32, u32, u32) {
let (cw, ch) = (u32::from(cell.0), u32::from(cell.1));
let mut h = u32::from(part.area.height) * ch;
if h >= 6 {
h -= h % 6;
}
(u32::from(part.col) * cw, u32::from(part.row) * ch, u32::from(part.area.width) * cw, h)
}