use crate::style::Style;
use std::cmp;
use std::iter::ExactSizeIterator;
use std::ops::Range;
use term::Terminal;
mod row;
#[cfg(test)]
mod test;
#[cfg(test)]
mod test_util;
pub mod style;
pub use self::row::Row;
pub trait AsciiView {
fn columns(&self) -> usize;
fn read_char(&mut self, row: usize, column: usize) -> char;
fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style);
}
impl<'a> dyn AsciiView + 'a {
fn add_box_dirs(&mut self, row: usize, column: usize, dirs: u8) {
let old_ch = self.read_char(row, column);
let new_ch = add_dirs(old_ch, dirs);
self.write_char(row, column, new_ch, Style::new());
}
pub fn draw_vertical_line(&mut self, rows: Range<usize>, column: usize) {
let len = rows.len();
for (index, r) in rows.enumerate() {
let new_dirs = if index == 0 {
DOWN
} else if index == len - 1 {
UP
} else {
UP | DOWN
};
self.add_box_dirs(r, column, new_dirs);
}
}
pub fn draw_horizontal_line(&mut self, row: usize, columns: Range<usize>) {
let len = columns.len();
for (index, c) in columns.enumerate() {
let new_dirs = if index == 0 {
RIGHT
} else if index == len - 1 {
LEFT
} else {
LEFT | RIGHT
};
self.add_box_dirs(row, c, new_dirs);
}
}
pub fn write_chars<I>(&mut self, row: usize, column: usize, chars: I, style: Style)
where
I: Iterator<Item = char>,
{
for (i, ch) in chars.enumerate() {
self.write_char(row, column + i, ch, style);
}
}
pub fn shift(&mut self, row: usize, column: usize) -> ShiftedView {
ShiftedView::new(self, row, column)
}
pub fn styled(&mut self, style: Style) -> StyleView {
StyleView::new(self, style)
}
}
pub struct AsciiCanvas {
columns: usize,
rows: usize,
characters: Vec<char>,
styles: Vec<Style>,
}
impl AsciiCanvas {
pub fn new(rows: usize, columns: usize) -> Self {
AsciiCanvas {
rows,
columns,
characters: vec![' '; columns * rows],
styles: vec![Style::new(); columns * rows],
}
}
fn grow_rows_if_needed(&mut self, new_rows: usize) {
if new_rows >= self.rows {
let new_chars = (new_rows - self.rows) * self.columns;
self.characters.extend((0..new_chars).map(|_| ' '));
self.styles.extend((0..new_chars).map(|_| Style::new()));
self.rows = new_rows;
}
}
fn index(&mut self, r: usize, c: usize) -> usize {
self.grow_rows_if_needed(r + 1);
self.in_range_index(r, c)
}
fn in_range_index(&self, r: usize, c: usize) -> usize {
assert!(r < self.rows);
assert!(c <= self.columns);
r * self.columns + c
}
fn start_index(&self, r: usize) -> usize {
self.in_range_index(r, 0)
}
fn end_index(&self, r: usize) -> usize {
self.in_range_index(r, self.columns)
}
pub fn write_to<T: Terminal + ?Sized>(&self, term: &mut T) -> term::Result<()> {
for row in self.to_strings() {
row.write_to(term)?;
writeln!(term)?;
}
Ok(())
}
pub fn to_strings(&self) -> Vec<Row> {
(0..self.rows)
.map(|row| {
let start = self.start_index(row);
let end = self.end_index(row);
let chars = &self.characters[start..end];
let styles = &self.styles[start..end];
Row::new(chars, styles)
})
.collect()
}
}
impl AsciiView for AsciiCanvas {
fn columns(&self) -> usize {
self.columns
}
fn read_char(&mut self, row: usize, column: usize) -> char {
assert!(column < self.columns);
let index = self.index(row, column);
self.characters[index]
}
fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
assert!(column < self.columns);
let index = self.index(row, column);
self.characters[index] = ch;
self.styles[index] = style;
}
}
#[derive(Copy, Clone)]
struct Point {
row: usize,
column: usize,
}
pub struct ShiftedView<'canvas> {
base: &'canvas mut dyn AsciiView,
upper_left: Point,
lower_right: Point,
}
impl<'canvas> ShiftedView<'canvas> {
fn new(base: &'canvas mut dyn AsciiView, row: usize, column: usize) -> Self {
let upper_left = Point { row, column };
ShiftedView {
base,
upper_left,
lower_right: upper_left,
}
}
pub fn close(self) -> (usize, usize) {
(self.lower_right.row, self.lower_right.column)
}
fn track_max(&mut self, row: usize, column: usize) {
self.lower_right.row = cmp::max(self.lower_right.row, row);
self.lower_right.column = cmp::max(self.lower_right.column, column);
}
}
impl<'canvas> AsciiView for ShiftedView<'canvas> {
fn columns(&self) -> usize {
self.base.columns() - self.upper_left.column
}
fn read_char(&mut self, row: usize, column: usize) -> char {
let row = self.upper_left.row + row;
let column = self.upper_left.column + column;
self.base.read_char(row, column)
}
fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
let row = self.upper_left.row + row;
let column = self.upper_left.column + column;
self.track_max(row, column);
self.base.write_char(row, column, ch, style)
}
}
pub struct StyleView<'canvas> {
base: &'canvas mut dyn AsciiView,
style: Style,
}
impl<'canvas> StyleView<'canvas> {
fn new(base: &'canvas mut dyn AsciiView, style: Style) -> Self {
StyleView { base, style }
}
}
impl<'canvas> AsciiView for StyleView<'canvas> {
fn columns(&self) -> usize {
self.base.columns()
}
fn read_char(&mut self, row: usize, column: usize) -> char {
self.base.read_char(row, column)
}
fn write_char(&mut self, row: usize, column: usize, ch: char, style: Style) {
self.base
.write_char(row, column, ch, style.with(self.style))
}
}
const UP: u8 = 0b0001;
const DOWN: u8 = 0b0010;
const LEFT: u8 = 0b0100;
const RIGHT: u8 = 0b1000;
const BOX_CHARS: &[(char, u8)] = &[
('╵', UP),
('│', UP | DOWN),
('┤', UP | DOWN | LEFT),
('├', UP | DOWN | RIGHT),
('┼', UP | DOWN | LEFT | RIGHT),
('┘', UP | LEFT),
('└', UP | RIGHT),
('┴', UP | LEFT | RIGHT),
('╷', DOWN),
('┐', DOWN | LEFT),
('┌', DOWN | RIGHT),
('┬', DOWN | LEFT | RIGHT),
('╶', LEFT),
('─', LEFT | RIGHT),
('╴', RIGHT),
(' ', 0),
];
fn box_char_for_dirs(dirs: u8) -> char {
for &(c, d) in BOX_CHARS {
if dirs == d {
return c;
}
}
panic!("no box character for dirs: {:b}", dirs);
}
fn dirs_for_box_char(ch: char) -> Option<u8> {
for &(c, d) in BOX_CHARS {
if c == ch {
return Some(d);
}
}
None
}
fn add_dirs(old_ch: char, new_dirs: u8) -> char {
let old_dirs = dirs_for_box_char(old_ch).unwrap_or(0);
box_char_for_dirs(old_dirs | new_dirs)
}