pub extern crate termion;
mod utils;
pub mod pixel;
use pixel::Pixel;
use termion::color;
use termion::event::{Event, Key};
use std::io::{stdout, Stdout};
use termion::raw::IntoRawMode;
use std::io::Write;
use termion::input::{TermRead, MouseTerminal};
pub struct ConsoleEngine {
input: termion::input::Events<termion::AsyncReader>,
output: MouseTerminal<termion::raw::RawTerminal<Stdout>>,
time_limit: u128,
pub frame_count: usize,
width: u32,
height: u32,
screen: Vec<Pixel>,
screen_last_frame: Vec<Pixel>,
instant: std::time::Instant,
keys_pressed: Vec<Event>,
keys_held: Vec<Event>,
keys_released: Vec<Event>,
}
impl ConsoleEngine {
pub fn init(width: u32, height: u32, target_fps: u32) -> ConsoleEngine {
assert!(target_fps > 0, "Target FPS needs to be greater than zero.");
let size = termion::terminal_size().unwrap();
assert!(size.0 as u32 >= width && size.1 as u32 >= height, "Your terminal must have at least a width and height of {}x{} characters. Currently has {}x{}", width, height, size.0, size.1);
let mut my = ConsoleEngine {
output: MouseTerminal::from(stdout().into_raw_mode().unwrap()),
input: termion::async_stdin().events(),
time_limit: (1000/target_fps) as u128,
frame_count: 0,
width: width,
height: height,
screen: vec![pixel::pxl(' '); (width*height) as usize],
screen_last_frame: vec![],
instant: std::time::Instant::now(),
keys_pressed: vec!(),
keys_held: vec!(),
keys_released: vec!(),
};
my.begin();
return my;
}
pub fn init_fill(target_fps: u32) -> ConsoleEngine {
let size = termion::terminal_size().unwrap();
return ConsoleEngine::init(size.0 as u32, size.1 as u32, target_fps);
}
pub fn init_fill_require(width: u32, height: u32, target_fps: u32) -> ConsoleEngine {
let size = termion::terminal_size().unwrap();
assert!(size.0 as u32 >= width && size.1 as u32 >= height, "Your terminal must have at least a width and height of {}x{} characters. Currently has {}x{}", width, height, size.0, size.1);
return ConsoleEngine::init_fill(target_fps);
}
#[cfg(windows)]
fn begin(&mut self) {
println!("Please Press Enter to initialize inputs");
while !self.input.next().is_some() {}
println!("{}{}{}", termion::cursor::Hide, termion::clear::All, termion::cursor::Goto(1,1));
}
#[cfg(not(windows))]
fn begin(&mut self) {
println!("{}{}{}", termion::cursor::Hide, termion::clear::All, termion::cursor::Goto(1,1));
}
fn end(&mut self){
println!("{}{}{}\r\n", termion::cursor::Show, color::Fg(color::Reset), color::Bg(color::Reset));
}
pub fn scr_w(&self) -> u32
{
self.width
}
pub fn scr_h(&self) -> u32
{
self.height
}
pub fn clear_screen(&mut self)
{
self.screen = vec![pixel::pxl(' '); (self.width*self.height) as usize];
}
pub fn print(&mut self, x: i32, y: i32, string: String)
{
if y >= 0 && x < self.width as i32 && y < self.height as i32 {
let pos = self.coord_to_index(std::cmp::max(0,x), y);
let mut delta_x = 0usize;
if x < 0 {
delta_x = x.abs() as usize;
}
let mut count = delta_x;
let char_vec: Vec<char> = string.chars().collect();
let origin_row = pos/self.scr_w() as usize;
for i in pos..std::cmp::min(pos+char_vec.len()-delta_x, self.screen.capacity()) {
if origin_row != i/self.scr_w() as usize {
break;
}
self.screen[i] = pixel::pxl(char_vec[count]);
count += 1;
}
}
}
pub fn print_fbg<C1: color::Color + Clone, C2: color::Color + Clone>(&mut self, x: i32, y: i32, string: String, fg: C1, bg: C2)
{
if y >= 0 && x < self.width as i32 && y < self.height as i32 {
let pos = self.coord_to_index(std::cmp::max(0,x), y);
let mut delta_x = 0usize;
if x < 0 {
delta_x = x.abs() as usize;
}
let mut count = delta_x;
let char_vec: Vec<char> = string.chars().collect();
let origin_row = pos/self.scr_w() as usize;
for i in pos..std::cmp::min(pos+char_vec.len()-delta_x, self.screen.capacity()) {
if origin_row != i/self.scr_w() as usize {
break;
}
self.screen[i] = pixel::pxl_fbg(char_vec[count], fg.clone(), bg.clone());
count += 1;
}
}
}
pub fn line(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel)
{
let delta_x = end_x - start_x;
let delta_y = end_y - start_y;
if delta_y == 0 {
let mut start = start_x;
let mut end = end_x+1;
if end_x < start_x {
end = start_x+1;
start = end_x;
};
for i in start..end {
self.set_pxl_ref(i, start_y, &character);
}
return;
}
if delta_x == 0 {
let mut start = start_y;
let mut end = end_y+1;
if end_y < start_y {
end = start_y+1;
start = end_y;
};
for j in start..end {
self.set_pxl_ref(start_x, j, &character);
}
return;
}
let line_low = |engine: &mut ConsoleEngine, x0: i32,y0: i32, x1: i32,y1: i32| {
let dx: i32 = x1 - x0;
let mut dy: i32 = y1 - y0;
let mut yi = 1;
if dy < 0 {
yi = -1;
dy = -dy;
}
let mut d = 2*dy - dx;
let mut y = y0;
for x in x0..x1+1 {
engine.set_pxl_ref(x, y, &character);
if d > 0 {
y = y + yi;
d = d - 2*dx;
}
d = d + 2*dy;
}
};
let line_high = |engine: &mut ConsoleEngine, x0: i32,y0: i32, x1: i32,y1: i32| {
let mut dx = x1 - x0;
let dy = y1 - y0;
let mut xi = 1;
if dx < 0 {
xi = -1;
dx = -dx;
}
let mut d = 2*dx - dy;
let mut x = x0;
for y in y0..y1+1 {
engine.set_pxl_ref(x, y, &character);
if d > 0 {
x = x + xi;
d = d - 2*dy;
}
d = d + 2*dx;
}
};
if (end_y - start_y).abs() < (end_x - start_x).abs() {
if start_x > end_x {
line_low(self, end_x, end_y, start_x, start_y);
} else {
line_low(self, start_x, start_y, end_x, end_y);
}
} else {
if start_y > end_y {
line_high(self, end_x, end_y, start_x, start_y);
} else {
line_high(self, start_x, start_y, end_x, end_y);
}
}
}
pub fn rect(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel)
{
self.line(start_x, start_y, end_x, start_y, character.clone());
self.line(end_x, start_y, end_x, end_y, character.clone());
self.line(end_x, end_y, start_x, end_y, character.clone());
self.line(start_x, end_y, start_x, start_y, character.clone());
}
pub fn fill_rect(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel)
{
let y0 = if start_y < end_y { start_y } else { end_y };
let y1 = if start_y < end_y { end_y+1 } else { start_y+1 };
for y in y0..y1 {
self.line(start_x, y, end_x, y, character.clone());
}
}
pub fn circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel)
{
let mut relative_pos_x = 0 as i32;
let mut relative_pos_y = radius as i32;
let mut distance: i32 = 3 - 2 * radius as i32;
if radius == 0 {
return;
}
while relative_pos_y >= relative_pos_x
{
self.set_pxl_ref(x + relative_pos_x, y - relative_pos_y, &character);
self.set_pxl_ref(x + relative_pos_y, y - relative_pos_x, &character);
self.set_pxl_ref(x + relative_pos_y, y + relative_pos_x, &character);
self.set_pxl_ref(x + relative_pos_x, y + relative_pos_y, &character);
self.set_pxl_ref(x - relative_pos_x, y + relative_pos_y, &character);
self.set_pxl_ref(x - relative_pos_y, y + relative_pos_x, &character);
self.set_pxl_ref(x - relative_pos_y, y - relative_pos_x, &character);
self.set_pxl_ref(x - relative_pos_x, y - relative_pos_y, &character);
if distance < 0 {
distance += 4 * relative_pos_x as i32 + 6;
relative_pos_x += 1;
} else {
distance += 4 * (relative_pos_x as i32 - relative_pos_y as i32) + 10;
relative_pos_x += 1;
relative_pos_y -= 1;
}
}
}
pub fn fill_circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel)
{
let mut relative_pos_x = 0 as i32;
let mut relative_pos_y = radius as i32;
let mut distance: i32 = 3 - 2 * radius as i32;
if radius == 0 {
return;
}
let mut drawline = |start_x: i32, end_x: i32, y: i32|
{
for i in start_x..end_x+1 {
self.set_pxl_ref(i, y, &character);
}
};
while relative_pos_y >= relative_pos_x
{
drawline(x - relative_pos_x, x + relative_pos_x, y - relative_pos_y);
drawline(x - relative_pos_y, x + relative_pos_y, y - relative_pos_x);
drawline(x - relative_pos_x, x + relative_pos_x, y + relative_pos_y);
drawline(x - relative_pos_y, x + relative_pos_y, y + relative_pos_x);
if distance < 0 {
distance += 4 * relative_pos_x + 6;
relative_pos_x += 1;
} else {
distance += 4 * (relative_pos_x - relative_pos_y) + 10;
relative_pos_x += 1;
relative_pos_y -= 1;
}
}
}
pub fn triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, character: Pixel)
{
self.line(x1, y1, x2, y2, character.clone());
self.line(x2, y2, x3, y3, character.clone());
self.line(x3, y3, x1, y1, character.clone());
}
pub fn fill_triangle(&mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, character: Pixel)
{
self.triangle(x1 as i32, y1 as i32, x2 as i32, y2 as i32, x3 as i32, y3 as i32, character.clone());
let v0 = (x1 as i32, y1 as i32);
let mut v1 = (x2 as i32, y2 as i32);
let mut v2 = (x3 as i32, y3 as i32);
let cross = (v1.1 - v0.1) * (v2.0 - v1.0) - (v1.0 - v0.0) * (v2.1 - v1.1);
if cross > 0 { std::mem::swap(&mut v1, &mut v2) }
let min_x = std::cmp::max(std::cmp::min(std::cmp::min(v0.0, v1.0), v2.0), 0);
let max_x = std::cmp::min(std::cmp::max(std::cmp::max(v0.0, v1.0), v2.0), self.scr_w() as i32 - 1);
let min_y = std::cmp::max(std::cmp::min(std::cmp::min(v0.1, v1.1), v2.1), 0);
let max_y = std::cmp::min(std::cmp::max(std::cmp::max(v0.1, v1.1), v2.1), self.scr_h() as i32 - 1);
let a01 = v0.1 - v1.1;
let b01 = v1.0 - v0.0;
let a12 = v1.1 - v2.1;
let b12 = v2.0 - v1.0;
let a20 = v2.1 - v0.1;
let b20 = v0.0 - v2.0;
let is_top_left = |v0: (i32, i32), v1: (i32, i32)| -> bool {
v0.1 > v1.1
};
let bias0 = if is_top_left(v1, v2) { 0 } else { -1 };
let bias1 = if is_top_left(v2, v0) { 0 } else { -1 };
let bias2 = if is_top_left(v0, v1) { 0 } else { -1 };
let orient2d = |a: (i32,i32), b: (i32,i32), c: (i32,i32)| -> i32 {
(b.0-a.0)*(c.1-a.1) - (b.1-a.1)*(c.0-a.0)
};
let mut p = (min_x, min_y);
let mut w0_row = orient2d(v1, v2, p) + bias0;
let mut w1_row = orient2d(v2, v0, p) + bias1;
let mut w2_row = orient2d(v0, v1, p) + bias2;
for y in min_y..max_y {
p.1 = y;
let mut w0 = w0_row;
let mut w1 = w1_row;
let mut w2 = w2_row;
for x in min_x..max_x {
p.0 = x;
if (w0 | w1 | w2) >= 0 {
self.set_pxl_ref(p.0, p.1, &character);
}
w0 += a12;
w1 += a20;
w2 += a01;
}
w0_row += b12;
w1_row += b20;
w2_row += b01;
}
}
fn set_pxl_ref(&mut self, x: i32, y: i32, character: &Pixel)
{
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
let index = self.coord_to_index(x, y);
self.screen[index] = character.clone();
}
}
pub fn set_pxl(&mut self, x: i32, y: i32, character: Pixel)
{
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
let index = self.coord_to_index(x, y);
self.screen[index] = character;
}
}
pub fn get_pxl(&self, x: i32, y: i32) -> Result<Pixel, String>
{
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
return Ok(self.screen[self.coord_to_index(x, y)].clone());
}
Err(format!("Attempted to get_pxl out of bounds (coords: [{}, {}], bounds: [{}, {}]", x,y,self.width-1,self.height-1))
}
pub fn draw(&mut self)
{
let mut out = self.output.lock();
write!(out, "{}", termion::cursor::Goto(1,1)).unwrap();
let mut current_colors = String::from("");
let mut moving = false;
for y in 0..self.height {
for x in 0..self.width {
let index = self.coord_to_index(x as i32, y as i32);
let pixel = &self.screen[index];
if self.screen_last_frame.is_empty() || *pixel != self.screen_last_frame[index] {
if moving {
write!(out, "{}", termion::cursor::Goto(1+x as u16,1+y as u16)).unwrap();
moving = false;
}
if current_colors != pixel.colors {
current_colors = pixel.colors.clone();
write!(out, "{}", pixel).unwrap();
} else {
write!(out, "{}", pixel.chr).unwrap();
}
} else {
moving = true
}
}
if y < self.height-1 {
write!(out, "\r\n").unwrap();
}
}
out.flush().unwrap();
self.screen_last_frame = self.screen.clone();
}
pub fn wait_frame(&mut self) {
let mut pressed: Vec<Event> = vec!();
if self.time_limit > self.instant.elapsed().as_millis() {
std::thread::sleep(std::time::Duration::from_millis(((self.time_limit - self.instant.elapsed().as_millis()) % self.time_limit) as u64));
}
self.instant = std::time::Instant::now();
self.frame_count += 1;
let mut c = self.input.next();
let mut count = 0;
while c.is_some() && count < 10 {
pressed.push(c.unwrap().unwrap());
c = self.input.next();
count += 1
}
let held = utils::intersect(&utils::union(&self.keys_pressed,&self.keys_held), &pressed);
self.keys_released = utils::outersect_left(&self.keys_held, &held);
self.keys_pressed = utils::outersect_left(&pressed, &held);
self.keys_held = utils::union(&held, &self.keys_pressed);
}
pub fn is_key_pressed(&self, key: Key) -> bool
{
self.keys_pressed.contains(&Event::Key(key))
}
pub fn is_key_held(&self, key: Key) -> bool
{
self.keys_held.contains(&Event::Key(key))
}
pub fn is_key_released(&self, key: Key) -> bool
{
self.keys_released.contains(&Event::Key(key))
}
pub fn get_mouse_press(&self, button: termion::event::MouseButton) -> Option<(u32,u32)>
{
for evt in self.keys_pressed.iter() {
match evt {
Event::Mouse(me) => {
match me {
termion::event::MouseEvent::Press(mouse, x, y) => {
if *mouse == button {
return Some((x.clone() as u32 -1, y.clone() as u32 -1));
}
},
_ => {}
}
}
_ => {}
};
}
return None;
}
pub fn get_mouse_held(&self) -> Option<(u32,u32)>
{
for evt in self.keys_pressed.iter() {
match evt {
Event::Mouse(me) => {
match me {
termion::event::MouseEvent::Hold(x, y) => {
return Some((x.clone() as u32 -1, y.clone() as u32 -1));
},
_ => {}
}
}
_ => {}
};
}
return None;
}
pub fn get_mouse_released(&self) -> Option<(u32,u32)>
{
for evt in self.keys_pressed.iter() {
match evt {
Event::Mouse(me) => {
match me {
termion::event::MouseEvent::Release(x, y) => {
return Some((x.clone() as u32 -1, y.clone() as u32 -1));
},
_ => {}
}
}
_ => {}
};
}
return None;
}
#[allow(dead_code)]
pub fn debug_keys(&self)
{
println!("pressed: {:?}\r\nheld: {:?}\r\nreleased: {:?}", self.keys_pressed, self.keys_held, self.keys_released);
}
fn coord_to_index(&self, x: i32, y: i32) -> usize
{
return ((y*self.width as i32) + x) as usize;
}
}
impl Drop for ConsoleEngine {
fn drop(&mut self) {
self.end();
}
}