Implement keyboard shortcut popup
This commit is contained in:
parent
c2acb446e7
commit
fa3e812a30
@ -18,6 +18,7 @@
|
|||||||
cargo
|
cargo
|
||||||
rust-analyzer
|
rust-analyzer
|
||||||
libiconv
|
libiconv
|
||||||
|
clippy
|
||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
exec zsh
|
exec zsh
|
||||||
|
18
src/app.rs
18
src/app.rs
@ -22,7 +22,7 @@ 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 mut key_commands = vec![KeyCommand {
|
let key_commands = vec![KeyCommand {
|
||||||
key_code: "q".to_string(),
|
key_code: "q".to_string(),
|
||||||
description: "Quit molehole".to_string(),
|
description: "Quit molehole".to_string(),
|
||||||
action: Some(AppAction::Quit),
|
action: Some(AppAction::Quit),
|
||||||
@ -45,7 +45,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) -> Result<()> {
|
pub fn run(&mut self) -> Result<()> {
|
||||||
for component in self.components.iter_mut() {
|
for component in &mut self.components {
|
||||||
component.init()?;
|
component.init()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,10 +73,9 @@ impl App {
|
|||||||
|
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
let mut actions: Vec<AppAction> = vec![];
|
let mut actions: Vec<AppAction> = vec![];
|
||||||
for component in self.components.iter_mut() {
|
for component in &mut self.components {
|
||||||
match component.handle_event(event)? {
|
if let Some(action) = component.handle_event(event)? {
|
||||||
Some(action) => actions.push(action),
|
actions.push(action);
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +89,8 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.tui.draw(|frame| {
|
self.tui.draw(|frame| {
|
||||||
for (_i, component) in self.components.iter_mut().enumerate() {
|
for component in &mut self.components {
|
||||||
match component.render(frame, frame.size()) {
|
let _ = component.render(frame, frame.size());
|
||||||
Ok(_) => (),
|
|
||||||
Err(_) => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -1,27 +1,38 @@
|
|||||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
|
use crossterm::event::{KeyEvent, KeyEventKind};
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::{
|
||||||
use ratatui::widgets::*;
|
Alignment, Color, Constraint, Direction, Frame, Layout, Line, Margin, Rect,
|
||||||
|
Span, Style, Stylize,
|
||||||
|
};
|
||||||
|
use ratatui::widgets::block::{Block, BorderType, Title};
|
||||||
|
use ratatui::widgets::{
|
||||||
|
Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState,
|
||||||
|
Wrap,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app_action::AppAction;
|
use crate::app_action::AppAction;
|
||||||
use crate::component::Component;
|
use crate::component::Component;
|
||||||
use crate::keys::key_commands::*;
|
use crate::keys::key_commands::{serialize_key_event, KeyCommand};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GlobalKeys {
|
pub struct GlobalKeys {
|
||||||
pub key_commands: Vec<KeyCommand>,
|
pub key_commands: Vec<KeyCommand>,
|
||||||
|
|
||||||
pub should_show: bool,
|
pub should_show: bool,
|
||||||
pub scroll: u16,
|
pub scroll: usize,
|
||||||
|
pub scroll_state: ScrollbarState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for GlobalKeys {
|
impl Component for GlobalKeys {
|
||||||
fn init(&mut self) -> eyre::Result<()> {
|
fn init(&mut self) -> eyre::Result<()> {
|
||||||
self.key_commands.push(KeyCommand {
|
self.key_commands.push(KeyCommand {
|
||||||
key_code: "?".to_string(),
|
key_code: "?".to_string(),
|
||||||
description: "Show help menu".to_string(),
|
description: "Toggle help menu".to_string(),
|
||||||
action: None,
|
action: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.scroll_state =
|
||||||
|
ScrollbarState::new(self.key_commands.len()).position(self.scroll);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,12 +41,36 @@ impl Component for GlobalKeys {
|
|||||||
key: KeyEvent,
|
key: KeyEvent,
|
||||||
) -> eyre::Result<Option<AppAction>> {
|
) -> eyre::Result<Option<AppAction>> {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
for key_command in self.key_commands.iter_mut() {
|
let key_event = serialize_key_event(key);
|
||||||
if key_command.key_code == serialize_key_event(key) {
|
let eat_input = match key_event.as_str() {
|
||||||
if serialize_key_event(key) == "?" {
|
"?" => {
|
||||||
self.should_show = !self.should_show;
|
self.should_show = !self.should_show;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
"down" => {
|
||||||
|
if self.scroll < self.key_commands.len() - 1 {
|
||||||
|
self.scroll += 1;
|
||||||
|
self.scroll_state =
|
||||||
|
self.scroll_state.position(self.scroll);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
"up" => {
|
||||||
|
if self.scroll > 0 {
|
||||||
|
self.scroll -= 1;
|
||||||
|
self.scroll_state =
|
||||||
|
self.scroll_state.position(self.scroll);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
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);
|
return Ok(key_command.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,12 +80,32 @@ impl Component for GlobalKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {
|
fn render(&mut self, frame: &mut Frame, rect: Rect) -> eyre::Result<()> {
|
||||||
|
let vertical_center = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
])
|
||||||
|
.split(rect);
|
||||||
|
let center = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
Constraint::Percentage(50),
|
||||||
|
Constraint::Percentage(50 / 2),
|
||||||
|
])
|
||||||
|
.split(vertical_center[1])[1];
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title("Keyboard shortcuts")
|
.title(
|
||||||
.borders(Borders::ALL);
|
Title::from("Keyboard shortcuts").alignment(Alignment::Center),
|
||||||
|
)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Thick);
|
||||||
|
|
||||||
let mut lines: Vec<Line> = vec![];
|
let mut lines: Vec<Line> = vec![];
|
||||||
for key_command in self.key_commands.iter_mut() {
|
for key_command in &mut self.key_commands {
|
||||||
let command = Span::from(key_command.key_code.clone());
|
let command = Span::from(key_command.key_code.clone());
|
||||||
let description =
|
let description =
|
||||||
Span::from(key_command.description.clone()).italic();
|
Span::from(key_command.description.clone()).italic();
|
||||||
@ -63,10 +118,20 @@ impl Component for GlobalKeys {
|
|||||||
let commands = Paragraph::new(lines)
|
let commands = Paragraph::new(lines)
|
||||||
.block(block)
|
.block(block)
|
||||||
.wrap(Wrap { trim: true })
|
.wrap(Wrap { trim: true })
|
||||||
.scroll((self.scroll, 0));
|
.scroll((u16::try_from(self.scroll)?, 0))
|
||||||
|
.style(Style::default().bg(Color::DarkGray).fg(Color::LightYellow));
|
||||||
|
|
||||||
if self.should_show {
|
if self.should_show {
|
||||||
frame.render_widget(commands, rect);
|
frame.render_widget(Clear, center);
|
||||||
|
frame.render_widget(commands, center);
|
||||||
|
frame.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::VerticalRight),
|
||||||
|
center.inner(&Margin {
|
||||||
|
vertical: 1,
|
||||||
|
horizontal: 0,
|
||||||
|
}),
|
||||||
|
&mut self.scroll_state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use ratatui::prelude::*;
|
use ratatui::prelude::{Frame, Rect};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::{Paragraph, Wrap};
|
||||||
|
|
||||||
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 {
|
||||||
@ -16,17 +14,11 @@ 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(
|
||||||
frame.render_widget(Paragraph::new(self.text.clone()), rect);
|
Paragraph::new(self.text.clone()).wrap(Wrap { trim: true }),
|
||||||
|
rect,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -32,15 +32,14 @@ pub fn serialize_key_event(event: KeyEvent) -> String {
|
|||||||
|
|
||||||
let char;
|
let char;
|
||||||
let key = match event.code {
|
let key = match event.code {
|
||||||
KeyCode::Backspace => "del",
|
KeyCode::Backspace | KeyCode::Delete => "del",
|
||||||
KeyCode::Enter => "enter",
|
KeyCode::Enter => "enter",
|
||||||
KeyCode::Left => "left",
|
KeyCode::Left => "left",
|
||||||
KeyCode::Right => "right",
|
KeyCode::Right => "right",
|
||||||
KeyCode::Up => "up",
|
KeyCode::Up => "up",
|
||||||
KeyCode::Down => "down",
|
KeyCode::Down => "down",
|
||||||
KeyCode::Tab => "tab",
|
KeyCode::Tab => "tab",
|
||||||
KeyCode::Delete => "del",
|
KeyCode::Char(' ') => "space",
|
||||||
KeyCode::Char(c) if c == ' ' => "space",
|
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
char = c.to_string();
|
char = c.to_string();
|
||||||
&char
|
&char
|
||||||
@ -48,7 +47,7 @@ pub fn serialize_key_event(event: KeyEvent) -> String {
|
|||||||
KeyCode::Esc => "esc",
|
KeyCode::Esc => "esc",
|
||||||
_ => "",
|
_ => "",
|
||||||
};
|
};
|
||||||
let separator = if modifiers.len() > 0 { "-" } else { "" };
|
let separator = if modifiers.is_empty() { "-" } else { "" };
|
||||||
let serialized_event =
|
let serialized_event =
|
||||||
format!("{}{}{}", modifiers.join("-"), separator, key);
|
format!("{}{}{}", modifiers.join("-"), separator, key);
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
use crossterm::{event, event::Event, execute, terminal::*};
|
use crossterm::terminal::{
|
||||||
use ratatui::prelude::*;
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
};
|
||||||
|
use crossterm::{event, event::Event, execute};
|
||||||
|
use ratatui::prelude::{CrosstermBackend, Terminal};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{stdout, Stdout};
|
use std::io::{stdout, Stdout};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user