pub extern crate termion;
mod utils;
pub mod pixel;
use pixel::Pixel;
use termion::color;
use termion::event::Key;
use std::io::{stdout, Stdout};
use termion::raw::IntoRawMode;
use std::io::Write;
use crate::termion::input::TermRead;
pub struct ConsoleEngine {
input: termion::input::Keys<termion::AsyncReader>,
output: 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<Key>,
keys_held: Vec<Key>,
keys_released: Vec<Key>,
}
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: stdout().into_raw_mode().unwrap(),
input: termion::async_stdin().keys(),
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 {
assert!(target_fps > 0, "Target FPS needs to be greater than zero.");
let size = termion::terminal_size().unwrap();
let mut my = ConsoleEngine {
output: stdout().into_raw_mode().unwrap(),
input: termion::async_stdin().keys(),
time_limit: (1000/target_fps) as u128,
frame_count: 0,
width: size.0 as u32,
height: size.1 as u32,
screen: vec![pixel::pxl(' '); (size.0*size.1) 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_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: u32, y: u32, string: String)
{
assert!(x < self.width && y < self.height, "Attempted to print out of bounds (coords: [{}, {}], bounds: [{}, {}]", x,y,self.width-1,self.height-1);
let pos = self.coord_to_index(x, y);
let mut count = 0usize;
let char_vec: Vec<char> = string.chars().collect();
for i in pos..std::cmp::min(pos+char_vec.len(), self.screen.capacity()) {
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: u32, y: u32, string: String, fg: C1, bg: C2)
{
assert!(x < self.width && y < self.height, "Attempted to print_fbg out of bounds (coords: [{}, {}], bounds: [{}, {}]", x,y,self.width-1,self.height-1);
let pos = self.coord_to_index(x, y);
let mut count = 0usize;
let char_vec: Vec<char> = string.chars().collect();
for i in pos..std::cmp::min(pos+char_vec.len(), self.screen.capacity()) {
self.screen[i] = pixel::pxl_fbg(char_vec[count], fg.clone(), bg.clone());
count += 1;
}
}
pub fn line(&mut self, start_x: u32, start_y: u32, end_x: u32, end_y: u32, character: Pixel)
{
let delta_x = end_x as i64 - start_x as i64;
let delta_y = end_y as i64 - start_y as i64;
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 delta_abs_x = delta_x.abs();
let delta_abs_y = delta_y.abs();
let mut pos_x = 2 * delta_abs_y - delta_abs_x;
let mut pos_y = 2 * delta_abs_x - delta_abs_y;
let mut x: i32;
let mut y: i32;
if delta_abs_y <= delta_abs_x {
let x_end: i32;
if delta_x >= 0
{ x = start_x as i32; y = start_y as i32; x_end = end_x as i32; }
else
{ x = end_x as i32; y = end_y as i32; x_end = start_x as i32; }
self.set_pxl_ref(x as u32, y as u32, &character);
for x in x..x_end {
if pos_x<0 {
pos_x = pos_x + 2 * delta_abs_y;
} else {
if (delta_x<0 && delta_y<0) || (delta_x>0 && delta_y>0) {y = y + 1;} else {y = y - 1;}
pos_x = pos_x + 2 * (delta_abs_y - delta_abs_x);
}
self.set_pxl_ref(x as u32, y as u32, &character);
}
} else {
let y_end: i32;
if delta_y >= 0
{ x = start_x as i32; y = start_y as i32; y_end = end_y as i32; }
else
{ x = end_x as i32; y = end_y as i32; y_end = start_y as i32; }
self.set_pxl_ref(x as u32, y as u32, &character);
for y in y..y_end {
if pos_y<0 {
pos_y = pos_y + 2 * delta_abs_x;
} else {
if (delta_x<0 && delta_y<0) || (delta_x>0 && delta_y>0) {x = x + 1;} else {x = x - 1};
pos_y = pos_y + 2 * (delta_abs_x - delta_abs_y);
}
self.set_pxl_ref(x as u32, y as u32, &character);
}
}
self.set_pxl_ref(end_x, end_y, &character);
}
pub fn rect(&mut self, start_x: u32, start_y: u32, end_x: u32, end_y: u32, 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: u32, start_y: u32, end_x: u32, end_y: u32, character: Pixel)
{
for y in start_y..end_y {
self.line(start_x, y, end_x, y, character.clone());
}
}
pub fn circle(&mut self, x: u32, y: u32, radius: u32, character: Pixel)
{
let mut relative_pos_x = 0;
let mut relative_pos_y = radius;
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: u32, y: u32, radius: u32, character: Pixel)
{
let mut relative_pos_x = 0;
let mut relative_pos_y = radius;
let mut distance: i32 = 3 - 2 * radius as i32;
if radius == 0 {
return;
}
let mut drawline = |start_x: u32, end_x: u32, y: u32|
{
for i in start_x..end_x {
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 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;
}
}
}
fn set_pxl_ref(&mut self, x: u32, y: u32, character: &Pixel)
{
if x < self.width && y < self.height {
let index = self.coord_to_index(x, y);
self.screen[index] = character.clone();
}
}
pub fn set_pxl(&mut self, x: u32, y: u32, character: Pixel)
{
assert!(x < self.width && y < self.height, "Attempted to set_pxl out of bounds (coords: [{}, {}], bounds: [{}, {}]", x,y,self.width-1,self.height-1);
let index = self.coord_to_index(x, y);
self.screen[index] = character;
}
pub fn get_pxl(&self, x: u32, y: u32) -> Pixel
{
assert!(x < self.width && y < self.height, "Attempted to get_pxl out of bounds (coords: [{}, {}], bounds: [{}, {}]", x,y,self.width-1,self.height-1);
self.screen[self.coord_to_index(x, y)].clone()
}
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, y);
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<Key> = 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(&key)
}
pub fn is_key_held(&self, key: Key) -> bool
{
self.keys_held.contains(&key)
}
pub fn is_key_released(&self, key: Key) -> bool
{
self.keys_released.contains(&key)
}
#[allow(dead_code)]
pub fn debug_keys(&self)
{
println!("pressed: {:?}\nheld: {:?}\nreleased: {:?}", self.keys_pressed, self.keys_held, self.keys_released);
}
fn coord_to_index(&self, x: u32, y: u32) -> usize
{
return ((y*self.width) + x) as usize;
}
}
impl Drop for ConsoleEngine {
fn drop(&mut self) {
self.end();
}
}