1use crossterm::{
2 cursor, event,
3 event::{DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers},
4 execute,
5 style::{self, Stylize},
6 terminal::{self, disable_raw_mode, enable_raw_mode, LeaveAlternateScreen},
7 QueueableCommand, Result,
8};
9use std::{
10 io::{stdout as std_init, Stdout, Write},
11 time::{Duration, Instant},
12};
13
14use crate::core::component::Component;
15
16pub struct App {
17 pub fps: u8,
18 components: Vec<Box<dyn Component>>,
19}
20
21impl App {
22 pub fn new() -> Self {
23 Self {
24 fps: 60,
25 components: vec![],
26 }
27 }
28
29 pub fn set_fps(&mut self, fps: u8) -> &mut Self {
30 self.fps = fps;
31
32 self
33 }
34
35 pub fn run(&mut self) -> Result<()> {
36 let mut stdout = std_init();
37
38 enable_raw_mode()?;
39
40 execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
41 execute!(stdout, LeaveAlternateScreen, EnableMouseCapture)?;
42
43 self.app_loop(&mut stdout)?;
44
45 disable_raw_mode()?;
46 execute!(stdout, LeaveAlternateScreen, DisableMouseCapture)?;
47
48 Ok(())
49 }
50
51 fn app_loop(&mut self, stdout: &mut Stdout) -> Result<()> {
52 let mut tick = Instant::now();
53 let tick_rate = Duration::from_millis((1 / self.fps).into());
54
55 loop {
56 self.render(stdout, None)?;
57
58 let timeout = tick_rate
60 .checked_sub(tick.elapsed())
61 .unwrap_or_else(|| Duration::from_secs(0));
62
63 if event::poll(timeout)? {
64 match event::read()? {
65 event => match event {
66 Event::Key(KeyEvent {
67 modifiers: KeyModifiers::CONTROL,
68 code: KeyCode::Char('c'),
69 ..
70 }) => return Ok(()),
71 Event::Key(event) => {
72 self.render(stdout, Some(event))?;
73 }
74 _ => (),
80 },
81 };
82 }
83
84 if tick.elapsed() >= tick_rate {
85 tick = Instant::now();
86 }
87 }
88 }
89
90 fn render(&mut self, stdout: &mut Stdout, key_event: Option<KeyEvent>) -> Result<()> {
91 for component in &mut self.components {
92 component.handle_render();
93
94 if let Some(key_e) = key_event {
95 component.handle_key_event(key_e);
96 }
97
98 let view = component.render();
99
100 let mut content = view.content.stylize().with(view.color);
101
102 content.as_mut().background_color = Some(view.background);
103
104 stdout
105 .queue(cursor::MoveTo(view.dimension.x, view.dimension.y))?
106 .queue(style::PrintStyledContent(content))?;
107 }
108
109 stdout.flush()?;
110
111 Ok(())
112 }
113
114 pub fn register(&mut self, c: Box<dyn Component>) {
115 self.components.push(c);
116 }
117}