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}