diff options
author | Shav Kinderlehrer <[email protected]> | 2024-03-23 13:08:14 -0400 |
---|---|---|
committer | Shav Kinderlehrer <[email protected]> | 2024-03-23 13:08:14 -0400 |
commit | 0e29fa02995273bfd803aea48773cbe52a7366ed (patch) | |
tree | efd8302cfc433c076010d94849fda224d36167d4 /src | |
parent | 0c5e8ab544823fbb4936c536ee1d8a66298f7e51 (diff) | |
download | molehole-0e29fa02995273bfd803aea48773cbe52a7366ed.tar.gz molehole-0e29fa02995273bfd803aea48773cbe52a7366ed.zip |
Rework actions and events + start statusbar
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 63 | ||||
-rw-r--r-- | src/app_action.rs | 25 | ||||
-rw-r--r-- | src/app_event.rs | 6 | ||||
-rw-r--r-- | src/components/global_keys.rs | 75 | ||||
-rw-r--r-- | src/components/mod.rs | 1 | ||||
-rw-r--r-- | src/components/status.rs | 74 | ||||
-rw-r--r-- | src/keys/key_commands.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 45 | ||||
-rw-r--r-- | src/tui.rs | 19 |
9 files changed, 221 insertions, 89 deletions
@@ -1,5 +1,6 @@ use crossterm::event::Event; use eyre::Result; +use ratatui::prelude::*; use std::time::Duration; use crate::app_action::AppAction; @@ -8,7 +9,6 @@ use crate::component::Component; use crate::components; use crate::keys::key_commands::KeyCommand; use crate::tui; - pub struct App { pub tui: tui::Tui, pub tick_rate: Duration, @@ -22,50 +22,24 @@ impl App { pub fn new(tick_rate: Duration) -> Result<Self> { let tui = tui::init()?; - let key_commands = vec![ - KeyCommand { - key_code: "q".to_string(), - description: "Quit molehole".to_string(), - action: Some(AppAction::Quit), - }, - KeyCommand { - key_code: "g".to_string(), - description: "Scroll to top".to_string(), - action: None, - }, - KeyCommand { - key_code: "G".to_string(), - description: "Scroll to bottom".to_string(), - action: None, - }, - KeyCommand { - key_code: "k".to_string(), - description: "Scroll up one line".to_string(), - action: None, - }, - KeyCommand { - key_code: "j".to_string(), - description: "Scroll down one line".to_string(), - action: None, - }, - ]; - - let global_keys = components::global_keys::GlobalKeys { - key_commands: key_commands.clone(), - ..Default::default() - }; - Ok(Self { tui, tick_rate, - components: vec![Box::new(global_keys)], - key_commands, should_quit: false, + components: vec![], + key_commands: vec![], }) } pub fn run(&mut self) -> Result<()> { + let global_keys = components::global_keys::GlobalKeys { + key_commands: self.key_commands.clone(), + ..Default::default() + }; + let status_bar = components::status::StatusBar::default(); + self.components = vec![Box::new(global_keys), Box::new(status_bar)]; + for component in &mut self.components { component.init()?; } @@ -95,7 +69,7 @@ impl App { if let Some(event) = event { let mut actions: Vec<AppAction> = vec![]; for component in &mut self.components { - if let Some(action) = component.handle_event(event)? { + if let Some(action) = component.handle_event(event.clone())? { actions.push(action); } } @@ -110,9 +84,18 @@ impl App { } self.tui.draw(|frame| { - for component in &mut self.components { - let _ = component.render(frame, frame.size()); - } + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Percentage(100), + Constraint::Min(1), + ]) + .split(frame.size()); + + // status bar + let _ = self.components[1].render(frame, layout[1]); + // global_keys + let _ = self.components[0].render(frame, frame.size()); })?; Ok(()) diff --git a/src/app_action.rs b/src/app_action.rs index 93131ac..333e284 100644 --- a/src/app_action.rs +++ b/src/app_action.rs @@ -1,8 +1,25 @@ -#[derive(Default, Clone)] +use std::fmt; + +#[derive(Default, Clone, Debug)] pub enum AppAction { - StatusBarMessage(String), - StatusBarError(String), - StatusBarInput(String), + StatusBarGetInput(String), + StatusBarSetMessage(String), + StatusBarSetError(String), + OpenUrl, + + ScrollUp, + ScrollDown, + ScrollTop, + ScrollBottom, + + ShowHelpMenu, + #[default] Quit, } + +impl fmt::Display for AppAction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/src/app_event.rs b/src/app_event.rs index ee9d037..8413234 100644 --- a/src/app_event.rs +++ b/src/app_event.rs @@ -1,7 +1,11 @@ use crossterm::event::{KeyEvent, MouseEvent}; +use url::Url; -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum AppEvent { Key(KeyEvent), Mouse(MouseEvent), + + StatusBarInput(String), + OpenUrl(Url), } diff --git a/src/components/global_keys.rs b/src/components/global_keys.rs index d0a5800..39ec1b7 100644 --- a/src/components/global_keys.rs +++ b/src/components/global_keys.rs @@ -24,60 +24,49 @@ pub struct GlobalKeys { impl Component for GlobalKeys { fn init(&mut self) -> eyre::Result<()> { - self.key_commands.append(&mut vec![KeyCommand { - key_code: "?".to_string(), - description: "Toggle help menu".to_string(), - action: None, - }]); - self.scroll_state = ScrollbarState::new(self.key_commands.len()).position(self.scroll); Ok(()) } + fn handle_action(&mut self, action: AppAction) { + match action { + AppAction::ScrollUp => { + if self.scroll > 0 { + self.scroll -= 1; + } + } + AppAction::ScrollDown => { + if self.scroll < self.key_commands.len() - 1 { + self.scroll += 1; + } + } + AppAction::ScrollTop => { + self.scroll = 0; + } + AppAction::ScrollBottom => { + self.scroll = self.key_commands.len() - 1; + } + AppAction::ShowHelpMenu => { + self.should_show = !self.should_show; + self.scroll = 0; + } + + _ => {} + } + self.scroll_state = self.scroll_state.position(self.scroll); + } + fn handle_key_event( &mut self, key: KeyEvent, ) -> eyre::Result<Option<AppAction>> { if key.kind == KeyEventKind::Press { let key_event = serialize_key_event(key); - let eat_input = match key_event.as_str() { - "?" => { - self.should_show = !self.should_show; - self.scroll = 0; - true - } - "g" => { - self.scroll = 0; - true - } - "G" => { - self.scroll = self.key_commands.len() - 1; - true - } - "down" | "j" => { - if self.scroll < self.key_commands.len() - 1 { - self.scroll += 1; - } - true - } - "up" | "k" => { - if self.scroll > 0 { - self.scroll -= 1; - } - true - } - _ => false, - }; - self.scroll_state = self.scroll_state.position(self.scroll); - if eat_input && self.should_show { - return Ok(None); - } - for key_command in &mut self.key_commands { if key_command.key_code == key_event { - return Ok(key_command.action.clone()); + return Ok(Some(key_command.action.clone())); } } } @@ -108,7 +97,8 @@ impl Component for GlobalKeys { Title::from("Keyboard shortcuts").alignment(Alignment::Center), ) .borders(Borders::ALL) - .border_type(BorderType::Thick); + .border_type(BorderType::Thick) + .style(Style::default().bg(Color::DarkGray)); let mut lines: Vec<Line> = vec![]; for key_command in &mut self.key_commands { @@ -124,8 +114,7 @@ impl Component for GlobalKeys { let commands = Paragraph::new(lines) .block(block) .wrap(Wrap { trim: true }) - .scroll((u16::try_from(self.scroll)?, 0)) - .style(Style::default().bg(Color::DarkGray).fg(Color::White)); + .scroll((u16::try_from(self.scroll)?, 0)); if self.should_show { frame.render_widget(Clear, center); diff --git a/src/components/mod.rs b/src/components/mod.rs index 07fe4d8..a779415 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1 +1,2 @@ pub mod global_keys; +pub mod status; diff --git a/src/components/status.rs b/src/components/status.rs new file mode 100644 index 0000000..d91782f --- /dev/null +++ b/src/components/status.rs @@ -0,0 +1,74 @@ +use ratatui::prelude::*; +use ratatui::widgets::*; + +use crate::app_action::AppAction; +use crate::component::Component; +use crate::keys::key_commands::serialize_key_event; + +#[derive(Default, Clone)] +pub struct StatusBar { + message: String, + current_key: String, + error: bool, +} + +impl Component for StatusBar { + fn handle_key_event( + &mut self, + key: crossterm::event::KeyEvent, + ) -> eyre::Result<Option<AppAction>> { + let key_str = serialize_key_event(key); + self.current_key = key_str; + + Ok(None) + } + + fn handle_action(&mut self, action: crate::app_action::AppAction) { + match action { + AppAction::StatusBarSetMessage(message) => { + self.error = false; + self.message = message; + } + AppAction::StatusBarSetError(message) => { + self.error = true; + self.message = message; + } + AppAction::StatusBarGetInput(_prompt) => todo!(), + _ => { + self.current_key += " "; + self.current_key += &action.to_string(); + } + } + } + + fn render( + &mut self, + frame: &mut ratatui::prelude::Frame, + rect: ratatui::prelude::Rect, + ) -> eyre::Result<()> { + let block = + Block::default().style(Style::default().bg(if self.error { + Color::Red + } else { + Color::DarkGray + })); + + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Percentage(50), + Constraint::Percentage(50), + ]) + .split(rect); + + let message = Paragraph::new(self.message.clone()).block(block.clone()); + let current_key = Paragraph::new(self.current_key.clone()) + .block(block) + .alignment(Alignment::Right); + + frame.render_widget(message, layout[0]); + frame.render_widget(current_key, layout[1]); + + Ok(()) + } +} diff --git a/src/keys/key_commands.rs b/src/keys/key_commands.rs index 27fe0ad..fc06286 100644 --- a/src/keys/key_commands.rs +++ b/src/keys/key_commands.rs @@ -6,7 +6,7 @@ use crate::app_action::AppAction; pub struct KeyCommand { pub key_code: String, pub description: String, - pub action: Option<AppAction>, + pub action: AppAction, } impl std::fmt::Display for KeyCommand { diff --git a/src/main.rs b/src/main.rs index ca87db6..33c2036 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,53 @@ mod tui; use eyre::Result; +use app_action::AppAction; +use keys::key_commands::KeyCommand; + fn main() -> Result<()> { + tui::install_hooks()?; let mut app = app::App::new(std::time::Duration::from_millis(10))?; + let mut key_commands = vec![ + // Status bar + KeyCommand { + key_code: "o".to_string(), + description: "Open new link".to_string(), + action: AppAction::OpenUrl, + }, + + // Navigation + KeyCommand { + key_code: "g".to_string(), + description: "Scroll to top".to_string(), + action: AppAction::ScrollTop, + }, + KeyCommand { + key_code: "G".to_string(), + description: "Scroll to bottom".to_string(), + action: AppAction::ScrollBottom, + }, + KeyCommand { + key_code: "k".to_string(), + description: "Scroll up one line".to_string(), + action: AppAction::ScrollUp, + }, + KeyCommand { + key_code: "j".to_string(), + description: "Scroll down one line".to_string(), + action: AppAction::ScrollDown, + }, + KeyCommand { + key_code: "q".to_string(), + description: "Quit molehole".to_string(), + action: AppAction::Quit, + }, + KeyCommand { + key_code: "?".to_string(), + description: "Show help menu".to_string(), + action: AppAction::ShowHelpMenu + } + ]; + app.key_commands.append(&mut key_commands); app.run() } @@ -10,6 +10,7 @@ use crossterm::{event, execute}; use ratatui::prelude::{CrosstermBackend, Terminal}; use std::io; use std::io::{stdout, Stdout}; +use std::panic; pub type Tui = Terminal<CrosstermBackend<Stdout>>; @@ -41,3 +42,21 @@ pub fn get_event(tick: std::time::Duration) -> io::Result<Option<Event>> { Ok(None) } + +pub fn install_hooks() -> eyre::Result<()> { + let hook_builder = color_eyre::config::HookBuilder::default(); + let (panic_hook, eyre_hook) = hook_builder.into_hooks(); + + let panic_hook = panic_hook.into_panic_hook(); + panic::set_hook(Box::new(move |panic_info| { + restore().unwrap(); + panic_hook(panic_info); + })); + + let eyre_hook = eyre_hook.into_eyre_hook(); + eyre::set_hook(Box::new(move |error| { + restore().unwrap(); + eyre_hook(error) + }))?; + Ok(()) +} |