console_engine 2.5.0

A simple terminal framework to draw things and manage user input
Documentation
//! This library provides simple features to draw things and manage user input for terminal applications.
//!
//! Besides these features, this library also provides some tools to build standalone "screens" that can be used outside of the engine itself.
//!
//! It's built on top of [Crossterm](https://crates.io/crates/crossterm) for handling the screen and inputs. You don't have to worry about initalizing anything because this crate will handle this for you.

pub extern crate crossterm;

pub mod pixel;
pub mod rect_style;
pub mod screen;
mod utils;

#[cfg(feature = "event")]
pub mod events;

#[cfg(feature = "form")]
pub mod forms;

pub use crossterm::event::{KeyCode, KeyModifiers, MouseButton};
pub use crossterm::style::Color;
use crossterm::terminal::{self, ClearType};
use crossterm::{
    event::{self, Event, KeyEvent, MouseEvent, MouseEventKind},
    ErrorKind,
};
use crossterm::{execute, queue, style};
use pixel::Pixel;
use rect_style::BorderStyle;
use screen::Screen;
use std::io::Write;
use std::io::{stdout, Stdout};

/// Console Engine Framework
///
/// # Features
///
/// *note : each link will redirect you to a bunch of functions*
///
/// - Build custom terminal display using [shapes](#method.line) or [text](#method.print)
/// - Terminal handling with a [target frame per seconds](#method.init)
/// - [Keyboard](#method.is_key_pressed) and [mouse](#method.get_mouse_press) support
/// - [Terminal resizing](#method.check_resize) support
///
/// # Basic Usage:
///
/// ```
/// use console_engine::pixel;
/// use console_engine::Color;
/// use console_engine::KeyCode;
///
/// fn main() {
///     // initializes a screen of 20x10 characters with a target of 3 frame per second
///     // coordinates will range from [0,0] to [19,9]
///     let mut engine = console_engine::ConsoleEngine::init(20, 10, 3);
///     let value = 14;
///     // main loop, be aware that you'll have to break it because ctrl+C is captured
///     loop {
///         engine.wait_frame(); // wait for next frame + capture inputs
///         engine.clear_screen(); // reset the screen
///
///         engine.line(0, 0, 19, 9, pixel::pxl('#')); // draw a line of '#' from [0,0] to [19,9]
///         engine.print(0, 4, format!("Result: {}", value).as_str()); // prints some value at [0,4]
///
///         engine.set_pxl(4, 0, pixel::pxl_fg('O', Color::Cyan)); // write a majestic cyan 'O' at [4,0]
///
///         if engine.is_key_pressed(KeyCode::Char('q')) { // if the user presses 'q' :
///             break; // exits app
///         }
///
///         engine.draw(); // draw the screen
///     }
/// }
/// ```
///
/// #
///
#[allow(clippy::needless_doctest_main)]
pub struct ConsoleEngine {
    stdout: Stdout,
    time_limit: std::time::Duration,
    /// The current frame count, publicly accessible
    /// Has no purpose internally, use it as you want
    pub frame_count: usize,
    width: u32,
    height: u32,
    screen: Screen,
    screen_last_frame: Screen,
    instant: std::time::Instant,
    keys_pressed: Vec<KeyEvent>,
    keys_held: Vec<KeyEvent>,
    keys_released: Vec<KeyEvent>,
    mouse_events: Vec<MouseEvent>,
    resize_events: Vec<(u16, u16)>,
}

impl ConsoleEngine {
    /// Initialize a screen of the provided width and height, and load the target FPS
    pub fn init(width: u32, height: u32, target_fps: u32) -> Result<ConsoleEngine, ErrorKind> {
        assert!(target_fps > 0, "Target FPS needs to be greater than zero.");
        let mut engine = ConsoleEngine {
            stdout: stdout(),
            time_limit: std::time::Duration::from_millis(1000 / target_fps as u64),
            frame_count: 0,
            width,
            height,
            screen: Screen::new(width, height),
            screen_last_frame: Screen::new_empty(width, height),
            instant: std::time::Instant::now(),
            keys_pressed: vec![],
            keys_held: vec![],
            keys_released: vec![],
            mouse_events: vec![],
            resize_events: vec![],
        };
        let previous_panic_hook = std::panic::take_hook();
        std::panic::set_hook(Box::new(move |panic_info| {
            Self::handle_panic(panic_info);
            previous_panic_hook(panic_info);
            std::process::exit(1);
        }));
        engine.begin()?;
        engine.try_resize(width, height)?;
        Ok(engine)
    }

