use std::fmt;
use std::ops::Range;
use std::sync::Arc;
use std::time::Duration;
use rich::Segment;
use super::{Change, Frame};
pub trait Graphic: Send + Sync + fmt::Debug {
fn key(&self) -> u64;
fn draw(&self, frame: usize) -> String;
fn in_cells(&self) -> bool {
false
}
}
#[derive(Clone, Debug)]
pub struct Placement {
pub row: usize,
pub column: usize,
pub cols: usize,
pub rows: usize,
pub graphic: Arc<dyn Graphic>,
pub frame: usize,
}
impl Placement {
pub fn same(&self, other: &Placement) -> bool {
self.row == other.row
&& self.column == other.column
&& self.cols == other.cols
&& self.rows == other.rows
&& self.frame == other.frame
&& self.graphic.key() == other.graphic.key()
}
pub fn columns(&self) -> Range<usize> {
self.column..self.column + self.cols
}
pub fn touched_by(&self, change: &Change) -> bool {
(self.row..self.row + self.rows).contains(&change.row)
&& change.columns.start < self.column + self.cols
&& self.column < change.columns.end
}
}
pub trait PlacementSource: Send + Sync {
fn prepare(&self, line: Vec<Segment>) -> Vec<Segment> {
line
}
fn placements(&self, frame: &Frame) -> Vec<Placement>;
fn next_change(&self) -> Option<Duration> {
None
}
fn release(&self, retained: &[Placement]) -> String {
let _ = retained;
String::new()
}
}
#[derive(Clone, Debug, Default)]
pub struct GraphicsPlan {
pub repaint: Vec<(usize, Range<usize>)>,
pub draw: Vec<Placement>,
}
impl GraphicsPlan {
pub fn is_empty(&self) -> bool {
self.repaint.is_empty() && self.draw.is_empty()
}
}
pub fn plan(previous: &[Placement], next: &[Placement], changes: &[Change]) -> GraphicsPlan {
let mut out = GraphicsPlan::default();
for old in previous {
if old.graphic.in_cells() || next.iter().any(|new| new.same(old)) {
continue;
}
for row in old.row..old.row + old.rows {
let mut open = None;
for column in old.columns() {
let painted = changes
.iter()
.any(|change| change.row == row && change.columns.contains(&column));
match (painted, open) {
(false, None) => open = Some(column),
(true, Some(start)) => {
out.repaint.push((row, start..column));
open = None;
}
_ => {}
}
}
if let Some(start) = open {
out.repaint.push((row, start..old.column + old.cols));
}
}
}
for new in next {
let kept = previous.iter().any(|old| old.same(new));
let wiped = !new.graphic.in_cells()
&& (changes.iter().any(|change| new.touched_by(change))
|| out.repaint.iter().any(|(row, columns)| {
(new.row..new.row + new.rows).contains(row)
&& columns.start < new.column + new.cols
&& new.column < columns.end
}));
if !kept || wiped {
out.draw.push(new.clone());
}
}
out
}
impl Frame {
pub fn with_placements(mut self, placements: Vec<Placement>) -> Frame {
self.placements = placements;
self
}
pub fn set_placements(&mut self, placements: Vec<Placement>) {
self.placements = placements;
}
pub fn placements(&self) -> &[Placement] {
&self.placements
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct Image(u64, bool);
impl Graphic for Image {
fn key(&self) -> u64 {
self.0
}
fn draw(&self, frame: usize) -> String {
format!("<img{}:{frame}>", self.0)
}
fn in_cells(&self) -> bool {
self.1
}
}
fn at(row: usize, column: usize, key: u64, frame: usize) -> Placement {
Placement {
row,
column,
cols: 2,
rows: 1,
graphic: Arc::new(Image(key, false)),
frame,
}
}
fn change(row: usize, columns: Range<usize>) -> Change {
Change { row, columns }
}
#[test]
fn unchanged_placements_draw_nothing() {
let placed = [at(0, 4, 1, 0)];
assert!(plan(&placed, &placed, &[]).is_empty());
assert!(plan(&placed, &placed, &[change(0, 0..2)]).is_empty());
}
#[test]
fn new_moved_animated_and_wiped_placements_are_drawn() {
let old = [at(0, 4, 1, 0)];
assert_eq!(plan(&[], &old, &[]).draw.len(), 1);
let moved = plan(&old, &[at(0, 6, 1, 0)], &[]);
assert_eq!(moved.draw.len(), 1);
assert_eq!(moved.repaint, vec![(0, 4..6)]);
assert_eq!(plan(&old, &[at(0, 4, 1, 1)], &[]).draw.len(), 1);
let wiped = plan(&old, &old, &[change(0, 5..9)]);
assert_eq!(wiped.draw.len(), 1);
assert!(wiped.repaint.is_empty());
}
#[test]
fn a_removed_overlay_repaints_only_cells_the_diff_did_not() {
let old = [at(1, 4, 1, 0)];
let out = plan(&old, &[], &[change(1, 3..5)]);
assert_eq!(out.repaint, vec![(1, 5..6)]);
assert!(out.draw.is_empty());
}
#[test]
fn graphics_in_cells_are_drawn_once_and_never_repainted() {
let kitty = Placement {
graphic: Arc::new(Image(9, true)),
..at(0, 0, 9, 0)
};
assert_eq!(plan(&[], std::slice::from_ref(&kitty), &[]).draw.len(), 1);
let again = plan(
std::slice::from_ref(&kitty),
std::slice::from_ref(&kitty),
&[change(0, 0..2)],
);
assert!(again.is_empty());
assert!(plan(std::slice::from_ref(&kitty), &[], &[]).is_empty());
}
}