Skip to main content

hac_client/
app.rs

1use hac_core::{collection::Collection, command::Command};
2
3use crate::event_pool::{Event, EventPool};
4use crate::pages::{Eventful, Renderable};
5use crate::screen_manager::ScreenManager;
6
7use std::io::Stdout;
8
9use ratatui::{backend::CrosstermBackend, Terminal};
10use tokio::sync::mpsc;
11
12pub struct App<'app> {
13    event_pool: EventPool,
14    terminal: Terminal<CrosstermBackend<Stdout>>,
15    should_quit: bool,
16    screen_manager: ScreenManager<'app>,
17}
18
19impl<'app> App<'app> {
20    pub fn new(
21        colors: &'app hac_colors::Colors,
22        collections: Vec<Collection>,
23        config: &'app hac_config::Config,
24        dry_run: bool,
25    ) -> anyhow::Result<Self> {
26        let terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
27        Ok(Self {
28            screen_manager: ScreenManager::new(
29                terminal.size()?,
30                colors,
31                collections,
32                config,
33                dry_run,
34            )?,
35            event_pool: EventPool::new(60f64, 30f64),
36            should_quit: false,
37            terminal,
38        })
39    }
40
41    /// this is the main method which starts the event loop task, listen for events and commands
42    /// to pass them down the chain, and render the terminal screen
43    pub async fn run(&mut self) -> anyhow::Result<()> {
44        let (command_tx, mut command_rx) = mpsc::unbounded_channel();
45        self.event_pool.start();
46
47        startup()?;
48
49        self.screen_manager
50            .register_command_handler(command_tx.clone())?;
51
52        loop {
53            {
54                while let Ok(command) = command_rx.try_recv() {
55                    match command {
56                        Command::Quit => self.should_quit = true,
57                        _ => self.screen_manager.handle_command(command),
58                    }
59                }
60            }
61
62            if let Some(event) = self.event_pool.next().await {
63                match event {
64                    Event::Tick => self.screen_manager.handle_tick()?,
65                    Event::Resize(new_size) => self.screen_manager.resize(new_size),
66                    Event::Render => {
67                        self.terminal.draw(|f| {
68                            let result = self.screen_manager.draw(f, f.size());
69                            if let Err(e) = result {
70                                command_tx
71                                    .send(Command::Error(format!("Failed to draw: {:?}", e)))
72                                    .expect("failed to send command through channel");
73                            }
74                        })?;
75                    }
76                    event => {
77                        if let Some(command) =
78                            self.screen_manager.handle_event(Some(event.clone()))?
79                        {
80                            command_tx
81                                .send(command)
82                                .expect("failed to send command through channel")
83                        }
84                    }
85                };
86            }
87
88            if self.should_quit {
89                break;
90            }
91        }
92
93        shutdown()?;
94        Ok(())
95    }
96}
97
98/// before initializing the app, we must setup the terminal to enable all the features
99/// we need, such as raw mode and entering the alternate screen
100fn startup() -> anyhow::Result<()> {
101    crossterm::terminal::enable_raw_mode()?;
102    crossterm::execute!(std::io::stdout(), crossterm::terminal::EnterAlternateScreen)?;
103    Ok(())
104}
105
106/// before shutting down we must reverse the changes we made to the users terminal, allowing
107/// them have a usable terminal
108fn shutdown() -> anyhow::Result<()> {
109    crossterm::terminal::disable_raw_mode()?;
110    crossterm::execute!(std::io::stdout(), crossterm::terminal::LeaveAlternateScreen)?;
111    Ok(())
112}