    /// Initialize a screen filling the entire terminal with the target FPS
    pub fn init_fill(target_fps: u32) -> Result<ConsoleEngine, ErrorKind> {
        let size = crossterm::terminal::size().unwrap();
        ConsoleEngine::init(size.0 as u32, size.1 as u32, target_fps)
    }

    /// Initialize a screen filling the entire terminal with the target FPS
    /// Also check the terminal width and height and assert if the terminal has at least the asked size
    pub fn init_fill_require(
        width: u32,
        height: u32,
        target_fps: u32,
    ) -> Result<ConsoleEngine, ErrorKind> {
        let mut engine = ConsoleEngine::init_fill(target_fps)?;
        engine.try_resize(width, height)?;
        Ok(engine)
    }

    /// Try to resize the terminal to match the asked width and height at minimum
    fn try_resize(&mut self, width: u32, height: u32) -> Result<(), ErrorKind> {
        let size = crossterm::terminal::size()?;
        if (size.0 as u32) < width || (size.1 as u32) < height {
            execute!(
                self.stdout,
                crossterm::terminal::SetSize(width as u16, height as u16),
                crossterm::terminal::SetSize(width as u16, height as u16)
            )?;
            self.resize(width, height);
        }
        if crossterm::terminal::size()? < (width as u16, height as u16) {
            Err(ErrorKind::new(std::io::ErrorKind::Other, format!("Your terminal must have at least a width and height of {}x{} characters. Currently has {}x{}", width, height, size.0, size.1)))
        } else {
            Ok(())
        }
    }

    /// Initializes the internal components such as hiding the cursor
    fn begin(&mut self) -> Result<(), ErrorKind> {
        terminal::enable_raw_mode().unwrap();
        execute!(
            self.stdout,
            terminal::EnterAlternateScreen,
            terminal::Clear(ClearType::All),
            crossterm::cursor::Hide,
            crossterm::cursor::MoveTo(0, 0),
            crossterm::event::EnableMouseCapture
        )
    }

    /// Gracefully stop the engine, and set back a visible cursor
    fn end(&mut self) {
        execute!(
            self.stdout,
            crossterm::cursor::Show,
            style::SetBackgroundColor(Color::Reset),
            style::SetForegroundColor(Color::Reset),
            crossterm::event::DisableMouseCapture,
            terminal::LeaveAlternateScreen
        )
        .unwrap();
        terminal::disable_raw_mode().unwrap();
    }

    /// stops the engine when a panic occurs
    /// Similar to the end function, but without the engine instance.
    /// So we assume we used stdout, and free it.
    fn handle_panic(_panic_info: &std::panic::PanicInfo) {
        execute!(
            stdout(),
            crossterm::cursor::Show,
            style::SetBackgroundColor(Color::Reset),
            style::SetForegroundColor(Color::Reset),
            crossterm::event::DisableMouseCapture,
            terminal::LeaveAlternateScreen
        )
        .unwrap();
        terminal::disable_raw_mode().unwrap();
    }

    /// Set the terminal's title
    pub fn set_title(&mut self, title: &str) {
        execute!(self.stdout, crossterm::terminal::SetTitle(title)).ok();
    }

    /// Get the screen width
    pub fn get_width(&self) -> u32 {
        self.screen.get_width()
    }

    /// Get the screen height
    pub fn get_height(&self) -> u32 {
        self.screen.get_height()
    }

    /// Reset the screen to a blank state
    pub fn clear_screen(&mut self) {
        self.screen.clear()
    }

    /// Fill the entire screen to the given pixel
    pub fn fill(&mut self, pixel: Pixel) {
        self.screen.fill(pixel);
    }

    /// prints a string at the specified coordinates.
    /// The string will be cropped if it reach the right border
    ///
    /// usage:
    /// ```
    /// engine.print(0,0, "Hello, world!");
    /// engine.print(0, 4, format!("Score: {}", score).as_str());
    /// ```
    pub fn print(&mut self, x: i32, y: i32, string: &str) {
        self.screen.print(x, y, string)
    }

