bubbles/
app.rs

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            // Delay b/w frames
59            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                        // Event::FocusGained => todo!(),
75                        // Event::FocusLost => todo!(),
76                        // Event::Mouse(_) => todo!(),
77                        // Event::Paste(_) => todo!(),
78                        // Event::Resize(_, _) => todo!(),
79                        _ => (),
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}