Add keyboard commands
This commit is contained in:
parent
a5dbccee4f
commit
c2acb446e7
15
src/app.rs
15
src/app.rs
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
#[derive(Default, Clone, Copy)]
|
||||||
pub enum AppAction {
|
pub enum AppAction {
|
||||||
Quit
|
#[default]
|
||||||
|
Quit,
|
||||||
}
|
}
|
||||||
|
@ -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)
|
}
|
||||||
|
|
||||||
|
return Ok(key_command.action);
|
||||||
}
|
}
|
||||||
_ => Ok(None),
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(())
|
||||||
|
@ -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
56
src/keys/key_commands.rs
Normal 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
1
src/keys/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod key_commands;
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user