    /// prints a string at the specified coordinates with the specified foreground and background color
    /// The string will automatically overlaps if it reach the right border
    ///
    /// usage:
    /// ```
    /// use console_engine::Color;
    ///
    /// // print "Hello, world" in blue on white background
    /// engine.print(0,0, "Hello, world!", Color::Blue, Color::White);
    /// ```
    pub fn print_fbg(&mut self, x: i32, y: i32, string: &str, fg: Color, bg: Color) {
        self.screen.print_fbg(x, y, string, fg, bg)
    }

    /// Prints another screen on specified coordinates.
    /// Useful when you want to manage several "subscreen"
    ///
    /// *see example* `screen-embed`
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// use console_engine::screen::Screen;
    ///
    /// // create a new Screen struct and draw a square inside it
    /// let mut my_square = Screen::new(8,8);
    /// my_square.rect(0,0,7,7,pixel::pxl('#'));
    /// my_square.print(1,1,"square");
    ///
    /// // prints the square in the engine's screen at a specific location
    /// engine.print_screen(5,2, &my_square);
    /// ```
    pub fn print_screen(&mut self, x: i32, y: i32, source: &Screen) {
        self.screen.print_screen(x, y, source)
    }

    /// Prints another screen on specified coordinates, ignoring a specific character while printing
    /// Ignoring a character will behave like transparency
    ///
    /// see [print_screen](#method.print_screen) for usage
    pub fn print_screen_alpha(&mut self, x: i32, y: i32, source: &Screen, alpha_character: char) {
        self.screen
            .print_screen_alpha(x, y, source, alpha_character)
    }

    /// draws a line of the provided character between two sets of coordinates
    /// see: [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm)
    ///
    /// Note : Your line can start or end out of bounds. These pixels won't be drawn
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.line(0, 0, 9, 9, pixel::pxl('#'));
    /// ```
    pub fn line(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel) {
        self.screen.line(start_x, start_y, end_x, end_y, character)
    }

    /// Draws a rectangle of the provided character between two sets of coordinates
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.rect(0, 0, 9, 9, pixel::pxl('#'));
    /// ```
    pub fn rect(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel) {
        self.screen.rect(start_x, start_y, end_x, end_y, character)
    }

    /// Draws a rectangle with custom borders of the provided between two sets of coordinates. Check the BorderStyle struct to learn how to use built-in or custom styles
    ///
    /// usage:
    /// ```
    /// use console_engine::rect_style::BorderStyle;
    /// // ...
    /// engine.rect_border(0, 0, 9, 9, BorderStyle::new_simple());
    /// ```
    pub fn rect_border(
        &mut self,
        start_x: i32,
        start_y: i32,
        end_x: i32,
        end_y: i32,
        rect_style: BorderStyle,
    ) {
        self.screen
            .rect_border(start_x, start_y, end_x, end_y, rect_style)
    }

    /// Fill a rectangle of the provided character between two sets of coordinates
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.fill_rect(0, 0, 9, 9, pixel::pxl('#'));
    /// ```
    pub fn fill_rect(
        &mut self,
        start_x: i32,
        start_y: i32,
        end_x: i32,
        end_y: i32,
        character: Pixel,
    ) {
        self.screen
            .fill_rect(start_x, start_y, end_x, end_y, character)
    }

