From c2acb446e7868b67e5743d54cee5c64c469e5a18 Mon Sep 17 00:00:00 2001 From: Shav Kinderlehrer Date: Wed, 6 Mar 2024 14:14:07 -0500 Subject: [PATCH] Add keyboard commands --- src/app.rs | 15 +++++++++- src/app_action.rs | 4 ++- src/components/global_keys.rs | 54 +++++++++++++++++++++++++-------- src/components/hello_world.rs | 11 +++++++ src/keys/key_commands.rs | 56 +++++++++++++++++++++++++++++++++++ src/keys/mod.rs | 1 + src/main.rs | 1 + 7 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 src/keys/key_commands.rs create mode 100644 src/keys/mod.rs diff --git a/src/app.rs b/src/app.rs index 22a8c40..a1fd1e4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,12 +6,14 @@ use crate::app_action::AppAction; use crate::app_event::AppEvent; 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, pub components: Vec>, + pub key_commands: Vec, should_quit: bool, } @@ -20,13 +22,24 @@ impl App { pub fn new(tick_rate: Duration) -> Result { let tui = tui::init()?; - let global_keys = components::global_keys::GlobalKeys::default(); + let mut key_commands = vec![KeyCommand { + key_code: "q".to_string(), + description: "Quit molehole".to_string(), + action: Some(AppAction::Quit), + }]; + + let global_keys = components::global_keys::GlobalKeys { + key_commands: key_commands.clone(), + ..Default::default() + }; let hello_world = components::hello_world::HelloWorld::default(); Ok(Self { tui, tick_rate, components: vec![Box::new(hello_world), Box::new(global_keys)], + key_commands, + should_quit: false, }) } diff --git a/src/app_action.rs b/src/app_action.rs index 104e4f9..8bbe9dd 100644 --- a/src/app_action.rs +++ b/src/app_action.rs @@ -1,3 +1,5 @@ +#[derive(Default, Clone, Copy)] pub enum AppAction { - Quit + #[default] + Quit, } diff --git a/src/components/global_keys.rs b/src/components/global_keys.rs index e99e2e0..67b1b6a 100644 --- a/src/components/global_keys.rs +++ b/src/components/global_keys.rs @@ -4,41 +4,69 @@ use ratatui::widgets::*; use crate::app_action::AppAction; use crate::component::Component; +use crate::keys::key_commands::*; -#[derive(Default, Clone, Copy)] +#[derive(Default)] pub struct GlobalKeys { - should_show: bool, + pub key_commands: Vec, + + pub should_show: bool, + pub scroll: u16, } impl Component for GlobalKeys { + fn init(&mut self) -> eyre::Result<()> { + self.key_commands.push(KeyCommand { + key_code: "?".to_string(), + description: "Show help menu".to_string(), + action: None, + }); + + Ok(()) + } + fn handle_key_event( &mut self, key: KeyEvent, ) -> eyre::Result> { if key.kind == KeyEventKind::Press { - return match key.code { - KeyCode::Char('q') => Ok(Some(AppAction::Quit)), - KeyCode::Char('?') => { - self.should_show = !self.should_show; - Ok(None) + for key_command in self.key_commands.iter_mut() { + if key_command.key_code == serialize_key_event(key) { + if serialize_key_event(key) == "?" { + self.should_show = !self.should_show; + } + + return Ok(key_command.action); } - _ => Ok(None), - }; + } } Ok(None) } fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { - let horizontal_center = Layout::default() - .direction(Direction::Horizontal); - let block = Block::default() .title("Keyboard shortcuts") .borders(Borders::ALL); + let mut lines: Vec = vec![]; + for key_command in self.key_commands.iter_mut() { + let command = Span::from(key_command.key_code.clone()); + let description = + Span::from(key_command.description.clone()).italic(); + let spacer = Span::from(" "); + + let line = Line::from(vec![command, spacer, description]); + lines.push(line); + } + + let commands = Paragraph::new(lines) + .block(block) + .wrap(Wrap { trim: true }) + .scroll((self.scroll, 0)); + if self.should_show { - frame.render_widget(block, rect); + frame.render_widget(commands, rect); } Ok(()) diff --git a/src/components/hello_world.rs b/src/components/hello_world.rs index afb9d47..2e3a2ee 100644 --- a/src/components/hello_world.rs +++ b/src/components/hello_world.rs @@ -1,7 +1,9 @@ use ratatui::prelude::*; use ratatui::widgets::Paragraph; +use crate::app_action::AppAction; use crate::component::Component; +use crate::keys::key_commands::*; #[derive(Default, Clone)] pub struct HelloWorld { @@ -14,7 +16,16 @@ impl Component for HelloWorld { Ok(()) } + fn handle_key_event( + &mut self, + key: crossterm::event::KeyEvent, + ) -> eyre::Result> { + self.text = serialize_key_event(key); + Ok(None) + } + fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { + frame.render_widget(Paragraph::new(self.text.clone()), rect); Ok(()) diff --git a/src/keys/key_commands.rs b/src/keys/key_commands.rs new file mode 100644 index 0000000..6124560 --- /dev/null +++ b/src/keys/key_commands.rs @@ -0,0 +1,56 @@ +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + +use crate::app_action::AppAction; + +#[derive(Default, Clone)] +pub struct KeyCommand { + pub key_code: String, + pub description: String, + pub action: Option, +} + +impl std::fmt::Display for KeyCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\t{}", self.key_code, self.description) + } +} + +pub fn serialize_key_event(event: KeyEvent) -> String { + let mut modifiers = Vec::with_capacity(3); + if event.modifiers.intersects(KeyModifiers::CONTROL) { + modifiers.push("ctrl"); + } + if event.modifiers.intersects(KeyModifiers::SUPER) + || event.modifiers.intersects(KeyModifiers::HYPER) + || event.modifiers.intersects(KeyModifiers::META) + { + modifiers.push("super"); + } + if event.modifiers.intersects(KeyModifiers::ALT) { + modifiers.push("alt"); + } + + let char; + let key = match event.code { + KeyCode::Backspace => "del", + KeyCode::Enter => "enter", + KeyCode::Left => "left", + KeyCode::Right => "right", + KeyCode::Up => "up", + KeyCode::Down => "down", + KeyCode::Tab => "tab", + KeyCode::Delete => "del", + KeyCode::Char(c) if c == ' ' => "space", + KeyCode::Char(c) => { + char = c.to_string(); + &char + } + KeyCode::Esc => "esc", + _ => "", + }; + let separator = if modifiers.len() > 0 { "-" } else { "" }; + let serialized_event = + format!("{}{}{}", modifiers.join("-"), separator, key); + + serialized_event +} diff --git a/src/keys/mod.rs b/src/keys/mod.rs new file mode 100644 index 0000000..c884843 --- /dev/null +++ b/src/keys/mod.rs @@ -0,0 +1 @@ +pub mod key_commands; diff --git a/src/main.rs b/src/main.rs index 16bee88..96ac2f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod app_action; mod app_event; mod component; mod components; +mod keys; mod tui; use eyre::Result;