My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
1use color_eyre::eyre::Result;
2use crossterm::event::KeyEvent;
3use ratatui::layout::Rect;
4use serde::{Deserialize, Serialize};
5use strum::{Display, EnumIter};
6use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
7use tracing::debug;
8
9use crate::{
10 components::Component,
11 config::Config,
12 signal::Signal,
13 tui::{Event, Tui},
14};
15
16pub struct App {
17 config: Config,
18 tick_rate: f64,
19 frame_rate: f64,
20 components: Vec<Box<dyn Component>>,
21 should_quit: bool,
22 should_suspend: bool,
23 #[allow(dead_code)]
24 region: Region,
25 last_tick_key_events: Vec<KeyEvent>,
26 signal_tx: UnboundedSender<Signal>,
27 signal_rx: UnboundedReceiver<Signal>,
28}
29
30/// The different regions of the application that the user can
31/// be interacting with. Think of these kind of like the highest class of
32/// components.
33#[derive(
34 Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display,
35)]
36pub enum Region {
37 #[default]
38 Home,
39}
40
41impl App {
42 /// Construct a new `App` instance.
43 pub fn new(tick_rate: f64, frame_rate: f64) -> Self {
44 let (signal_tx, signal_rx) = mpsc::unbounded_channel();
45
46 Self {
47 tick_rate,
48 frame_rate,
49 components: vec![],
50 should_quit: false,
51 should_suspend: false,
52 config: Config::new(),
53 region: Region::default(),
54 last_tick_key_events: Vec::new(),
55 signal_tx,
56 signal_rx,
57 }
58 }
59
60 pub async fn run(&mut self) -> Result<()> {
61 let mut tui = Tui::new()?
62 .with_tick_rate(self.tick_rate)
63 .with_frame_rate(self.frame_rate);
64 tui.enter()?;
65
66 for component in &mut self.components {
67 component.register_signal_handler(self.signal_tx.clone())?;
68 }
69 for component in &mut self.components {
70 component.register_config_handler(self.config.clone())?;
71 }
72
73 for component in &mut self.components {
74 component.init(tui.size()?)?;
75 }
76
77 let signal_tx = self.signal_tx.clone();
78
79 loop {
80 self.handle_events(&mut tui).await?;
81 self.handle_signals(&mut tui)?;
82 if self.should_suspend {
83 tui.suspend()?;
84
85 // We are sending resume here because once its done suspending,
86 // it will continue execution here.
87 signal_tx.send(Signal::Resume)?;
88 signal_tx.send(Signal::ClearScreen)?;
89 tui.enter()?;
90 } else if self.should_quit {
91 tui.stop();
92 break;
93 }
94 }
95
96 tui.exit()?;
97
98 Ok(())
99 }
100
101 async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
102 let Some(event) = tui.next_event().await else {
103 return Ok(());
104 };
105
106 debug!("received event: {event:?}");
107
108 let signal_tx = self.signal_tx.clone();
109
110 match event {
111 Event::Quit => signal_tx.send(Signal::Quit)?,
112 Event::Tick => signal_tx.send(Signal::Tick)?,
113 Event::Render => signal_tx.send(Signal::Render)?,
114 Event::Resize(x, y) => signal_tx.send(Signal::Resize(x, y))?,
115 Event::Key(key) => self.handle_key_event(key)?,
116
117 _ => {}
118 }
119
120 for component in &mut self.components {
121 if let Some(signal) = component.handle_events(Some(event.clone()))? {
122 signal_tx.send(signal)?;
123 }
124 }
125
126 Ok(())
127 }
128
129 fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
130 debug!("key received: {key:#?}");
131
132 let signal_tx = self.signal_tx.clone();
133
134 let Some(region_keymap) = self.config.keymap.get(&self.region) else {
135 return Ok(());
136 };
137
138 if let Some(signal) = region_keymap.get(&vec![key]) {
139 signal_tx.send(signal.clone())?;
140 } else {
141 self.last_tick_key_events.push(key);
142 if let Some(signal) = region_keymap.get(&self.last_tick_key_events) {
143 debug!("Got signal: {signal:?}");
144 signal_tx.send(signal.clone())?;
145 }
146 }
147
148 Ok(())
149 }
150
151 fn handle_signals(&mut self, tui: &mut Tui) -> Result<()> {
152 while let Ok(signal) = self.signal_rx.try_recv() {
153 if signal != Signal::Tick && signal != Signal::Render {
154 debug!("handling signal: {signal:?}");
155 }
156
157 match signal {
158 Signal::Tick => {
159 self.last_tick_key_events.drain(..);
160 }
161
162 Signal::Quit => self.should_quit = true,
163
164 Signal::Suspend => self.should_suspend = true,
165
166 Signal::Resume => self.should_suspend = false,
167
168 Signal::ClearScreen => tui.terminal.clear()?,
169 Signal::Resize(x, y) => self.handle_resize(tui, x, y)?,
170 Signal::Render => self.render(tui)?,
171 _ => {}
172 }
173
174 for component in &mut self.components {
175 if let Some(signal) = component.update(signal.clone())? {
176 self.signal_tx.send(signal)?;
177 }
178 }
179 }
180 Ok(())
181 }
182
183 fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> {
184 tui.resize(Rect::new(0, 0, w, h))?;
185
186 self.render(tui)?;
187 Ok(())
188 }
189
190 fn render(&mut self, tui: &mut Tui) -> Result<()> {
191 tui.draw(|frame| {
192 for component in &mut self.components {
193 if let Err(err) = component.draw(frame, frame.area()) {
194 let _ = self
195 .signal_tx
196 .send(Signal::Error(format!("Failed to draw: {err:?}")));
197 }
198 }
199 })?;
200
201 Ok(())
202 }
203}