Overhaul events system + add url_manager

This commit is contained in:
Shav Kinderlehrer 2024-03-24 14:18:53 -04:00
parent 9d23304feb
commit f638f4bd1e
9 changed files with 117 additions and 47 deletions

View File

@ -9,6 +9,7 @@ use crate::component::Component;
use crate::components; use crate::components;
use crate::keys::key_commands::KeyCommand; use crate::keys::key_commands::KeyCommand;
use crate::tui; use crate::tui;
pub struct App { pub struct App {
pub tui: tui::Tui, pub tui: tui::Tui,
pub tick_rate: Duration, pub tick_rate: Duration,
@ -37,8 +38,16 @@ impl App {
key_commands: self.key_commands.clone(), key_commands: self.key_commands.clone(),
..Default::default() ..Default::default()
}; };
let status_bar = components::status::StatusBar::default(); let status_bar = components::status::StatusBar {
self.components = vec![Box::new(global_keys), Box::new(status_bar)]; message: "Press '?' to show the help menu".to_string(),
..Default::default()
};
let url_manager = components::url_manager::UrlManager::default();
self.components = vec![
Box::new(global_keys),
Box::new(status_bar),
Box::new(url_manager),
];
for component in &mut self.components { for component in &mut self.components {
component.init()?; component.init()?;
@ -67,16 +76,7 @@ impl App {
}; };
if let Some(event) = event { if let Some(event) = event {
let mut actions: Vec<AppAction> = vec![]; self.handle_event(event)?;
for component in &mut self.components {
if let Some(action) = component.handle_event(event.clone())? {
actions.push(action);
}
}
for action in actions {
self.handle_action(action)?;
}
} }
if self.should_quit { if self.should_quit {
@ -94,10 +94,28 @@ impl App {
// status bar // status bar
let _ = self.components[1].render(frame, layout[1]); let _ = self.components[1].render(frame, layout[1]);
// global_keys // global_keys
let _ = self.components[0].render(frame, frame.size()); let _ = self.components[0].render(frame, frame.size());
})?; })?;
self.update()?;
Ok(())
}
pub fn update(&mut self) -> Result<()> {
let mut events: Vec<AppEvent> = vec![];
for component in &mut self.components {
if let Some(event) = component.update() {
events.push(event);
}
}
for event in events {
self.handle_event(event)?;
}
Ok(()) Ok(())
} }
@ -119,4 +137,18 @@ impl App {
} }
} }
} }
fn handle_event(&mut self, event: AppEvent) -> Result<()> {
let mut actions: Vec<AppAction> = vec![];
for component in &mut self.components {
if let Some(action) = component.handle_event(event.clone()) {
actions.push(action);
}
}
for action in actions {
self.handle_action(action)?;
}
Ok(())
}
} }

View File

