use std::io::{self, Stdout, Write};
use crossterm::cursor::{self, MoveTo};
use crossterm::style::{SetBackgroundColor, SetForegroundColor};
#[cfg(target_os = "windows")]
use crossterm::event::EnableMouseCapture;
#[cfg(not(target_os = "windows"))]
use crossterm::event::DisableMouseCapture;
use crossterm::style::Print;
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen,
};
use crossterm::QueueableCommand;
use crossterm::{execute, ExecutableCommand, Result};
use crate::{Color, Pixel, Viewport};
fn setup_terminal_for_stdout_target() -> Result<Stdout> {
enable_raw_mode()?;
let mut stdout = io::stdout();
stdout.execute(EnterAlternateScreen)?;
#[cfg(target_os = "windows")]
execute!(stdout, EnableMouseCapture,)?;
#[cfg(not(target_os = "windows"))]
execute!(stdout, DisableMouseCapture,)?;
stdout.execute(cursor::Hide)?;
stdout.execute(Clear(ClearType::All))?;
Ok(stdout)
}
fn reset_terminal_from_stdout_target(stdout: &mut Stdout) -> Result<()> {
if !std::thread::panicking() {
stdout.execute(cursor::Show)?;
stdout.execute(LeaveAlternateScreen)?;
disable_raw_mode()?;
}
Ok(())
}
pub struct Renderer<T: RenderTarget> {
pub(crate) target: T,
}
impl<T: RenderTarget> Renderer<T> {
pub fn new(target: T) -> Self {
Self { target }
}
pub fn render(&mut self, viewport: &mut Viewport) {
self.target.render(viewport.pixels());
}
pub fn clear(&mut self) {
self.target.clear();
}
}
pub trait RenderTarget {
fn render<'a, P: Iterator<Item=&'a Pixel>>(&'a mut self, pixels: P);
fn clear(&mut self);
}
pub struct StdoutTarget {
stdout: Stdout,
last_color_fg: Option<Color>,
last_color_bg: Option<Color>,
}
impl StdoutTarget {
pub fn new() -> Result<Self> {
let stdout = setup_terminal_for_stdout_target()?;
Ok(Self {
stdout,
last_color_fg: None,
last_color_bg: None,
})
}
}
impl RenderTarget for StdoutTarget {
fn render<'a, P: Iterator<Item=&'a Pixel>>(&'a mut self, pixels: P) {
for pixel in pixels {
self.stdout
.queue(MoveTo(pixel.pos.x, pixel.pos.y))
.expect("failed to move cursor");
if self.last_color_fg != pixel.fg_color {
self.last_color_fg = pixel.fg_color;
let _ = match self.last_color_fg {
Some(color) => self.stdout.queue(SetForegroundColor(color)),
None => self.stdout.queue(SetForegroundColor(Color::Reset)),
};
}
if self.last_color_bg != pixel.bg_color {
self.last_color_bg = pixel.bg_color;
let _ = match self.last_color_bg {
Some(color) => self.stdout.queue(SetBackgroundColor(color)),
None => self.stdout.queue(SetBackgroundColor(Color::Reset)),
};
}
self.stdout
.queue(Print(pixel.glyph.to_string()))
.expect("failed to print");
}
let _ = self.stdout.flush();
}
fn clear(&mut self) {
let _ = self.stdout.execute(SetBackgroundColor(Color::Reset));
let _ = self.stdout.execute(Clear(ClearType::All));
}
}
impl Drop for StdoutTarget {
fn drop(&mut self) {
let _ = reset_terminal_from_stdout_target(&mut self.stdout);
}
}
pub struct DummyTarget;
impl RenderTarget for DummyTarget {
fn render<'a, P: Iterator<Item=&'a Pixel>>(&'a mut self, _: P) {}
fn clear(&mut self) {}
}
#[cfg(test)]
mod test {
use super::*;
use crate::*;
fn camera() -> Camera<camera::NoLimit> {
let pos = WorldPos::new(30, 30);
let size = WorldSize::new(6, 6);
Camera::new(pos, size)
}
fn viewport() -> Viewport {
let pos = ScreenPos::new(2, 2);
let size = ScreenSize::new(6, 6);
Viewport::new(pos, size)
}
struct DummyTarget {
pixels: Vec<Pixel>,
}
impl RenderTarget for DummyTarget {
fn render<'a, P: Iterator<Item=&'a Pixel>>(&'a mut self, pixels: P) {
self.pixels = pixels.cloned().collect();
}
fn clear(&mut self) {}
}
#[test]
fn render_pixels() {
let cam = camera();
let mut view = viewport();
let min_x = cam.bounding_box.min_x();
let min_y = cam.bounding_box.min_y();
let a = ('A', WorldPos::new(min_x, min_y));
let a = Pixel::new(a.0, cam.to_screen(a.1), None, None);
view.draw_pixel(a);
let mut renderer = Renderer::new(DummyTarget { pixels: Vec::new() });
renderer.render(&mut view);
let a = Pixel::new('A', ScreenPos::new(2, 2), None, None); let pixels = vec![a];
assert_eq!(pixels, renderer.target.pixels);
}
}