Add keyboard commands

This commit is contained in:
Shav Kinderlehrer 2024-03-06 14:14:07 -05:00
parent a5dbccee4f
commit c2acb446e7
7 changed files with 127 additions and 15 deletions

View File

@ -6,12 +6,14 @@ use crate::app_action::AppAction;
use crate::app_event::AppEvent; use crate::app_event::AppEvent;
use crate::component::Component; use crate::component::Component;
use crate::components; use crate::components;
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,
pub components: Vec<Box<dyn Component>>, pub components: Vec<Box<dyn Component>>,
pub key_commands: Vec<KeyCommand>,
should_quit: bool, should_quit: bool,
} }
@ -20,13 +22,24 @@ impl App {
pub fn new(tick_rate: Duration) -> Result<Self> { pub fn new(tick_rate: Duration) -> Result<Self> {
let tui = tui::init()?; 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(); let hello_world = components::hello_world::HelloWorld::default();
Ok(Self { Ok(Self {
tui, tui,
tick_rate, tick_rate,
components: vec![Box::new(hello_world), Box::new(global_keys)], components: vec![Box::new(hello_world), Box::new(global_keys)],
key_commands,
should_quit: false, should_quit: false,
}) })
} }

View File

@ -1,3 +1,5 @@
#[derive(Default, Clone, Copy)]
pub enum AppAction { pub enum AppAction {
Quit #[default]
Quit,
} }

View File

@ -4,41 +4,69 @@ use ratatui::widgets::*;
use crate::app_action::AppAction; use crate::app_action::AppAction;
use crate::component::Component; use crate::component::Component;
use crate::keys::key_commands::*;
#[derive(Default, Clone, Copy)] #[derive(Default)]
pub struct GlobalKeys { pub struct GlobalKeys {
should_show: bool, pub key_commands: Vec<KeyCommand>,
pub should_show: bool,
pub scroll: u16,
} }
impl Component for GlobalKeys { 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( fn handle_key_event(
&mut self, &mut self,
key: KeyEvent, key: KeyEvent,
) -> eyre::Result<Option<AppAction>> { ) -> eyre::Result<Option<AppAction>> {
if key.kind == KeyEventKind::Press { if key.kind == KeyEventKind::Press {
return match key.code { for key_command in self.key_commands.iter_mut() {
KeyCode::Char('q') => Ok(Some(AppAction::Quit)), if key_command.key_code == serialize_key_event(key) {
KeyCode::Char('?') => { if serialize_key_event(key) == "?" {
self.should_show = !self.should_show; self.should_show = !self.should_show;
Ok(None)
} }
_ => Ok(None),
}; return Ok(key_command.action);
}
}
} }
Ok(None) Ok(None)
} }
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {
let horizontal_center = Layout::default()
.direction(Direction::Horizontal);
let block = Block::default() let block = Block::default()
.title("Keyboard shortcuts") .title("Keyboard shortcuts")
.borders(Borders::ALL); .borders(Borders::ALL);
let mut lines: Vec<Line> = 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 { if self.should_show {
frame.render_widget(block, rect); frame.render_widget(commands, rect);
} }
Ok(()) Ok(())

View File

@ -1,7 +1,9 @@
use ratatui::prelude::*; use ratatui::prelude::*;
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
use crate::app_action::AppAction;
use crate::component::Component; use crate::component::Component;
use crate::keys::key_commands::*;
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct HelloWorld { pub struct HelloWorld {
@ -14,7 +16,16 @@ impl Component for HelloWorld {
Ok(()) Ok(())
} }
fn handle_key_event(
&mut self,
key: crossterm::event::KeyEvent,
) -> eyre::Result<Option<AppAction>> {
self.text = serialize_key_event(key);
Ok(None)
}
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> { fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {
frame.render_widget(Paragraph::new(self.text.clone()), rect); frame.render_widget(Paragraph::new(self.text.clone()), rect);
Ok(()) Ok(())

56
src/keys/key_commands.rs Normal file
View File

@ -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<AppAction>,
}
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
}

1
src/keys/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod key_commands;

View File

@ -3,6 +3,7 @@ mod app_action;
mod app_event; mod app_event;
mod component; mod component;
mod components; mod components;
mod keys;
mod tui; mod tui;
use eyre::Result; use eyre::Result;