+196
-3
Diff
round #0
+192
src/app.rs
+192
src/app.rs
···
1
+
use color_eyre::eyre::Result;
2
+
use crossterm::event::KeyEvent;
3
+
use ratatui::layout::Rect;
4
+
use serde::{Deserialize, Serialize};
5
+
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
6
+
use tracing::{debug, info};
7
+
8
+
use crate::{
9
+
components::Component,
10
+
config::Config,
11
+
signal::Signal,
12
+
tui::{Event, Tui},
13
+
};
14
+
15
+
pub struct App {
16
+
config: Config,
17
+
tick_rate: f64,
18
+
frame_rate: f64,
19
+
components: Vec<Box<dyn Component>>,
20
+
should_quit: bool,
21
+
should_suspend: bool,
22
+
#[allow(dead_code)]
23
+
region: Region,
24
+
last_tick_key_events: Vec<KeyEvent>,
25
+
signal_tx: UnboundedSender<Signal>,
26
+
signal_rx: UnboundedReceiver<Signal>,
27
+
}
28
+
29
+
/// The different regions of the application that the user can
30
+
/// be interacting with. Think of these kind of like the highest class of
31
+
/// components.
32
+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33
+
pub enum Region {
34
+
#[default]
35
+
Home,
36
+
}
37
+
38
+
#[expect(dead_code)]
39
+
impl App {
40
+
/// Construct a new `App` instance.
41
+
pub fn new(tick_rate: f64, frame_rate: f64) -> Self {
42
+
let (signal_tx, signal_rx) = mpsc::unbounded_channel();
43
+
44
+
Self {
45
+
tick_rate,
46
+
frame_rate,
47
+
components: vec![],
48
+
should_quit: false,
49
+
should_suspend: false,
50
+
config: Config::new(),
51
+
region: Region::default(),
52
+
last_tick_key_events: Vec::new(),
53
+
signal_tx,
54
+
signal_rx,
55
+
}
56
+
}
57
+
58
+
pub async fn run(&mut self) -> Result<()> {
59
+
let mut tui = Tui::new()?
60
+
.with_tick_rate(self.tick_rate)
61
+
.with_frame_rate(self.frame_rate);
62
+
tui.enter()?;
63
+
64
+
for component in &mut self.components {
65
+
component.register_signal_handler(self.signal_tx.clone())?;
66
+
}
67
+
for component in &mut self.components {
68
+
component.register_config_handler(self.config.clone())?;
69
+
}
70
+
71
+
for component in &mut self.components {
72
+
component.init(tui.size()?)?;
73
+
}
74
+
75
+
let signal_tx = self.signal_tx.clone();
76
+
77
+
loop {
78
+
self.handle_events(&mut tui).await?;
79
+
80
+
self.handle_signals(&mut tui).await?;
81
+
if self.should_suspend {
82
+
tui.suspend()?;
83
+
84
+
// We are sending resume here because once its done suspending,
85
+
// it will continue execution here.
86
+
signal_tx.send(Signal::Resume)?;
87
+
signal_tx.send(Signal::ClearScreen)?;
88
+
tui.enter()?;
89
+
} else if self.should_quit {
90
+
tui.stop();
91
+
break;
92
+
}
93
+
}
94
+
95
+
tui.exit()?;
96
+
97
+
Ok(())
98
+
}
99
+
100
+
async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
101
+
let Some(event) = tui.next_event().await else {
102
+
return Ok(());
103
+
};
104
+
105
+
let signal_tx = self.signal_tx.clone();
106
+
107
+
match event {
108
+
Event::Quit => signal_tx.send(Signal::Quit)?,
109
+
Event::Tick => signal_tx.send(Signal::Tick)?,
110
+
Event::Render => signal_tx.send(Signal::Render)?,
111
+
Event::Resize(x, y) => signal_tx.send(Signal::Resize(x, y))?,
112
+
Event::Key(key) => self.handle_key_event(key)?,
113
+
114
+
_ => {}
115
+
}
116
+
117
+
for component in &mut self.components {
118
+
if let Some(signal) = component.handle_events(Some(event.clone()))? {
119
+
signal_tx.send(signal)?;
120
+
}
121
+
}
122
+
123
+
Ok(())
124
+
}
125
+
126
+
// We are okay with this because we know that this is the function signature,
127
+
// we just haven't implemented the keyboard parsing logic just yet, revisit
128
+
// this later.
129
+
//
130
+
// DO NOT LET THIS MERGE INTO MAIN WITH THIS CLIPPY IGNORES
131
+
#[allow(clippy::needless_pass_by_ref_mut, clippy::unnecessary_wraps)]
132
+
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
133
+
let _signal_tx = self.signal_tx.clone();
134
+
135
+
info!("key received: {key:#?}");
136
+
137
+
Ok(())
138
+
}
139
+
140
+
async fn handle_signals(&mut self, tui: &mut Tui) -> Result<()> {
141
+
while let Some(signal) = self.signal_rx.recv().await {
142
+
if signal != Signal::Tick && signal != Signal::Render {
143
+
debug!("App: handling signal: {signal:?}");
144
+
}
145
+
146
+
match signal {
147
+
Signal::Tick => {
148
+
self.last_tick_key_events.drain(..);
149
+
}
150
+
151
+
Signal::Quit => self.should_quit = true,
152
+
153
+
Signal::Suspend => self.should_suspend = true,
154
+
155
+
Signal::Resume => self.should_suspend = false,
156
+
157
+
Signal::ClearScreen => tui.terminal.clear()?,
158
+
Signal::Resize(x, y) => self.handle_resize(tui, x, y)?,
159
+
Signal::Render => self.render(tui)?,
160
+
_ => {}
161
+
}
162
+
163
+
for component in &mut self.components {
164
+
if let Some(signal) = component.update(signal.clone())? {
165
+
self.signal_tx.send(signal)?;
166
+
}
167
+
}
168
+
}
169
+
Ok(())
170
+
}
171
+
172
+
fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> {
173
+
tui.resize(Rect::new(0, 0, w, h))?;
174
+
175
+
self.render(tui)?;
176
+
Ok(())
177
+
}
178
+
179
+
fn render(&mut self, tui: &mut Tui) -> Result<()> {
180
+
tui.draw(|frame| {
181
+
for component in &mut self.components {
182
+
if let Err(err) = component.draw(frame, frame.area()) {
183
+
let _ = self
184
+
.signal_tx
185
+
.send(Signal::Error(format!("Failed to draw: {err:?}")));
186
+
}
187
+
}
188
+
})?;
189
+
190
+
Ok(())
191
+
}
192
+
}
+1
-2
src/components/mod.rs
+1
-2
src/components/mod.rs
···
11
11
///
12
12
/// Implementers of this trait can be registered with the main application loop and will be able to
13
13
/// receive events, update state, and be rendered on the screen.
14
-
#[expect(dead_code)]
15
-
pub trait Component {
14
+
pub trait Component: Send {
16
15
/// Register a signal handler that can send signals for processing if necessary.
17
16
///
18
17
/// # Arguments
+1
-1
src/config.rs
+1
-1
src/config.rs
···
32
32
33
33
/// Configuration for the App
34
34
#[expect(dead_code)]
35
+
#[derive(Debug, Clone)]
35
36
pub struct Config {
36
37
pub app_dirs: AppDirs, // pub data_dir: PathBuf,
37
38
// pub keybindings: KeyBindings,
···
39
40
// pub styles: Styles,
40
41
}
41
42
42
-
#[expect(dead_code)]
43
43
impl Config {
44
44
pub fn new() -> Self {
45
45
todo!()
+1
src/main.rs
+1
src/main.rs
History
1 round
0 comments
expand 0 comments
closed without merging