extern crate libc;
use crate::math::Vec2;
use crate::img::{BlendMode, Color, Image};
use crate::input::Input;
use crate::screen_buffer::*;
use termios::*;
use std::mem;
use std::io::{stdout, Write};
use std::panic;
use std::backtrace::Backtrace;
use std::thread;
use std::sync::{mpsc, Barrier, Arc, Mutex};
use std::io::stdin;
use std::os::unix::io::AsRawFd;
const NCCS: usize = 32;
macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1b[", $( $l ),*) };
}
enum RenderingDirective {
DrawLine(Vec2, Vec2, Color),
DrawRect(Vec2, Vec2, Color),
DrawRectBoudary(Vec2, Vec2, Color),
DrawEllipseBoudary(Vec2, Vec2, Color),
DrawPoint(Vec2, Color),
DrawImage(Arc<Mutex<Image>>, Vec2, Vec2, Vec2, Option<Color>),
DrawWholeImageAlpha(Arc<Mutex<Image>>, Vec2, Color),
DrawWholeImage(Arc<Mutex<Image>>, Vec2),
PrintTextRaw(String, Vec2, CharBackgroundMode, CharForegroundMode),
ClearColor(Color),
ClearText,
UpdateScreenSize(Vec2),
BeginFrame,
PushFrame
}
pub struct Renderer {
termios: Termios,
default_c_lflags: u32,
default_c_cc: [u8; NCCS],
building_frame: bool,
prev_screen_size: Vec2,
_server_handle: Option<thread::JoinHandle<()>>,
sender: mpsc::Sender<RenderingDirective>,
frame_barrier: Arc<Barrier>
}
static mut RENDERER: Option<Renderer> = None;
impl Renderer {
fn init() -> Renderer {
let stdinfd = stdin().as_raw_fd();
let mut termios = match Termios::from_fd(stdinfd) {
Ok(t) => t,
Err(_) => panic!("Could not read stdin fd")
};
let default_c_lflags = termios.c_lflag;
let default_c_cc = termios.c_cc;
termios.c_lflag &= !(ECHO | ICANON | ISIG);
termios.c_cc[VMIN] = 1;
termios.c_cc[VTIME] = 0;
tcsetattr(stdinfd, TCSANOW, &mut termios).expect("could not set stdin attributes");
print!("{}{}",
csi!("?25l"), csi!("?1049h") );
stdout().flush().expect("Could not write to stdout");
panic::set_hook(Box::new(|panic_info| {
let backtrace = Backtrace::capture();
eprintln!("{}", backtrace);
eprintln!("{}", panic_info);
Renderer::exit();
}));
let (rx, tx) = mpsc::channel();
let barrier = Arc::new(Barrier::new(2));
let frame_barrier = Arc::clone(&barrier);
let handle = thread::spawn(move || {
let mut screen_size = Renderer::get_size();
let mut screen : ScreeBuffer = ScreeBuffer::new((0, 0));
let mut prev_screen: ScreeBuffer = ScreeBuffer::new((0, 0));
let mut back: Color = Color::BLACK;
let mut fore: Color = Color::BLACK;
print!("{:-}{:+}", back, fore);
loop {
match tx.recv().expect("RenderingServer channel was destroyed") {
RenderingDirective::DrawLine(p1, p2, c) => screen.line(p1, p2, c),
RenderingDirective::DrawRect(p, s, c) => screen.rect(p, s, c),
RenderingDirective::DrawRectBoudary(p, s, c) => screen.rect_boudary(p, s, c),
RenderingDirective::DrawEllipseBoudary(center, s, c) => screen.ellipse_boundary(center, s, c),
RenderingDirective::DrawPoint(p, c) => screen.point(p, c),
RenderingDirective::DrawImage(img, pos, size, off, alpha) => screen.image(&(*img.lock().unwrap()), pos, size, off, alpha),
RenderingDirective::DrawWholeImageAlpha(img, pos, alpha) => screen.whole_image_alpha(&(*img.lock().unwrap()), pos, alpha),
RenderingDirective::DrawWholeImage(img, pos) => screen.whole_image(&(*img.lock().unwrap()), pos),
RenderingDirective::PrintTextRaw(text, pos, back_mode, fore_mode) => screen.print_text_raw(text, pos, back_mode, fore_mode),
RenderingDirective::ClearColor(c) => screen.clear_color(c),
RenderingDirective::ClearText => screen.clear_text(),
RenderingDirective::UpdateScreenSize(size) => {
screen_size = size;
screen.raw_resize(size); }
RenderingDirective::BeginFrame => {frame_barrier.wait(); ()},
RenderingDirective::PushFrame => {
print!("\x1b[H");
let mut skiped = false;
for j in (0..screen_size.y).step_by(2) {
for i in 0..screen_size.x {
let pos1 = vec2!(i, j);
let pos2 = vec2!(i, j + 1);
let color1 = screen.get_color(pos1);
let color2 = screen.get_color(pos2);
let char_data = screen.get_char_data(pos1);
let prev_color1 = prev_screen.get_color(pos1);
let prev_color2 = prev_screen.get_color(pos2);
let prev_char_data = prev_screen.get_char_data(pos1);
if screen.size() == prev_screen.size()
&& char_data == prev_char_data
&& color1 == prev_color1
&& color2 == prev_color2 {
skiped = true;
continue;
}
if skiped {
print!("\x1b[{};{}H", j/2 + 1, i + 1);
skiped = false;
}
match char_data.c {
Some(c) => {
let background_color = match char_data.bg_mode {
CharBackgroundMode::Blend => Color::blend(color1, color2, BlendMode::Add),
CharBackgroundMode::Colored(c) => c
};
let foreground_color = match char_data.fg_mode {
CharForegroundMode::Opposite => {
let (_, _, l) = background_color.get_okhsl();
if l > 0.5 {
Color::BLACK
} else {
Color::WHITE
}
},
CharForegroundMode::Colored(c) => c
};
if back != background_color {
back = background_color;
print!("{:-}", back);
}
if fore != foreground_color {
fore = foreground_color;
print!("{:+}", fore);
}
print!("{}", c);
}
None => {
if color1 != back && color1 != fore && color2 == back {
fore = color1;
print!("{:+}", fore);
} else if color1 != back && color1 != fore && color2 == fore {
back = color1;
print!("{:-}", back);
} else if color2 != back && color2 != fore && color1 == back {
fore = color2;
print!("{:+}", fore);
} else if color2 != back && color2 != fore && color1 == fore {
back = color2;
print!("{:-}", back);
} else if color1 != back && color1 != fore && color2 != back && color2 != fore {
fore = color1;
back = color2;
print!("{:+}", fore);
print!("{:-}", back);
}
if color1 == back && color2 == back {
print!(" ");
} else if color1 == back && color2 == fore {
print!("â–„");
} else if color1 == fore && color2 == back {
print!("â–€");
} else if color1 == fore && color2 == fore {
print!("â–ˆ");
}
}
}
}
}
stdout().flush().expect("Could not write to stdout");
prev_screen = screen.clone();
}
}
}
});
Renderer {
termios: termios,
default_c_lflags: default_c_lflags,
default_c_cc: default_c_cc,
building_frame: false,
prev_screen_size: Vec2::ZERO,
_server_handle: Some(handle),
sender: rx,
frame_barrier: barrier
}
}
pub fn exit() {
unsafe {
RENDERER = None;
}
}
pub fn get() -> &'static mut Renderer {
unsafe {
match &mut RENDERER {
None => { RENDERER = Some(Renderer::init());
Renderer::get()
}
Some(r) => r
}
}
}
pub fn get_size() -> Vec2 {
unsafe {
let mut size: TermSize = mem::zeroed();
libc::ioctl(libc::STDOUT_FILENO, libc::TIOCGWINSZ, &mut size as *mut _);
vec2!(size.col as i32, 2 * size.row as i32)
}
}
fn can_draw(&self) {
if !self.building_frame { panic!("drawing outside of a frame build (call begin_draw)"); }
}
pub fn begin_draw(&mut self) {
if self.building_frame {
panic!("begin_draw called when already building a frame");
}
self.building_frame = true;
let new_size = Renderer::get_size();
if self.prev_screen_size != new_size {
self.sender.send(RenderingDirective::UpdateScreenSize(new_size)).expect("Rendering thread stopped");
self.prev_screen_size = new_size;
}
self.sender.send(RenderingDirective::BeginFrame).expect("Rendering thread stopped");
self.frame_barrier.wait();
}
pub fn end_draw(&mut self) {
if !self.building_frame {
panic!("end_draw called when already building a frame");
}
self.building_frame = false;
self.sender.send(RenderingDirective::PushFrame).expect("Rendering thread stopped");
}
pub fn clear(&mut self, c: Color) {
self.can_draw();
self.sender.send(RenderingDirective::ClearColor(c)).expect("Rendering thread stopped");
self.sender.send(RenderingDirective::ClearText).expect("Rendering thread stopped");
}
pub fn clear_color(&mut self, c: Color) {
self.can_draw();
self.sender.send(RenderingDirective::ClearColor(c)).expect("Rendering thread stopped");
}
pub fn clear_text(&mut self) {
self.can_draw();
self.sender.send(RenderingDirective::ClearText).expect("Rendering thread stopped");
}
pub fn draw_line<A, B>(&mut self, p1: A, p2: B, c: Color)
where A: AsRef<Vec2>, B: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawLine(*p1.as_ref(), *p2.as_ref(), c))
.expect("Rendering thread stopped");
}
pub fn draw_rect<A, B>(&mut self, p: A, s: B, c: Color)
where A: AsRef<Vec2>, B: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawRect(*p.as_ref(), *s.as_ref(), c))
.expect("Rendering thread stopped");
}
pub fn draw_rect_boundary<A, B>(&mut self, p: A, s: B, c: Color)
where A: AsRef<Vec2>, B: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawRectBoudary(*p.as_ref(), *s.as_ref(), c))
.expect("Rendering thread stopped");
}
pub fn draw_ellipse_boundary<A, B>(&mut self, c: A, s: B, col: Color)
where A: AsRef<Vec2>, B: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawEllipseBoudary(*c.as_ref(), *s.as_ref(), col))
.expect("Rendering thread stopped");
}
pub fn draw_point<A>(&mut self, p: A, c: Color)
where A: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawPoint(*p.as_ref(), c)).expect("Rendering thread stopped");
}
pub fn draw_image<A, B, C>(&mut self,
img: Arc<Mutex<Image>>, pos: A, size: B, offset: C, alpha: Option<Color>)
where A: AsRef<Vec2>, B: AsRef<Vec2>, C: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawImage(img, *pos.as_ref(), *size.as_ref(), *offset.as_ref(), alpha))
.expect("Rendering thread stopped");
}
pub fn draw_whole_image_alpha<A>(&mut self, img: Arc<Mutex<Image>>, pos: A, alpha: Color)
where A: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawWholeImageAlpha(img, *pos.as_ref(), alpha))
.expect("Rendering thread stopped");
}
pub fn draw_whole_image<A>(&mut self, img: Arc<Mutex<Image>>, pos: A)
where A: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::DrawWholeImage(img, *pos.as_ref())).expect("Rendering thread stopped");
}
pub fn print_text_raw<A>(&mut self, text: &String, pos: A, background_mode: CharBackgroundMode, foreground_mode: CharForegroundMode)
where A: AsRef<Vec2>
{
self.can_draw();
self.sender.send(RenderingDirective::PrintTextRaw(text.clone(), *pos.as_ref(), background_mode, foreground_mode))
.expect("Rendering thread stopped");
}
pub fn print_blended_text_raw<A>(&mut self, text: &String, pos: A)
where A: AsRef<Vec2>
{
self.print_text_raw(
text,
pos,
CharBackgroundMode::Blend,
CharForegroundMode::Opposite
);
}
pub fn print_colored_text_raw<A>(&mut self, text: &String, pos: A, background_color: Color, foreground_color: Color)
where A: AsRef<Vec2>
{
self.print_text_raw(
text,
pos,
CharBackgroundMode::Colored(background_color),
CharForegroundMode::Colored(foreground_color)
)
}
pub fn ring_bell(&self) {
self.can_draw();
print!("\x07");
}
}
impl Drop for Renderer {
fn drop(&mut self) {
self.termios.c_cc = self.default_c_cc;
self.termios.c_lflag = self.default_c_lflags;
let stdinfd = stdin().as_raw_fd();
tcsetattr(stdinfd, TCSANOW, &mut self.termios).expect("could not set stdin attributes");
print!("{}{}",
csi!("?25h"), csi!("?1049l") );
stdout().flush().expect("Could not write to stdout");
Input::disable_mouse();
std::process::exit(0);
}
}
struct TermSize {
row: libc::c_ushort,
col: libc::c_ushort,
_x : libc::c_ushort,
_y : libc::c_ushort
}