    /// Draws a circle of the provided character at an x and y position with a radius
    /// see: [olcPixelGameEngine Repository](https://github.com/OneLoneCoder/olcPixelGameEngine)
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.circle(10, 10, 4, pixel::pxl('#'));
    /// ```
    pub fn circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel) {
        self.screen.circle(x, y, radius, character)
    }

    /// Fill a circle of the provided character at an x and y position with a radius
    /// see: [olcPixelGameEngine Repository](https://github.com/OneLoneCoder/olcPixelGameEngine)
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.fill_circle(10, 10, 4, pixel::pxl('#'));
    /// ```
    pub fn fill_circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel) {
        self.screen.fill_circle(x, y, radius, character)
    }

    /// Draws a triangle of the provided character using three sets of coordinates
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.triangle(8,8, 4,6, 9,2, pixel::pxl('#'));
    /// ```
    #[allow(clippy::too_many_arguments)]
    pub fn triangle(
        &mut self,
        x1: i32,
        y1: i32,
        x2: i32,
        y2: i32,
        x3: i32,
        y3: i32,
        character: Pixel,
    ) {
        self.screen.triangle(x1, y1, x2, y2, x3, y3, character)
    }

    /// Fill a triangle of the provided character using three sets of coordinates
    /// see: [rustyPixelGameEngine Repository](https://github.com/mattbettcher/rustyPixelGameEngine)
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.fill_triangle(8,8, 4,6, 9,2, pixel::pxl('#'));
    /// ```
    #[allow(clippy::too_many_arguments)]
    pub fn fill_triangle(
        &mut self,
        x1: i32,
        y1: i32,
        x2: i32,
        y2: i32,
        x3: i32,
        y3: i32,
        character: Pixel,
    ) {
        self.screen.fill_triangle(x1, y1, x2, y2, x3, y3, character)
    }

    /// Scrolls the screen for a certain amount of characters vertically or horizontally
    /// Scrolling is a destructive process, the outer border will be filled with the background pixel.
    ///
    /// Scrolling a positive value will move the screen characters to the left / top,
    /// freeing space to the right / bottom
    ///
    /// Scrolling a negative value will move the screen characters to the right / bottom,
    /// freeing space to the left / top
    ///
    /// usage :
    /// ```
    /// use console_engine::pixel;
    ///
    /// // fill the screen with characters
    /// engine.fill(pixel::pxl('#'));
    /// // free one space to the bottom
    /// engine.scroll(0,1,pixel::pxl(' '));
    /// // print something at this place
    /// engine.print(0, height-1, "Hello, world!");
    /// ```
    pub fn scroll(&mut self, h_scroll: i32, v_scroll: i32, background: Pixel) {
        self.screen.scroll(h_scroll, v_scroll, background);
    }

    /// sets the provided character in the specified coordinates
    /// out of bounds pixels will be ignored
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // ...
    /// engine.set_pxl(3,8,pixel::pixel('o'));
    /// ```
    pub fn set_pxl(&mut self, x: i32, y: i32, character: Pixel) {
        self.screen.set_pxl(x, y, character)
    }

    /// Get the character stored at provided coordinates
    ///
    /// usage:
    /// ```
    /// if engine.get_pxl(3,8).unwrap().chr == 'o' {
    ///     engine.print(0,0,"Found a 'o'");
    /// }
    /// ```
    pub fn get_pxl(&self, x: i32, y: i32) -> Result<Pixel, String> {
        self.screen.get_pxl(x, y)
    }

    /// Resizes the screen to match the given width and height
    /// truncates the bottom and right side of the screen
    ///
    /// usage:
    /// ```
    /// engine.resize(40,10)
    /// ```
    pub fn resize(&mut self, new_width: u32, new_height: u32) {
        self.screen.resize(new_width, new_height);
        self.width = new_width;
        self.height = new_height;
        self.screen_last_frame = Screen::new_empty(self.width, self.height);
    }

    /// Extracts part of the current screen as a separate Screen object
    /// The original screen is not altered
    /// If the coordinates are out of bounds, they'll be replace by the `default` pixel
    ///
    /// usage:
    /// ```
    /// use console_engine::pixel;
    /// // extract a 3x2 screen from the engine screen
    /// let scr_chunk = engine.extract(10, 4, 12, 5, pixel::pxl(' '));
    /// ```
    pub fn extract(
        &self,
        start_x: i32,
        start_y: i32,
        end_x: i32,
        end_y: i32,
        default: Pixel,
    ) -> Screen {
        self.screen.extract(start_x, start_y, end_x, end_y, default)
    }

    /// Changes the screen instance used by the engine and updates internal informations
    ///
    /// Useful if you want to manage multiple screens independently.
    ///
    /// usage
    /// ```
    /// // create a new screen of 40x10 and draw some things on it
    /// let mut scr = Screen::new(40,10)
    /// scr.rect(0,0,39,9, pixel::pxl("#"));
    /// // ...
    ///
    /// // keep a backup of the old screen before replacing it
    /// let old_scr = engine.get_screen();
    /// // change the engine's current screen to the newly created one
    /// engine.set_screen(&scr);
    ///
    /// // ... later
    /// // set back the old screen
    /// engine.set_screen(&old_scr);
    /// ```
    pub fn set_screen(&mut self, screen: &Screen) {
        self.width = screen.get_width();
        self.height = screen.get_height();
        self.screen = screen.clone();
        self.request_full_draw();
    }

    /// Returns a clone of the current screen
    ///
    /// You can keep it into a variable to restore the screen later, via `set_screen`.
    /// You can then use the to_string method to write the screen in a file for example
    ///
    /// see [set_screen](#method.set_screen) for a more complete example
    ///
    /// usage :
    /// ```
    /// let scr = engine.get_screen();
    /// ```
    pub fn get_screen(&self) -> Screen {
        self.screen.clone()
    }

    /// Draw the screen in the terminal
    /// For best results, use it once per frame
    ///
    /// If the terminal content is changed outside of the draw call, the draw function won't be aware of it and may leave some artifacts.
    /// If you want to force the draw function to redraw the entire screen, you should call [request_full_draw](#method.request_full_draw) before `draw()`.
    ///
    /// That's because for optimizing the output speed, the draw function only draw the difference between each frames.
    ///
    /// usage:
    /// ```
    /// engine.print(0,0,"Hello, world!"); // <- prints "Hello, world!" in 'screen' memory
    /// engine.draw(); // display 'screen' memory to the user's terminal
    /// ```
    pub fn draw(&mut self) {
        // we use the queue! macro to store in one-shot the screen we'll write.
        // This is an optimization because we write all we need once instead of writing small bit of screen by small bit of screen.
        // Actually, this does not change much for Linux terminals (like 5 fps gained from this)
        // But for windows terminal we can see huge improvements (example lines-fps goes from 35-40 fps to 65-70 for a 100x50 term)
        // reset cursor position
        queue!(self.stdout, crossterm::cursor::MoveTo(0, 0)).unwrap();
        let mut first = true;
        let mut current_colors: (Color, Color) = (Color::Reset, Color::Reset);
        let mut moving = false;
        self.screen_last_frame.check_empty(); // refresh internal "empty" value of the last_frame screen
        let mut skip_next = false;

        // iterates through the screen memory and prints it on the output buffer
        for y in 0..self.height as i32 {
            for x in 0..self.width as i32 {
                let pixel = self.screen.get_pxl(x, y).unwrap();
                // we check if the screen has been modified at this coordinate or if the last_frame screen is empty
                // if so, we write on the terminal normally, else we set a 'moving' flag
                if skip_next {
                    skip_next = false;
                    continue;
                }
                if let Some(char_width) = unicode_width::UnicodeWidthChar::width(pixel.chr) {
                    if char_width > 1 {
                        skip_next = true;
                    }
                }
                if self.screen_last_frame.is_empty()
                    || pixel != self.screen_last_frame.get_pxl(x, y).unwrap()
                {
                    if moving {
                        // if the moving flag is set, we need to write a goto instruction first
                        // this optimization minimize useless write on the screen
                        // actually writing to the screen is very slow so it's a good compromise
                        queue!(self.stdout, crossterm::cursor::MoveTo(x as u16, y as u16)).unwrap();
                        moving = false;
                    }
                    // we check if the last color is the same as the current one.
                    // if the color is the same, only print the character
                    // the less we write on the output the faster we'll get
                    // and additional characters for colors we already have set is
                    // time consuming
                    if current_colors != pixel.get_colors() || first {
                        current_colors = pixel.get_colors();
                        queue!(
                            self.stdout,
                            style::SetForegroundColor(pixel.fg),
                            style::SetBackgroundColor(pixel.bg),
                            style::Print(pixel.chr)
                        )
                        .unwrap();
                        first = false;
                    } else {
                        queue!(self.stdout, style::Print(pixel.chr)).unwrap();
                    }
                } else {
                    moving = true
                }
            }
            // at the end of each line, we write a newline character
            // I believe that since we're on raw mode we need CR and LF even on unix terminals
            if y < self.height as i32 - 1 {
                queue!(self.stdout, style::Print("\r\n")).unwrap();
            }
        }
        // flush the buffer into user's terminal
        self.stdout.flush().unwrap();
        // store the frame for the next draw call
        self.screen_last_frame = self.screen.clone();
    }

    /// Ask the engine to redraw the entire screen on the next `draw` call
    /// Useful if the terminal's content got altered outside of the `draw` function.
    ///
    /// See [draw](#method.draw) for more info about the drawing process
    pub fn request_full_draw(&mut self) {
        // reset the last_frame screen to force a full redraw
        self.screen_last_frame = Screen::new_empty(self.width, self.height);
    }

    /// Pause the execution until the next frame need to be rendered
    /// Internally gets user's input for the next frame
    ///
    /// usage:
    /// ```
    /// // initializes a screen with a 10x10 screen and targetting 30 fps
    /// let mut engine = console_engine::ConsoleEngine::init(10, 10, 30);
    /// loop {
    ///     engine.wait_frame(); // wait for next frame
    ///     // do your stuff
    /// }
    /// ```
    pub fn wait_frame(&mut self) {
        let mut captured_keyboard: Vec<KeyEvent> = vec![];
        let mut captured_mouse: Vec<MouseEvent> = vec![];
        let mut captured_resize: Vec<(u16, u16)> = vec![];

        // if there is time before next frame, poll keyboard and mouse events until next frame
        let mut elapsed_time = self.instant.elapsed();
        while self.time_limit > elapsed_time {
            let remaining_time = self.time_limit - elapsed_time;
            if let Ok(has_event) = event::poll(std::time::Duration::from_millis(
                (remaining_time.as_millis() % self.time_limit.as_millis()) as u64,
            )) {
                if has_event {
                    if let Ok(current_event) = event::read() {
                        match current_event {
                            Event::Key(evt) => {
                                captured_keyboard.push(evt);
                            }
                            Event::Mouse(evt) => {
                                captured_mouse.push(evt);
                            }
                            Event::Resize(w, h) => {
                                captured_resize.push((w, h));
                            }
                        };
                    }
                }
            }
            elapsed_time = self.instant.elapsed();
        }
        self.instant = std::time::Instant::now();
        self.frame_count = self.frame_count.wrapping_add(1);

        // updates pressed / held / released states
        let held = utils::intersect(
            &utils::union(&self.keys_pressed, &self.keys_held),
            &captured_keyboard,
        );
        self.keys_released = utils::outersect_left(&self.keys_held, &held);
        self.keys_pressed = utils::outersect_left(&captured_keyboard, &held);
        self.keys_held = utils::union(&held, &self.keys_pressed);
        self.mouse_events = captured_mouse;
        self.resize_events = captured_resize;
    }

    /// Poll the next ConsoleEngine Event
    /// This function waits for the next event to occur,
    /// from a user event like key press or mouse click to automatic events like frame change
    ///
    /// usage:
    /// ```
    /// use console_engine::events::Event;
    /// // initializes a screen with a 10x10 screen and targetting 30 fps
    /// let mut engine = console_engine::ConsoleEngine::init(10, 10, 30).unwrap();
    /// loop {
    ///     match engine.poll() {
    ///         Event::Frame => {
    ///             // do things
    ///             engine.draw();
    ///         }
    ///         Event::Key(key_event) => {
    ///             // handle keys
    ///         }
    ///     }
    /// }
    /// ```
    #[cfg(feature = "event")]
    pub fn poll(&mut self) -> events::Event {
        use std::time::Duration;

        let mut elapsed_time = self.instant.elapsed();
        // guarantees that this loop is running at least once
        loop {
            let remaining_time = if self.time_limit > elapsed_time {
                self.time_limit - elapsed_time
            } else {
                Duration::from_millis(0)
            };
            if let Ok(has_event) = event::poll(std::time::Duration::from_millis(
                (remaining_time.as_millis() % self.time_limit.as_millis()) as u64,
            )) {
                if has_event {
                    if let Ok(current_event) = event::read() {
                        match current_event {
                            Event::Key(evt) => return events::Event::Key(evt),
                            Event::Mouse(evt) => return events::Event::Mouse(evt),
                            Event::Resize(w, h) => return events::Event::Resize(w, h),
                        };
                    }
                }
            }
            elapsed_time = self.instant.elapsed();
            if self.time_limit <= elapsed_time {
                break;
            }
        }
        self.instant = std::time::Instant::now();
        self.frame_count = self.frame_count.wrapping_add(1);
        events::Event::Frame
    }

    /// Check and resize the terminal if needed.
    /// Note that the resize will occur but there is no check yet if the terminal
    /// is smaller than the required size provided in the init() function.
    ///
    /// usage:
    /// ```
    /// // initializes a screen filling the terminal
    /// let mut engine = console_engine::ConsoleEngine::init_fill(30);
    /// loop {
    ///     engine.wait_frame(); // wait for next frame
    ///     engine.check_resize(); // resize the terminal if its size has changed
    ///     // do your stuff
    /// }
    /// ```
    pub fn check_resize(&mut self) {
        if crossterm::terminal::size().unwrap() != (self.width as u16, self.height as u16) {
            // resize terminal
            let size = crossterm::terminal::size().unwrap();
            let new_width = size.0 as u32;
            let new_height = size.1 as u32;

            self.resize(new_width, new_height);
        }
    }

    /// checks whenever a key is pressed (first frame held only)
    ///
    /// usage:
    /// ```
    /// use console_engine::KeyCode;
    ///
    /// loop {
    ///     engine.wait_frame(); // wait for next frame + captures input
    ///
    ///     if engine.is_key_pressed(KeyCode::Char('q')) {
    ///         break; // exits app
    ///     }
    /// }
    /// ```
    pub fn is_key_pressed(&self, key: KeyCode) -> bool {
        self.is_key_pressed_with_modifier(key, KeyModifiers::NONE)
    }

    /// checks whenever a key + a modifier (ctrl, shift...) is pressed (first frame held only)
    ///
    /// usage:
    /// ```
    /// use console_engine::{KeyCode, KeyModifiers}
    ///
    /// loop {
    ///     engine.wait_frame(); // wait for next frame + captures input
    ///
    ///     if engine.is_key_pressed_with_modifier(KeyCode::Char('c'), KeyModifiers::CONTROL) {
    ///         break; // exits app
    ///     }
    /// }
    /// ```
    pub fn is_key_pressed_with_modifier(&self, key: KeyCode, modifier: KeyModifiers) -> bool {
        self.keys_pressed.contains(&KeyEvent::new(key, modifier))
    }

    /// checks whenever a key is held down
    ///
    /// usage:
    /// ```
    /// use console_engine::KeyCode;
    ///
    /// loop {
    ///     engine.wait_frame(); // wait for next frame + captures input
    ///
    ///     if engine.is_key_held(KeyCode::Char('8')) && pos_y > 0 {
    ///         pos_y -= 1; // move position upward
    ///     }
    /// }
    /// ```
    pub fn is_key_held(&self, key: KeyCode) -> bool {
        self.is_key_held_with_modifier(key, KeyModifiers::NONE)
    }

    /// checks whenever a key + a modifier (ctrl, shift...) is held down
    pub fn is_key_held_with_modifier(&self, key: KeyCode, modifier: KeyModifiers) -> bool {
        self.keys_held.contains(&KeyEvent::new(key, modifier))
    }

    /// checks whenever a key has been released (first frame released)
    ///
    /// usage:
    /// ```
    /// use console_engine::KeyCode;
    ///
    /// if engine.is_key_held(KeyCode::Char('h')) {
    ///     engine.clear_screen();
    ///     engine.print(0,0,"Please don't hold this button.");
    ///     engine.draw();
    ///     while !engine.is_key_released(KeyCode::Char('h')) {
    ///         engine.wait_frame(); // refresh button's states
    ///     }
    /// }
    /// ```
    pub fn is_key_released(&self, key: KeyCode) -> bool {
        self.is_key_released_with_modifier(key, KeyModifiers::NONE)
    }

    /// checks whenever a key + a modifier (ctrl, shift...) has been released (first frame released)
    pub fn is_key_released_with_modifier(&self, key: KeyCode, modifier: KeyModifiers) -> bool {
        self.keys_released.contains(&KeyEvent::new(key, modifier))
    }

    /// Give the mouse's terminal coordinates if the provided button has been pressed
    ///
    /// usage:
    /// ```
    /// use console_engine::MouseButton;
    ///
    /// // prints a 'P' where the mouse's left button has been pressed
    /// let mouse_pos = engine.get_mouse_press(MouseButton::Left);
    /// if let Some(mouse_pos) = mouse_pos {
    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('P'));
    /// }
    /// ```
    ///
    pub fn get_mouse_press(&self, button: MouseButton) -> Option<(u32, u32)> {
        self.get_mouse_press_with_modifier(button, KeyModifiers::NONE)
    }

    /// Give the mouse's terminal coordinates if the provided button + modifier (ctrl, shift, ...) has been pressed
    pub fn get_mouse_press_with_modifier(
        &self,
        button: MouseButton,
        modifier: KeyModifiers,
    ) -> Option<(u32, u32)> {
        for evt in self.mouse_events.iter() {
            if let MouseEventKind::Down(mouse) = evt.kind {
                if mouse == button && evt.modifiers == modifier {
                    return Some((evt.column as u32, evt.row as u32));
                }
            };
        }
        None
    }

    /// Give the terminal resize event
    ///
    /// usage:
    /// ```
    /// if let Some((width, height)) = engine.get_resize() {
    ///     // do something
    /// }
    /// ```
    pub fn get_resize(&self) -> Option<(u16, u16)> {
        for evt in self.resize_events.iter() {
            if let Event::Resize(w, h) = Event::Resize(evt.0, evt.1) {
                return Some((w, h));
            };
        }
        None
    }

    /// Give the mouse's terminal coordinates if a button is held on the mouse
    ///
    /// usage:
    /// ```
    /// use console_engine::MouseButton;
    ///
    /// // prints a 'H' where the mouse is currently held
    /// let mouse_pos = engine.get_mouse_held(MouseButton::Left);
    /// if let Some(mouse_pos) = mouse_pos {
    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('H'));
    /// }
    /// ```
    pub fn get_mouse_held(&self, button: MouseButton) -> Option<(u32, u32)> {
        self.get_mouse_held_with_modifier(button, KeyModifiers::NONE)
    }

    /// Give the mouse's terminal coordinates if a button + modifier (ctrl, shift, ...) is held on the mouse
    pub fn get_mouse_held_with_modifier(
        &self,
        button: MouseButton,
        modifier: KeyModifiers,
    ) -> Option<(u32, u32)> {
        for evt in self.mouse_events.iter() {
            if let MouseEventKind::Drag(mouse) = evt.kind {
                if mouse == button && evt.modifiers == modifier {
                    return Some((evt.column as u32, evt.row as u32));
                }
            };
        }
        None
    }

    /// Give the mouse's terminal coordinates if a button has been released on the mouse
    ///
    /// usage:
    /// ```
    /// use console_engine::MouseButton;
    ///
    /// // prints a 'R' where the mouse has been released
    /// let mouse_pos = engine.get_mouse_released(MouseButton::Left);
    /// if let Some(mouse_pos) = mouse_pos {
    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('R'));
    /// }
    /// ```
    pub fn get_mouse_released(&self, button: MouseButton) -> Option<(u32, u32)> {
        self.get_mouse_released_with_modifier(button, KeyModifiers::NONE)
    }

    /// Give the mouse's terminal coordinates if a button + modifier (ctrl, shift, ...) has been released on the mouse
    pub fn get_mouse_released_with_modifier(
        &self,
        button: MouseButton,
        modifier: KeyModifiers,
    ) -> Option<(u32, u32)> {
        for evt in self.mouse_events.iter() {
            if let MouseEventKind::Up(mouse) = evt.kind {
                if mouse == button && evt.modifiers == modifier {
                    return Some((evt.column as u32, evt.row as u32));
                }
            };
        }
        None
    }

    /// checks whenever the mouse's scroll has been turned down, towards the user
    ///
    /// usage:
    /// ```
    /// if engine.is_mouse_scrolled_down() {
    ///     // do some scrolling logic
    /// }
    /// ```
    pub fn is_mouse_scrolled_down(&self) -> bool {
        self.is_mouse_scrolled_down_with_modifier(KeyModifiers::NONE)
    }

    /// checks whenever the mouse's scroll has been turned down, towards the user with a modifier (ctrl, shift, ...)
    pub fn is_mouse_scrolled_down_with_modifier(&self, modifier: KeyModifiers) -> bool {
        for evt in self.mouse_events.iter() {
            if let MouseEventKind::ScrollDown = evt.kind {
                if evt.modifiers == modifier {
                    return true;
                }
            };
        }
        false
    }

    /// checks whenever the mouse's scroll has been turned up, away from the user
    ///
    /// usage:
    /// ```
    /// if engine.is_mouse_scrolled_up() {
    ///     // do some scrolling logic
    /// }
    /// ```
    pub fn is_mouse_scrolled_up(&self) -> bool {
        self.is_mouse_scrolled_up_with_modifier(KeyModifiers::NONE)
    }

    /// checks whenever the mouse's scroll has been turned up, away from the user with a modifier (ctrl, shift, ...)
    pub fn is_mouse_scrolled_up_with_modifier(&self, modifier: KeyModifiers) -> bool {
        for evt in self.mouse_events.iter() {
            if let MouseEventKind::ScrollUp = evt.kind {
                if evt.modifiers == modifier {
                    return true;
                }
            };
        }
        false
    }
}

impl Drop for ConsoleEngine {
    /// gracefully stop the engine when dropping it
    fn drop(&mut self) {
        self.end();
    }
}