console_engine/
lib.rs

1//! This library provides simple features to draw things and manage user input for terminal applications.
2//!
3//! Besides these features, this library also provides some tools to build standalone "screens" that can be used outside of the engine itself.
4//!
5//! 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.
6
7pub extern crate crossterm;
8
9pub mod pixel;
10pub mod rect_style;
11pub mod screen;
12mod utils;
13
14#[cfg(feature = "event")]
15pub mod events;
16
17#[cfg(feature = "form")]
18pub mod forms;
19
20pub use crossterm::event::{KeyCode, KeyEventKind, KeyModifiers, MouseButton};
21pub use crossterm::style::Color;
22use crossterm::terminal::{self, ClearType};
23use crossterm::{
24    event::{self, Event, KeyEvent, MouseEvent, MouseEventKind},
25    ErrorKind,
26};
27use crossterm::{execute, queue, style};
28use pixel::Pixel;
29use rect_style::BorderStyle;
30use screen::Screen;
31use std::io::Write;
32use std::io::{stdout, Stdout};
33
34/// Console Engine Framework
35///
36/// # Features
37///
38/// *note : each link will redirect you to a bunch of functions*
39///
40/// - Build custom terminal display using [shapes](#method.line) or [text](#method.print)
41/// - Terminal handling with a [target frame per seconds](#method.init)
42/// - [Keyboard](#method.is_key_pressed) and [mouse](#method.get_mouse_press) support
43/// - [Terminal resizing](#method.check_resize) support
44///
45/// # Basic Usage:
46///
47/// ```
48/// use console_engine::pixel;
49/// use console_engine::Color;
50/// use console_engine::KeyCode;
51///
52/// fn main() {
53///     // initializes a screen of 20x10 characters with a target of 3 frame per second
54///     // coordinates will range from [0,0] to [19,9]
55///     let mut engine = console_engine::ConsoleEngine::init(20, 10, 3);
56///     let value = 14;
57///     // main loop, be aware that you'll have to break it because ctrl+C is captured
58///     loop {
59///         engine.wait_frame(); // wait for next frame + capture inputs
60///         engine.clear_screen(); // reset the screen
61///
62///         engine.line(0, 0, 19, 9, pixel::pxl('#')); // draw a line of '#' from [0,0] to [19,9]
63///         engine.print(0, 4, format!("Result: {}", value).as_str()); // prints some value at [0,4]
64///
65///         engine.set_pxl(4, 0, pixel::pxl_fg('O', Color::Cyan)); // write a majestic cyan 'O' at [4,0]
66///
67///         if engine.is_key_pressed(KeyCode::Char('q')) { // if the user presses 'q' :
68///             break; // exits app
69///         }
70///
71///         engine.draw(); // draw the screen
72///     }
73/// }
74/// ```
75///
76/// #
77///
78#[allow(clippy::needless_doctest_main)]
79pub struct ConsoleEngine {
80    stdout: Stdout,
81    time_limit: std::time::Duration,
82    /// The current frame count, publicly accessible
83    /// Has no purpose internally, use it as you want
84    pub frame_count: usize,
85    width: u32,
86    height: u32,
87    screen: Screen,
88    screen_last_frame: Screen,
89    instant: std::time::Instant,
90    keys_pressed: Vec<KeyEvent>,
91    keys_held: Vec<KeyEvent>,
92    keys_released: Vec<KeyEvent>,
93    mouse_events: Vec<MouseEvent>,
94    resize_events: Vec<(u16, u16)>,
95}
96
97impl ConsoleEngine {
98    /// Initialize a screen of the provided width and height, and load the target FPS
99    pub fn init(width: u32, height: u32, target_fps: u32) -> Result<ConsoleEngine, ErrorKind> {
100        assert!(target_fps > 0, "Target FPS needs to be greater than zero.");
101        let mut engine = ConsoleEngine {
102            stdout: stdout(),
103            time_limit: std::time::Duration::from_millis(1000 / target_fps as u64),
104            frame_count: 0,
105            width,
106            height,
107            screen: Screen::new(width, height),
108            screen_last_frame: Screen::new_empty(width, height),
109            instant: std::time::Instant::now(),
110            keys_pressed: vec![],
111            keys_held: vec![],
112            keys_released: vec![],
113            mouse_events: vec![],
114            resize_events: vec![],
115        };
116        let previous_panic_hook = std::panic::take_hook();
117        std::panic::set_hook(Box::new(move |panic_info| {
118            Self::handle_panic(panic_info);
119            previous_panic_hook(panic_info);
120            std::process::exit(1);
121        }));
122        engine.begin()?;
123        engine.try_resize(width, height)?;
124        Ok(engine)
125    }
126
127    /// Initialize a screen filling the entire terminal with the target FPS
128    pub fn init_fill(target_fps: u32) -> Result<ConsoleEngine, ErrorKind> {
129        let size = crossterm::terminal::size()?;
130        ConsoleEngine::init(size.0 as u32, size.1 as u32, target_fps)
131    }
132
133    /// Initialize a screen filling the entire terminal with the target FPS
134    /// Also check the terminal width and height and assert if the terminal has at least the asked size
135    pub fn init_fill_require(
136        width: u32,
137        height: u32,
138        target_fps: u32,
139    ) -> Result<ConsoleEngine, ErrorKind> {
140        let mut engine = ConsoleEngine::init_fill(target_fps)?;
141        engine.try_resize(width, height)?;
142        Ok(engine)
143    }
144
145    /// Try to resize the terminal to match the asked width and height at minimum
146    fn try_resize(&mut self, width: u32, height: u32) -> Result<(), ErrorKind> {
147        let size = crossterm::terminal::size()?;
148        if (size.0 as u32) < width || (size.1 as u32) < height {
149            execute!(
150                self.stdout,
151                crossterm::terminal::SetSize(width as u16, height as u16),
152                crossterm::terminal::SetSize(width as u16, height as u16)
153            )?;
154            self.resize(width, height);
155            // flush events
156            #[cfg(feature = "event")]
157            use std::time::Duration;
158            #[cfg(feature = "event")]
159            while let Ok(true) = event::poll(Duration::from_micros(100)) {
160                event::read().ok();
161            }
162        }
163        if crossterm::terminal::size()? < (width as u16, height as u16) {
164            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)))
165        } else {
166            Ok(())
167        }
168    }
169
170    /// Initializes the internal components such as hiding the cursor
171    fn begin(&mut self) -> Result<(), ErrorKind> {
172        terminal::enable_raw_mode().unwrap();
173        execute!(
174            self.stdout,
175            terminal::EnterAlternateScreen,
176            terminal::Clear(ClearType::All),
177            crossterm::cursor::Hide,
178            crossterm::cursor::MoveTo(0, 0),
179            crossterm::event::EnableMouseCapture
180        )
181    }
182
183    /// Gracefully stop the engine, and set back a visible cursor
184    fn end(&mut self) {
185        execute!(
186            self.stdout,
187            crossterm::cursor::Show,
188            style::SetBackgroundColor(Color::Reset),
189            style::SetForegroundColor(Color::Reset),
190            crossterm::event::DisableMouseCapture,
191            terminal::LeaveAlternateScreen
192        )
193        .unwrap();
194        terminal::disable_raw_mode().unwrap();
195    }
196
197    /// stops the engine when a panic occurs
198    /// Similar to the end function, but without the engine instance.
199    /// So we assume we used stdout, and free it.
200    fn handle_panic(_panic_info: &std::panic::PanicInfo) {
201        execute!(
202            stdout(),
203            crossterm::cursor::Show,
204            style::SetBackgroundColor(Color::Reset),
205            style::SetForegroundColor(Color::Reset),
206            crossterm::event::DisableMouseCapture,
207            terminal::LeaveAlternateScreen
208        )
209        .unwrap();
210        terminal::disable_raw_mode().unwrap();
211    }
212
213    /// Set the terminal's title
214    pub fn set_title(&mut self, title: &str) {
215        execute!(self.stdout, crossterm::terminal::SetTitle(title)).ok();
216    }
217
218    /// Get the screen width
219    pub fn get_width(&self) -> u32 {
220        self.screen.get_width()
221    }
222
223    /// Get the screen height
224    pub fn get_height(&self) -> u32 {
225        self.screen.get_height()
226    }
227
228    /// Reset the screen to a blank state
229    pub fn clear_screen(&mut self) {
230        self.screen.clear()
231    }
232
233    /// Fill the entire screen to the given pixel
234    pub fn fill(&mut self, pixel: Pixel) {
235        self.screen.fill(pixel);
236    }
237
238    /// prints a string at the specified coordinates.
239    /// The string will be cropped if it reach the right border
240    ///
241    /// usage:
242    /// ```
243    /// engine.print(0,0, "Hello, world!");
244    /// engine.print(0, 4, format!("Score: {}", score).as_str());
245    /// ```
246    pub fn print(&mut self, x: i32, y: i32, string: &str) {
247        self.screen.print(x, y, string)
248    }
249
250    /// prints a string at the specified coordinates with the specified foreground and background color
251    /// The string will automatically overlaps if it reach the right border
252    ///
253    /// usage:
254    /// ```
255    /// use console_engine::Color;
256    ///
257    /// // print "Hello, world" in blue on white background
258    /// engine.print(0,0, "Hello, world!", Color::Blue, Color::White);
259    /// ```
260    pub fn print_fbg(&mut self, x: i32, y: i32, string: &str, fg: Color, bg: Color) {
261        self.screen.print_fbg(x, y, string, fg, bg)
262    }
263
264    /// Prints another screen on specified coordinates.
265    /// Useful when you want to manage several "subscreen"
266    ///
267    /// *see example* `screen-embed`
268    ///
269    /// usage:
270    /// ```
271    /// use console_engine::pixel;
272    /// use console_engine::screen::Screen;
273    ///
274    /// // create a new Screen struct and draw a square inside it
275    /// let mut my_square = Screen::new(8,8);
276    /// my_square.rect(0,0,7,7,pixel::pxl('#'));
277    /// my_square.print(1,1,"square");
278    ///
279    /// // prints the square in the engine's screen at a specific location
280    /// engine.print_screen(5,2, &my_square);
281    /// ```
282    pub fn print_screen(&mut self, x: i32, y: i32, source: &Screen) {
283        self.screen.print_screen(x, y, source)
284    }
285
286    /// Prints another screen on specified coordinates, ignoring a specific character while printing
287    /// Ignoring a character will behave like transparency
288    ///
289    /// see [print_screen](#method.print_screen) for usage
290    pub fn print_screen_alpha(&mut self, x: i32, y: i32, source: &Screen, alpha_character: char) {
291        self.screen
292            .print_screen_alpha(x, y, source, alpha_character)
293    }
294
295    /// draws a line of the provided character between two sets of coordinates
296    /// see: [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm)
297    ///
298    /// Note : Your line can start or end out of bounds. These pixels won't be drawn
299    ///
300    /// usage:
301    /// ```
302    /// use console_engine::pixel;
303    /// // ...
304    /// engine.line(0, 0, 9, 9, pixel::pxl('#'));
305    /// ```
306    pub fn line(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel) {
307        self.screen.line(start_x, start_y, end_x, end_y, character)
308    }
309
310    /// Draws a rectangle of the provided character between two sets of coordinates
311    ///
312    /// usage:
313    /// ```
314    /// use console_engine::pixel;
315    /// // ...
316    /// engine.rect(0, 0, 9, 9, pixel::pxl('#'));
317    /// ```
318    pub fn rect(&mut self, start_x: i32, start_y: i32, end_x: i32, end_y: i32, character: Pixel) {
319        self.screen.rect(start_x, start_y, end_x, end_y, character)
320    }
321
322    /// 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
323    ///
324    /// usage:
325    /// ```
326    /// use console_engine::rect_style::BorderStyle;
327    /// // ...
328    /// engine.rect_border(0, 0, 9, 9, BorderStyle::new_simple());
329    /// ```
330    pub fn rect_border(
331        &mut self,
332        start_x: i32,
333        start_y: i32,
334        end_x: i32,
335        end_y: i32,
336        rect_style: BorderStyle,
337    ) {
338        self.screen
339            .rect_border(start_x, start_y, end_x, end_y, rect_style)
340    }
341
342    /// Fill a rectangle of the provided character between two sets of coordinates
343    ///
344    /// usage:
345    /// ```
346    /// use console_engine::pixel;
347    /// // ...
348    /// engine.fill_rect(0, 0, 9, 9, pixel::pxl('#'));
349    /// ```
350    pub fn fill_rect(
351        &mut self,
352        start_x: i32,
353        start_y: i32,
354        end_x: i32,
355        end_y: i32,
356        character: Pixel,
357    ) {
358        self.screen
359            .fill_rect(start_x, start_y, end_x, end_y, character)
360    }
361
362    /// Draws a circle of the provided character at an x and y position with a radius
363    /// see: [olcPixelGameEngine Repository](https://github.com/OneLoneCoder/olcPixelGameEngine)
364    ///
365    /// usage:
366    /// ```
367    /// use console_engine::pixel;
368    /// // ...
369    /// engine.circle(10, 10, 4, pixel::pxl('#'));
370    /// ```
371    pub fn circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel) {
372        self.screen.circle(x, y, radius, character)
373    }
374
375    /// Fill a circle of the provided character at an x and y position with a radius
376    /// see: [olcPixelGameEngine Repository](https://github.com/OneLoneCoder/olcPixelGameEngine)
377    ///
378    /// usage:
379    /// ```
380    /// use console_engine::pixel;
381    /// // ...
382    /// engine.fill_circle(10, 10, 4, pixel::pxl('#'));
383    /// ```
384    pub fn fill_circle(&mut self, x: i32, y: i32, radius: u32, character: Pixel) {
385        self.screen.fill_circle(x, y, radius, character)
386    }
387
388    /// Draws a triangle of the provided character using three sets of coordinates
389    ///
390    /// usage:
391    /// ```
392    /// use console_engine::pixel;
393    /// // ...
394    /// engine.triangle(8,8, 4,6, 9,2, pixel::pxl('#'));
395    /// ```
396    #[allow(clippy::too_many_arguments)]
397    pub fn triangle(
398        &mut self,
399        x1: i32,
400        y1: i32,
401        x2: i32,
402        y2: i32,
403        x3: i32,
404        y3: i32,
405        character: Pixel,
406    ) {
407        self.screen.triangle(x1, y1, x2, y2, x3, y3, character)
408    }
409
410    /// Fill a triangle of the provided character using three sets of coordinates
411    /// see: [rustyPixelGameEngine Repository](https://github.com/mattbettcher/rustyPixelGameEngine)
412    ///
413    /// usage:
414    /// ```
415    /// use console_engine::pixel;
416    /// // ...
417    /// engine.fill_triangle(8,8, 4,6, 9,2, pixel::pxl('#'));
418    /// ```
419    #[allow(clippy::too_many_arguments)]
420    pub fn fill_triangle(
421        &mut self,
422        x1: i32,
423        y1: i32,
424        x2: i32,
425        y2: i32,
426        x3: i32,
427        y3: i32,
428        character: Pixel,
429    ) {
430        self.screen.fill_triangle(x1, y1, x2, y2, x3, y3, character)
431    }
432
433    /// Scrolls the screen for a certain amount of characters vertically or horizontally
434    /// Scrolling is a destructive process, the outer border will be filled with the background pixel.
435    ///
436    /// Scrolling a positive value will move the screen characters to the left / top,
437    /// freeing space to the right / bottom
438    ///
439    /// Scrolling a negative value will move the screen characters to the right / bottom,
440    /// freeing space to the left / top
441    ///
442    /// usage :
443    /// ```
444    /// use console_engine::pixel;
445    ///
446    /// // fill the screen with characters
447    /// engine.fill(pixel::pxl('#'));
448    /// // free one space to the bottom
449    /// engine.scroll(0,1,pixel::pxl(' '));
450    /// // print something at this place
451    /// engine.print(0, height-1, "Hello, world!");
452    /// ```
453    pub fn scroll(&mut self, h_scroll: i32, v_scroll: i32, background: Pixel) {
454        self.screen.scroll(h_scroll, v_scroll, background);
455    }
456
457    /// sets the provided character in the specified coordinates
458    /// out of bounds pixels will be ignored
459    ///
460    /// usage:
461    /// ```
462    /// use console_engine::pixel;
463    /// // ...
464    /// engine.set_pxl(3,8,pixel::pixel('o'));
465    /// ```
466    pub fn set_pxl(&mut self, x: i32, y: i32, character: Pixel) {
467        self.screen.set_pxl(x, y, character)
468    }
469
470    /// Get the character stored at provided coordinates
471    ///
472    /// usage:
473    /// ```
474    /// if engine.get_pxl(3,8).unwrap().chr == 'o' {
475    ///     engine.print(0,0,"Found a 'o'");
476    /// }
477    /// ```
478    pub fn get_pxl(&self, x: i32, y: i32) -> Result<Pixel, String> {
479        self.screen.get_pxl(x, y)
480    }
481
482    /// Resizes the screen to match the given width and height
483    /// truncates the bottom and right side of the screen
484    ///
485    /// usage:
486    /// ```
487    /// engine.resize(40,10)
488    /// ```
489    pub fn resize(&mut self, new_width: u32, new_height: u32) {
490        self.screen.resize(new_width, new_height);
491        self.width = new_width;
492        self.height = new_height;
493        self.screen_last_frame = Screen::new_empty(self.width, self.height);
494    }
495
496    /// Extracts part of the current screen as a separate Screen object
497    /// The original screen is not altered
498    /// If the coordinates are out of bounds, they'll be replace by the `default` pixel
499    ///
500    /// usage:
501    /// ```
502    /// use console_engine::pixel;
503    /// // extract a 3x2 screen from the engine screen
504    /// let scr_chunk = engine.extract(10, 4, 12, 5, pixel::pxl(' '));
505    /// ```
506    pub fn extract(
507        &self,
508        start_x: i32,
509        start_y: i32,
510        end_x: i32,
511        end_y: i32,
512        default: Pixel,
513    ) -> Screen {
514        self.screen.extract(start_x, start_y, end_x, end_y, default)
515    }
516
517    /// Changes the screen instance used by the engine and updates internal informations
518    ///
519    /// Useful if you want to manage multiple screens independently.
520    ///
521    /// usage
522    /// ```
523    /// // create a new screen of 40x10 and draw some things on it
524    /// let mut scr = Screen::new(40,10)
525    /// scr.rect(0,0,39,9, pixel::pxl("#"));
526    /// // ...
527    ///
528    /// // keep a backup of the old screen before replacing it
529    /// let old_scr = engine.get_screen();
530    /// // change the engine's current screen to the newly created one
531    /// engine.set_screen(&scr);
532    ///
533    /// // ... later
534    /// // set back the old screen
535    /// engine.set_screen(&old_scr);
536    /// ```
537    pub fn set_screen(&mut self, screen: &Screen) {
538        self.width = screen.get_width();
539        self.height = screen.get_height();
540        self.screen = screen.clone();
541        self.request_full_draw();
542    }
543
544    /// Returns a clone of the current screen
545    ///
546    /// You can keep it into a variable to restore the screen later, via `set_screen`.
547    /// You can then use the to_string method to write the screen in a file for example
548    ///
549    /// see [set_screen](#method.set_screen) for a more complete example
550    ///
551    /// usage :
552    /// ```
553    /// let scr = engine.get_screen();
554    /// ```
555    pub fn get_screen(&self) -> Screen {
556        self.screen.clone()
557    }
558
559    /// Draw the screen in the terminal
560    /// For best results, use it once per frame
561    ///
562    /// 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.
563    /// 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()`.
564    ///
565    /// That's because for optimizing the output speed, the draw function only draw the difference between each frames.
566    ///
567    /// usage:
568    /// ```
569    /// engine.print(0,0,"Hello, world!"); // <- prints "Hello, world!" in 'screen' memory
570    /// engine.draw(); // display 'screen' memory to the user's terminal
571    /// ```
572    pub fn draw(&mut self) {
573        // we use the queue! macro to store in one-shot the screen we'll write.
574        // This is an optimization because we write all we need once instead of writing small bit of screen by small bit of screen.
575        // Actually, this does not change much for Linux terminals (like 5 fps gained from this)
576        // But for windows terminal we can see huge improvements (example lines-fps goes from 35-40 fps to 65-70 for a 100x50 term)
577        // reset cursor position
578        queue!(self.stdout, crossterm::cursor::MoveTo(0, 0)).unwrap();
579        let mut first = true;
580        let mut current_colors: (Color, Color) = (Color::Reset, Color::Reset);
581        let mut moving = false;
582        self.screen_last_frame.check_empty(); // refresh internal "empty" value of the last_frame screen
583        let mut skip_next = false;
584
585        // iterates through the screen memory and prints it on the output buffer
586        for y in 0..self.height as i32 {
587            for x in 0..self.width as i32 {
588                let pixel = self.screen.get_pxl(x, y).unwrap();
589                // we check if the screen has been modified at this coordinate or if the last_frame screen is empty
590                // if so, we write on the terminal normally, else we set a 'moving' flag
591                if skip_next {
592                    skip_next = false;
593                    continue;
594                }
595                if let Some(char_width) = unicode_width::UnicodeWidthChar::width(pixel.chr) {
596                    if char_width > 1 {
597                        skip_next = true;
598                    }
599                }
600                if self.screen_last_frame.is_empty()
601                    || pixel != self.screen_last_frame.get_pxl(x, y).unwrap()
602                {
603                    if moving {
604                        // if the moving flag is set, we need to write a goto instruction first
605                        // this optimization minimize useless write on the screen
606                        // actually writing to the screen is very slow so it's a good compromise
607                        queue!(self.stdout, crossterm::cursor::MoveTo(x as u16, y as u16)).unwrap();
608                        moving = false;
609                    }
610                    // we check if the last color is the same as the current one.
611                    // if the color is the same, only print the character
612                    // the less we write on the output the faster we'll get
613                    // and additional characters for colors we already have set is
614                    // time consuming
615                    if current_colors != pixel.get_colors() || first {
616                        current_colors = pixel.get_colors();
617                        queue!(
618                            self.stdout,
619                            style::SetForegroundColor(pixel.fg),
620                            style::SetBackgroundColor(pixel.bg),
621                            style::Print(pixel.chr)
622                        )
623                        .unwrap();
624                        first = false;
625                    } else {
626                        queue!(self.stdout, style::Print(pixel.chr)).unwrap();
627                    }
628                } else {
629                    moving = true
630                }
631            }
632            // at the end of each line, we write a newline character
633            // I believe that since we're on raw mode we need CR and LF even on unix terminals
634            if y < self.height as i32 - 1 {
635                queue!(self.stdout, style::Print("\r\n")).unwrap();
636            }
637        }
638        // flush the buffer into user's terminal
639        self.stdout.flush().unwrap();
640        // store the frame for the next draw call
641        self.screen_last_frame = self.screen.clone();
642    }
643
644    /// Ask the engine to redraw the entire screen on the next `draw` call
645    /// Useful if the terminal's content got altered outside of the `draw` function.
646    ///
647    /// See [draw](#method.draw) for more info about the drawing process
648    pub fn request_full_draw(&mut self) {
649        // reset the last_frame screen to force a full redraw
650        self.screen_last_frame = Screen::new_empty(self.width, self.height);
651    }
652
653    /// Pause the execution until the next frame need to be rendered
654    /// Internally gets user's input for the next frame
655    ///
656    /// usage:
657    /// ```
658    /// // initializes a screen with a 10x10 screen and targetting 30 fps
659    /// let mut engine = console_engine::ConsoleEngine::init(10, 10, 30);
660    /// loop {
661    ///     engine.wait_frame(); // wait for next frame
662    ///     // do your stuff
663    /// }
664    /// ```
665    pub fn wait_frame(&mut self) {
666        let mut captured_keyboard: Vec<KeyEvent> = vec![];
667        let mut captured_mouse: Vec<MouseEvent> = vec![];
668        let mut captured_resize: Vec<(u16, u16)> = vec![];
669
670        // if there is time before next frame, poll keyboard and mouse events until next frame
671        let mut elapsed_time = self.instant.elapsed();
672        while self.time_limit > elapsed_time {
673            let remaining_time = self.time_limit - elapsed_time;
674            if let Ok(has_event) = event::poll(std::time::Duration::from_millis(
675                (remaining_time.as_millis() % self.time_limit.as_millis()) as u64,
676            )) {
677                if has_event {
678                    if let Ok(current_event) = event::read() {
679                        match current_event {
680                            Event::Key(evt) => {
681                                captured_keyboard.push(evt);
682                            }
683                            Event::Mouse(evt) => {
684                                captured_mouse.push(evt);
685                            }
686                            Event::Resize(w, h) => {
687                                captured_resize.push((w, h));
688                            }
689                            Event::FocusGained => (),
690                            Event::FocusLost => (),
691                            Event::Paste(_) => (),
692                        };
693                    }
694                }
695            }
696            elapsed_time = self.instant.elapsed();
697        }
698        self.instant = std::time::Instant::now();
699        self.frame_count = self.frame_count.wrapping_add(1);
700
701        // updates pressed / held / released states
702        let held = utils::intersect(
703            &utils::union(&self.keys_pressed, &self.keys_held),
704            &captured_keyboard,
705        );
706        self.keys_released = utils::outersect_left(&self.keys_held, &held);
707        self.keys_pressed = utils::outersect_left(&captured_keyboard, &held);
708        self.keys_held = utils::union(&held, &self.keys_pressed);
709        self.mouse_events = captured_mouse;
710        self.resize_events = captured_resize;
711    }
712
713    /// Poll the next ConsoleEngine Event
714    /// This function waits for the next event to occur,
715    /// from a user event like key press or mouse click to automatic events like frame change
716    ///
717    /// usage:
718    /// ```
719    /// use console_engine::events::Event;
720    /// // initializes a screen with a 10x10 screen and targetting 30 fps
721    /// let mut engine = console_engine::ConsoleEngine::init(10, 10, 30).unwrap();
722    /// loop {
723    ///     match engine.poll() {
724    ///         Event::Frame => {
725    ///             // do things
726    ///             engine.draw();
727    ///         }
728    ///         Event::Key(key_event) => {
729    ///             // handle keys
730    ///         }
731    ///     }
732    /// }
733    /// ```
734    #[cfg(feature = "event")]
735    pub fn poll(&mut self) -> events::Event {
736        use std::time::Duration;
737
738        let mut elapsed_time = self.instant.elapsed();
739        // guarantees that this loop is running at least once
740        loop {
741            let remaining_time = if self.time_limit > elapsed_time {
742                self.time_limit - elapsed_time
743            } else {
744                Duration::from_millis(0)
745            };
746            if let Ok(has_event) = event::poll(std::time::Duration::from_millis(
747                (remaining_time.as_millis() % self.time_limit.as_millis()) as u64,
748            )) {
749                if has_event {
750                    if let Ok(current_event) = event::read() {
751                        match current_event {
752                            Event::Key(evt) => return events::Event::Key(evt),
753                            Event::Mouse(evt) => return events::Event::Mouse(evt),
754                            Event::Resize(w, h) => return events::Event::Resize(w, h),
755                            Event::FocusGained => (),
756                            Event::FocusLost => (),
757                            Event::Paste(_) => (),
758                        };
759                    }
760                }
761            }
762            elapsed_time = self.instant.elapsed();
763            if self.time_limit <= elapsed_time {
764                break;
765            }
766        }
767        self.instant = std::time::Instant::now();
768        self.frame_count = self.frame_count.wrapping_add(1);
769        events::Event::Frame
770    }
771
772    /// Check and resize the terminal if needed.
773    /// Note that the resize will occur but there is no check yet if the terminal
774    /// is smaller than the required size provided in the init() function.
775    ///
776    /// usage:
777    /// ```
778    /// // initializes a screen filling the terminal
779    /// let mut engine = console_engine::ConsoleEngine::init_fill(30);
780    /// loop {
781    ///     engine.wait_frame(); // wait for next frame
782    ///     engine.check_resize(); // resize the terminal if its size has changed
783    ///     // do your stuff
784    /// }
785    /// ```
786    pub fn check_resize(&mut self) {
787        if crossterm::terminal::size().unwrap() != (self.width as u16, self.height as u16) {
788            // resize terminal
789            let size = crossterm::terminal::size().unwrap();
790            let new_width = size.0 as u32;
791            let new_height = size.1 as u32;
792
793            self.resize(new_width, new_height);
794        }
795    }
796
797    /// checks whenever a key is pressed (first frame held only)
798    ///
799    /// usage:
800    /// ```
801    /// use console_engine::KeyCode;
802    ///
803    /// loop {
804    ///     engine.wait_frame(); // wait for next frame + captures input
805    ///
806    ///     if engine.is_key_pressed(KeyCode::Char('q')) {
807    ///         break; // exits app
808    ///     }
809    /// }
810    /// ```
811    pub fn is_key_pressed(&self, key: KeyCode) -> bool {
812        self.is_key_pressed_with_modifier(key, KeyModifiers::NONE, KeyEventKind::Press)
813    }
814
815    /// checks whenever a key + a modifier (ctrl, shift...) is pressed (first frame held only)
816    ///
817    /// usage:
818    /// ```
819    /// use console_engine::{KeyCode, KeyModifiers}
820    ///
821    /// loop {
822    ///     engine.wait_frame(); // wait for next frame + captures input
823    ///
824    ///     if engine.is_key_pressed_with_modifier(KeyCode::Char('c'), KeyModifiers::CONTROL) {
825    ///         break; // exits app
826    ///     }
827    /// }
828    /// ```
829    pub fn is_key_pressed_with_modifier(
830        &self,
831        key: KeyCode,
832        modifier: KeyModifiers,
833        kind: KeyEventKind,
834    ) -> bool {
835        self.keys_pressed
836            .contains(&KeyEvent::new_with_kind(key, modifier, kind))
837    }
838
839    /// checks whenever a key is held down
840    ///
841    /// usage:
842    /// ```
843    /// use console_engine::KeyCode;
844    ///
845    /// loop {
846    ///     engine.wait_frame(); // wait for next frame + captures input
847    ///
848    ///     if engine.is_key_held(KeyCode::Char('8')) && pos_y > 0 {
849    ///         pos_y -= 1; // move position upward
850    ///     }
851    /// }
852    /// ```
853    pub fn is_key_held(&self, key: KeyCode) -> bool {
854        self.is_key_held_with_modifier(key, KeyModifiers::NONE, KeyEventKind::Press)
855    }
856
857    /// checks whenever a key + a modifier (ctrl, shift...) is held down
858    pub fn is_key_held_with_modifier(
859        &self,
860        key: KeyCode,
861        modifier: KeyModifiers,
862        kind: KeyEventKind,
863    ) -> bool {
864        self.keys_held
865            .contains(&KeyEvent::new_with_kind(key, modifier, kind))
866    }
867
868    /// checks whenever a key has been released (first frame released)
869    ///
870    /// usage:
871    /// ```
872    /// use console_engine::KeyCode;
873    ///
874    /// if engine.is_key_held(KeyCode::Char('h')) {
875    ///     engine.clear_screen();
876    ///     engine.print(0,0,"Please don't hold this button.");
877    ///     engine.draw();
878    ///     while !engine.is_key_released(KeyCode::Char('h')) {
879    ///         engine.wait_frame(); // refresh button's states
880    ///     }
881    /// }
882    /// ```
883    pub fn is_key_released(&self, key: KeyCode) -> bool {
884        self.is_key_released_with_modifier(key, KeyModifiers::NONE, KeyEventKind::Release)
885    }
886
887    /// checks whenever a key + a modifier (ctrl, shift...) has been released (first frame released)
888    pub fn is_key_released_with_modifier(
889        &self,
890        key: KeyCode,
891        modifier: KeyModifiers,
892        kind: KeyEventKind,
893    ) -> bool {
894        self.keys_released
895            .contains(&KeyEvent::new_with_kind(key, modifier, kind))
896    }
897
898    /// Give the mouse's terminal coordinates if the provided button has been pressed
899    ///
900    /// usage:
901    /// ```
902    /// use console_engine::MouseButton;
903    ///
904    /// // prints a 'P' where the mouse's left button has been pressed
905    /// let mouse_pos = engine.get_mouse_press(MouseButton::Left);
906    /// if let Some(mouse_pos) = mouse_pos {
907    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('P'));
908    /// }
909    /// ```
910    ///
911    pub fn get_mouse_press(&self, button: MouseButton) -> Option<(u32, u32)> {
912        self.get_mouse_press_with_modifier(button, KeyModifiers::NONE)
913    }
914
915    /// Give the mouse's terminal coordinates if the provided button + modifier (ctrl, shift, ...) has been pressed
916    pub fn get_mouse_press_with_modifier(
917        &self,
918        button: MouseButton,
919        modifier: KeyModifiers,
920    ) -> Option<(u32, u32)> {
921        for evt in self.mouse_events.iter() {
922            if let MouseEventKind::Down(mouse) = evt.kind {
923                if mouse == button && evt.modifiers == modifier {
924                    return Some((evt.column as u32, evt.row as u32));
925                }
926            };
927        }
928        None
929    }
930
931    /// Give the terminal resize event
932    ///
933    /// usage:
934    /// ```
935    /// if let Some((width, height)) = engine.get_resize() {
936    ///     // do something
937    /// }
938    /// ```
939    pub fn get_resize(&self) -> Option<(u16, u16)> {
940        for evt in self.resize_events.iter() {
941            if let Event::Resize(w, h) = Event::Resize(evt.0, evt.1) {
942                return Some((w, h));
943            };
944        }
945        None
946    }
947
948    /// Give the mouse's terminal coordinates if a button is held on the mouse
949    ///
950    /// usage:
951    /// ```
952    /// use console_engine::MouseButton;
953    ///
954    /// // prints a 'H' where the mouse is currently held
955    /// let mouse_pos = engine.get_mouse_held(MouseButton::Left);
956    /// if let Some(mouse_pos) = mouse_pos {
957    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('H'));
958    /// }
959    /// ```
960    pub fn get_mouse_held(&self, button: MouseButton) -> Option<(u32, u32)> {
961        self.get_mouse_held_with_modifier(button, KeyModifiers::NONE)
962    }
963
964    /// Give the mouse's terminal coordinates if a button + modifier (ctrl, shift, ...) is held on the mouse
965    pub fn get_mouse_held_with_modifier(
966        &self,
967        button: MouseButton,
968        modifier: KeyModifiers,
969    ) -> Option<(u32, u32)> {
970        for evt in self.mouse_events.iter() {
971            if let MouseEventKind::Drag(mouse) = evt.kind {
972                if mouse == button && evt.modifiers == modifier {
973                    return Some((evt.column as u32, evt.row as u32));
974                }
975            };
976        }
977        None
978    }
979
980    /// Give the mouse's terminal coordinates if a button has been released on the mouse
981    ///
982    /// usage:
983    /// ```
984    /// use console_engine::MouseButton;
985    ///
986    /// // prints a 'R' where the mouse has been released
987    /// let mouse_pos = engine.get_mouse_released(MouseButton::Left);
988    /// if let Some(mouse_pos) = mouse_pos {
989    ///     engine.set_pxl(mouse_pos.0 as i32, mouse_pos.1 as i32, pixel::pxl('R'));
990    /// }
991    /// ```
992    pub fn get_mouse_released(&self, button: MouseButton) -> Option<(u32, u32)> {
993        self.get_mouse_released_with_modifier(button, KeyModifiers::NONE)
994    }
995
996    /// Give the mouse's terminal coordinates if a button + modifier (ctrl, shift, ...) has been released on the mouse
997    pub fn get_mouse_released_with_modifier(
998        &self,
999        button: MouseButton,
1000        modifier: KeyModifiers,
1001    ) -> Option<(u32, u32)> {
1002        for evt in self.mouse_events.iter() {
1003            if let MouseEventKind::Up(mouse) = evt.kind {
1004                if mouse == button && evt.modifiers == modifier {
1005                    return Some((evt.column as u32, evt.row as u32));
1006                }
1007            };
1008        }
1009        None
1010    }
1011
1012    /// checks whenever the mouse's scroll has been turned down, towards the user
1013    ///
1014    /// usage:
1015    /// ```
1016    /// if engine.is_mouse_scrolled_down() {
1017    ///     // do some scrolling logic
1018    /// }
1019    /// ```
1020    pub fn is_mouse_scrolled_down(&self) -> bool {
1021        self.is_mouse_scrolled_down_with_modifier(KeyModifiers::NONE)
1022    }
1023
1024    /// checks whenever the mouse's scroll has been turned down, towards the user with a modifier (ctrl, shift, ...)
1025    pub fn is_mouse_scrolled_down_with_modifier(&self, modifier: KeyModifiers) -> bool {
1026        for evt in self.mouse_events.iter() {
1027            if let MouseEventKind::ScrollDown = evt.kind {
1028                if evt.modifiers == modifier {
1029                    return true;
1030                }
1031            };
1032        }
1033        false
1034    }
1035
1036    /// checks whenever the mouse's scroll has been turned up, away from the user
1037    ///
1038    /// usage:
1039    /// ```
1040    /// if engine.is_mouse_scrolled_up() {
1041    ///     // do some scrolling logic
1042    /// }
1043    /// ```
1044    pub fn is_mouse_scrolled_up(&self) -> bool {
1045        self.is_mouse_scrolled_up_with_modifier(KeyModifiers::NONE)
1046    }
1047
1048    /// checks whenever the mouse's scroll has been turned up, away from the user with a modifier (ctrl, shift, ...)
1049    pub fn is_mouse_scrolled_up_with_modifier(&self, modifier: KeyModifiers) -> bool {
1050        for evt in self.mouse_events.iter() {
1051            if let MouseEventKind::ScrollUp = evt.kind {
1052                if evt.modifiers == modifier {
1053                    return true;
1054                }
1055            };
1056        }
1057        false
1058    }
1059}
1060
1061impl Drop for ConsoleEngine {
1062    /// gracefully stop the engine when dropping it
1063    fn drop(&mut self) {
1064        self.end();
1065    }
1066}