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 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
98fn startup() -> anyhow::Result<()> {
101 crossterm::terminal::enable_raw_mode()?;
102 crossterm::execute!(std::io::stdout(), crossterm::terminal::EnterAlternateScreen)?;
103 Ok(())
104}
105
106fn shutdown() -> anyhow::Result<()> {
109 crossterm::terminal::disable_raw_mode()?;
110 crossterm::execute!(std::io::stdout(), crossterm::terminal::LeaveAlternateScreen)?;
111 Ok(())
112}