mirror of
https://github.com/ricoriedel/wipe.git
synced 2024-11-26 09:46:37 +00:00
Add render.rs
This commit is contained in:
parent
3c5b0c00d9
commit
d600c133bc
@ -7,6 +7,7 @@ description = "Wipe your terminal with a random animation."
|
|||||||
repository = "https://github.com/ricoriedel/wipe"
|
repository = "https://github.com/ricoriedel/wipe"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
clap = { version = "3.1", features = ["derive"]}
|
clap = { version = "3.1", features = ["derive"]}
|
||||||
crossterm = "0.23"
|
crossterm = "0.23"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
@ -7,6 +7,7 @@ mod char;
|
|||||||
mod fill;
|
mod fill;
|
||||||
mod vec;
|
mod vec;
|
||||||
mod array;
|
mod array;
|
||||||
|
mod render;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(author = "Rico Riedel", version = "0.1.0", about = "Wipe your terminal with a random animation.")]
|
#[clap(author = "Rico Riedel", version = "0.1.0", about = "Wipe your terminal with a random animation.")]
|
||||||
|
208
src/render.rs
Normal file
208
src/render.rs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
use anyhow::Error;
|
||||||
|
use crossterm::cursor::MoveTo;
|
||||||
|
use crossterm::{ExecutableCommand, QueueableCommand};
|
||||||
|
use crossterm::style::{Color, Print, SetForegroundColor};
|
||||||
|
use crossterm::terminal::{Clear, ClearType};
|
||||||
|
use std::io::Write;
|
||||||
|
use crate::array::Array2D;
|
||||||
|
|
||||||
|
pub trait Renderer {
|
||||||
|
fn width(&self) -> usize;
|
||||||
|
fn height(&self) -> usize;
|
||||||
|
fn draw(&mut self, x: usize, y: usize, char: char, color: Color);
|
||||||
|
fn clear(&mut self, x: usize, y: usize);
|
||||||
|
fn render(&mut self) -> Result<(), Error>;
|
||||||
|
fn purge(&mut self) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WriteRenderer {
|
||||||
|
out: Box<dyn Write>,
|
||||||
|
array: Array2D<Cell>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum Cell {
|
||||||
|
Keep,
|
||||||
|
Draw { char: char, color: Color },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cell {
|
||||||
|
fn default() -> Self { Cell::Keep }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteRenderer {
|
||||||
|
pub fn new(out: Box<dyn Write>, width: usize, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
out,
|
||||||
|
array: Array2D::new(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Renderer for WriteRenderer {
|
||||||
|
fn width(&self) -> usize {
|
||||||
|
self.array.width()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.array.height()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(&mut self, x: usize, y: usize, char: char, color: Color) {
|
||||||
|
self.array[(x, y)] = Cell::Draw { char, color };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, x: usize, y: usize) {
|
||||||
|
self.array[(x, y)] = Cell::Draw { char: ' ', color: Color::Reset };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self) -> Result<(), Error> {
|
||||||
|
let mut needs_move;
|
||||||
|
let mut last_color = None;
|
||||||
|
|
||||||
|
for y in 0..self.array.height() {
|
||||||
|
needs_move = true;
|
||||||
|
|
||||||
|
for x in 0..self.array.width() {
|
||||||
|
match self.array[(x, y)] {
|
||||||
|
Cell::Keep => {
|
||||||
|
needs_move = true;
|
||||||
|
}
|
||||||
|
Cell::Draw { char, color } => {
|
||||||
|
if needs_move {
|
||||||
|
needs_move = false;
|
||||||
|
self.out.queue(MoveTo(x as u16, y as u16))?;
|
||||||
|
}
|
||||||
|
if last_color.is_none() || last_color.unwrap() != color {
|
||||||
|
last_color = Some(color);
|
||||||
|
self.out.queue(SetForegroundColor(color))?;
|
||||||
|
}
|
||||||
|
self.out.queue(Print(char))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.out.queue(MoveTo(0, 0))?;
|
||||||
|
self.out.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn purge(&mut self) -> Result<(), Error> {
|
||||||
|
self.out.execute(Clear(ClearType::Purge))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
struct Data {
|
||||||
|
flushed: Vec<Vec<u8>>,
|
||||||
|
buffer: Vec<u8>
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MockWrite {
|
||||||
|
data: Rc<RefCell<Data>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
fn new() -> Rc<RefCell<Self>> {
|
||||||
|
Rc::new(RefCell::new(Self {
|
||||||
|
flushed: Vec::new(),
|
||||||
|
buffer: Vec::new()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockWrite {
|
||||||
|
fn new(data: Rc<RefCell<Data>>) -> Box<dyn Write> {
|
||||||
|
Box::new(Self { data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for MockWrite {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
self.data.borrow_mut().buffer.extend_from_slice(buf);
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
let data = self.data.borrow_mut().buffer.drain(..).collect();
|
||||||
|
|
||||||
|
self.data.borrow_mut().flushed.push(data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn width() {
|
||||||
|
let data = Data::new();
|
||||||
|
let mock = MockWrite::new(data);
|
||||||
|
let renderer = WriteRenderer::new(mock, 10, 2);
|
||||||
|
|
||||||
|
assert_eq!(10, renderer.width());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn height() {
|
||||||
|
let data = Data::new();
|
||||||
|
let mock = MockWrite::new(data);
|
||||||
|
let renderer = WriteRenderer::new(mock, 5, 8);
|
||||||
|
|
||||||
|
assert_eq!(8, renderer.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn purge() {
|
||||||
|
// Execute
|
||||||
|
let data = Data::new();
|
||||||
|
let mock = MockWrite::new(data.clone());
|
||||||
|
let mut renderer = WriteRenderer::new(mock, 4, 4);
|
||||||
|
|
||||||
|
renderer.purge().unwrap();
|
||||||
|
|
||||||
|
// Recreate expectation
|
||||||
|
let expected = Data::new();
|
||||||
|
let mut stream = MockWrite::new(expected.clone());
|
||||||
|
|
||||||
|
stream.execute(Clear(ClearType::Purge)).unwrap();
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
assert_eq!(expected, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render() {
|
||||||
|
// Execute
|
||||||
|
let data = Data::new();
|
||||||
|
let mock = MockWrite::new(data.clone());
|
||||||
|
let mut renderer = WriteRenderer::new(mock, 3, 2);
|
||||||
|
|
||||||
|
renderer.draw(0, 0, 'A', Color::Green);
|
||||||
|
renderer.draw(1, 0, 'x', Color::Green);
|
||||||
|
renderer.clear(1, 1);
|
||||||
|
renderer.render().unwrap();
|
||||||
|
|
||||||
|
// Recreate expectation
|
||||||
|
let expected = Data::new();
|
||||||
|
let mut stream = MockWrite::new(expected.clone());
|
||||||
|
|
||||||
|
stream.queue(MoveTo(0, 0)).unwrap();
|
||||||
|
stream.queue(SetForegroundColor(Color::Green)).unwrap();
|
||||||
|
stream.queue(Print('A')).unwrap();
|
||||||
|
stream.queue(Print('x')).unwrap();
|
||||||
|
stream.queue(MoveTo(1, 1)).unwrap();
|
||||||
|
stream.queue(SetForegroundColor(Color::Reset)).unwrap();
|
||||||
|
stream.queue(Print(' ')).unwrap();
|
||||||
|
stream.queue(MoveTo(0, 0)).unwrap();
|
||||||
|
stream.flush().unwrap();
|
||||||
|
|
||||||
|
// Compare
|
||||||
|
assert_eq!(expected, data);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user