@ -2,7 +2,6 @@ use std::fmt;
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
pub enum AppAction { pub enum AppAction {
StatusBarGetInput(String),
StatusBarSetMessage(String), StatusBarSetMessage(String),
StatusBarSetError(String), StatusBarSetError(String),
OpenUrl, OpenUrl,

View File

@ -6,6 +6,5 @@ pub enum AppEvent {
Key(KeyEvent), Key(KeyEvent),
Mouse(MouseEvent), Mouse(MouseEvent),
StatusBarInput(String),
OpenUrl(Url), OpenUrl(Url),
} }

View File

@ -14,32 +14,29 @@ pub trait Component {
fn handle_action(&mut self, action: AppAction) {} fn handle_action(&mut self, action: AppAction) {}
#[allow(unused)] #[allow(unused)]
fn handle_event(&mut self, event: AppEvent) -> Result<Option<AppAction>> { fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> {
match event { match event {
AppEvent::Key(key_event) => Ok(self.handle_key_event(key_event)?), AppEvent::Key(key_event) => self.handle_key_event(key_event),
AppEvent::Mouse(mouse_event) => { AppEvent::Mouse(mouse_event) => {
Ok(self.handle_mouse_event(mouse_event)?) self.handle_mouse_event(mouse_event)
} }
_ => Ok(None), _ => None,
} }
} }
#[allow(unused)] #[allow(unused)]
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<AppAction>> { fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> {
Ok(None) None
} }
#[allow(unused)] #[allow(unused)]
fn handle_mouse_event( fn handle_mouse_event(&mut self, mouse: MouseEvent) -> Option<AppAction> {
&mut self, None
mouse: MouseEvent,
) -> Result<Option<AppAction>> {
Ok(None)
} }
#[allow(unused)] #[allow(unused)]
fn update(&mut self, action: AppAction) -> Result<Option<AppAction>> { fn update(&mut self) -> Option<AppEvent> {
Ok(None) None
} }
fn render(&mut self, frame: &mut Frame, rect: Rect) -> Result<()>; fn render(&mut self, frame: &mut Frame, rect: Rect) -> Result<()>;

View File

@ -58,20 +58,17 @@ impl Component for GlobalKeys {
self.scroll_state = self.scroll_state.position(self.scroll); self.scroll_state = self.scroll_state.position(self.scroll);
} }
fn handle_key_event( fn handle_key_event(&mut self, key: KeyEvent) -> Option<AppAction> {
&mut self,
key: KeyEvent,
) -> eyre::Result<Option<AppAction>> {
if key.kind == KeyEventKind::Press { if key.kind == KeyEventKind::Press {
let key_event = serialize_key_event(key); let key_event = serialize_key_event(key);
for key_command in &mut self.key_commands { for key_command in &mut self.key_commands {
if key_command.key_code == key_event { if key_command.key_code == key_event {
return Ok(Some(key_command.action.clone())); return Some(key_command.action.clone());
} }
} }
} }
Ok(None) None
} }
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {

View File

@ -1,2 +1,3 @@
pub mod global_keys; pub mod global_keys;
pub mod status; pub mod status;
pub mod url_manager;

View File

@ -2,29 +2,31 @@ use ratatui::prelude::*;
use ratatui::widgets::*; use ratatui::widgets::*;
use crate::app_action::AppAction; use crate::app_action::AppAction;
use crate::app_event::AppEvent;
use crate::component::Component; use crate::component::Component;
use crate::keys::key_commands::serialize_key_event; use crate::keys::key_commands::serialize_key_event;
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct StatusBar { pub struct StatusBar {
message: String, pub message: String,
current_key: String, pub current_key: String,
error: bool, pub error: bool,
pub url_to_open: Option<url::Url>,
} }
impl Component for StatusBar { impl Component for StatusBar {
fn handle_key_event( fn handle_key_event(
&mut self, &mut self,
key: crossterm::event::KeyEvent, key: crossterm::event::KeyEvent,
) -> eyre::Result<Option<AppAction>> { ) -> Option<AppAction> {
let key_str = serialize_key_event(key); let key_str = serialize_key_event(key);
self.current_key = key_str; self.current_key = key_str;
Ok(None) None
} }
fn handle_action(&mut self, action: crate::app_action::AppAction) { fn handle_action(&mut self, action: crate::app_action::AppAction) {
match action { match action.clone() {
AppAction::StatusBarSetMessage(message) => { AppAction::StatusBarSetMessage(message) => {
self.error = false; self.error = false;
self.message = message; self.message = message;
@ -33,14 +35,24 @@ impl Component for StatusBar {
self.error = true; self.error = true;
self.message = message; self.message = message;
} }
AppAction::StatusBarGetInput(_prompt) => todo!(), AppAction::OpenUrl => {
_ => { self.url_to_open =
self.current_key += " "; Some(url::Url::parse("molerat://example.com").unwrap());
self.current_key += &action.to_string();
} }
_ => {}
} }
} }
fn update(&mut self) -> Option<AppEvent> {
if let Some(url) = &self.url_to_open {
let event = AppEvent::OpenUrl(url.clone());
self.url_to_open = None;
return Some(event);
}
None
}
fn render( fn render(
&mut self, &mut self,
frame: &mut ratatui::prelude::Frame, frame: &mut ratatui::prelude::Frame,

View File

@ -0,0 +1,34 @@
use url::Url;
use crate::app_action::AppAction;
use crate::app_event::AppEvent;
use crate::component::Component;
#[derive(Default)]
pub struct UrlManager {
url: Option<Url>,
}
impl Component for UrlManager {
fn handle_event(&mut self, event: AppEvent) -> Option<AppAction> {
match event {
AppEvent::OpenUrl(url) => {
self.url = Some(url.clone());
return Some(AppAction::StatusBarSetMessage(format!(
"Opening {}",
url.as_str()
)));
}
_ => {}
}
None
}
fn render(
&mut self,
_frame: &mut ratatui::prelude::Frame,
_rect: ratatui::prelude::Rect,
) -> eyre::Result<()> {
Ok(())
}
}

View File

@ -21,7 +21,6 @@ fn main() -> Result<()> {
description: "Open new link".to_string(), description: "Open new link".to_string(),
action: AppAction::OpenUrl, action: AppAction::OpenUrl,
}, },
// Navigation // Navigation
KeyCommand { KeyCommand {
key_code: "g".to_string(), key_code: "g".to_string(),
@ -50,9 +49,9 @@ fn main() -> Result<()> {
}, },
KeyCommand { KeyCommand {
key_code: "?".to_string(), key_code: "?".to_string(),
description: "Show help menu".to_string(), description: "Toggle help menu".to_string(),
action: AppAction::ShowHelpMenu action: AppAction::ShowHelpMenu,
} },
]; ];
app.key_commands.append(&mut key_commands); app.key_commands.append(&mut key_commands);