aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShav Kinderlehrer <[email protected]>2024-03-23 13:08:14 -0400
committerShav Kinderlehrer <[email protected]>2024-03-23 13:08:14 -0400
commit0e29fa02995273bfd803aea48773cbe52a7366ed (patch)
treeefd8302cfc433c076010d94849fda224d36167d4 /src
parent0c5e8ab544823fbb4936c536ee1d8a66298f7e51 (diff)
downloadmolehole-0e29fa02995273bfd803aea48773cbe52a7366ed.tar.gz
molehole-0e29fa02995273bfd803aea48773cbe52a7366ed.zip
Rework actions and events + start statusbar
Diffstat (limited to 'src')
-rw-r--r--src/app.rs63
-rw-r--r--src/app_action.rs25
-rw-r--r--src/app_event.rs6
-rw-r--r--src/components/global_keys.rs75
-rw-r--r--src/components/mod.rs1
-rw-r--r--src/components/status.rs74
-rw-r--r--src/keys/key_commands.rs2
-rw-r--r--src/main.rs45
-rw-r--r--src/tui.rs19
9 files changed, 221 insertions, 89 deletions
diff --git a/src/app.rs b/src/app.rs
index 5e6ab1f..b9c3492 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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()
}
diff --git a/src/tui.rs b/src/tui.rs
index 930f6e7..911a50d 100644
--- a/src/tui.rs
+++ b/src/tui.rs
@@ -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(())
+}