aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShav Kinderlehrer <[email protected]>2024-03-07 02:05:03 -0500
committerShav Kinderlehrer <[email protected]>2024-03-07 02:05:03 -0500
commitfa3e812a301455c7bc7b4d829a7fba01ddaea413 (patch)
treea7f5c2b3b500c8780b6460f6d1107965680f7148
parentc2acb446e7868b67e5743d54cee5c64c469e5a18 (diff)
downloadmolehole-fa3e812a301455c7bc7b4d829a7fba01ddaea413.tar.gz
Implement keyboard shortcut popup
-rw-r--r--flake.nix1
-rw-r--r--src/app.rs18
-rw-r--r--src/components/global_keys.rs95
-rw-r--r--src/components/hello_world.rs20
-rw-r--r--src/keys/key_commands.rs7
-rw-r--r--src/tui.rs8
6 files changed, 103 insertions, 46 deletions
diff --git a/flake.nix b/flake.nix
index c59cb93..9eceed3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -18,6 +18,7 @@
cargo
rust-analyzer
libiconv
+ clippy
];
shellHook = ''
exec zsh
diff --git a/src/app.rs b/src/app.rs
index a1fd1e4..03ba04a 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -22,7 +22,7 @@ impl App {
pub fn new(tick_rate: Duration) -> Result<Self> {
let tui = tui::init()?;
- let mut key_commands = vec![KeyCommand {
+ let key_commands = vec![KeyCommand {
key_code: "q".to_string(),
description: "Quit molehole".to_string(),
action: Some(AppAction::Quit),
@@ -45,7 +45,7 @@ impl App {
}
pub fn run(&mut self) -> Result<()> {
- for component in self.components.iter_mut() {
+ for component in &mut self.components {
component.init()?;
}
@@ -73,10 +73,9 @@ impl App {
if let Some(event) = event {
let mut actions: Vec<AppAction> = vec![];
- for component in self.components.iter_mut() {
- match component.handle_event(event)? {
- Some(action) => actions.push(action),
- None => (),
+ for component in &mut self.components {
+ if let Some(action) = component.handle_event(event)? {
+ actions.push(action);
}
}
@@ -90,11 +89,8 @@ impl App {
}
self.tui.draw(|frame| {
- for (_i, component) in self.components.iter_mut().enumerate() {
- match component.render(frame, frame.size()) {
- Ok(_) => (),
- Err(_) => (),
- }
+ for component in &mut self.components {
+ let _ = component.render(frame, frame.size());
}
})?;
diff --git a/src/components/global_keys.rs b/src/components/global_keys.rs
index 67b1b6a..27adab0 100644
--- a/src/components/global_keys.rs
+++ b/src/components/global_keys.rs
@@ -1,27 +1,38 @@
-use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
-use ratatui::prelude::*;
-use ratatui::widgets::*;
+use crossterm::event::{KeyEvent, KeyEventKind};
+use ratatui::prelude::{
+ 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::component::Component;
-use crate::keys::key_commands::*;
+use crate::keys::key_commands::{serialize_key_event, KeyCommand};
#[derive(Default)]
pub struct GlobalKeys {
pub key_commands: Vec<KeyCommand>,
pub should_show: bool,
- pub scroll: u16,
+ pub scroll: usize,
+ pub scroll_state: ScrollbarState,
}
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(),
+ description: "Toggle help menu".to_string(),
action: None,
});
+ self.scroll_state =
+ ScrollbarState::new(self.key_commands.len()).position(self.scroll);
+
Ok(())
}
@@ -30,12 +41,36 @@ impl Component for GlobalKeys {
key: KeyEvent,
) -> eyre::Result<Option<AppAction>> {
if key.kind == KeyEventKind::Press {
- 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;
+ let key_event = serialize_key_event(key);
+ let eat_input = match key_event.as_str() {
+ "?" => {
+ 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);
}
}
@@ -45,12 +80,32 @@ impl Component for GlobalKeys {
}
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()
- .title("Keyboard shortcuts")
- .borders(Borders::ALL);
+ .title(
+ Title::from("Keyboard shortcuts").alignment(Alignment::Center),
+ )
+ .borders(Borders::ALL)
+ .border_type(BorderType::Thick);
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 description =
Span::from(key_command.description.clone()).italic();
@@ -63,10 +118,20 @@ impl Component for GlobalKeys {
let commands = Paragraph::new(lines)
.block(block)
.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 {
- 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(())
diff --git a/src/components/hello_world.rs b/src/components/hello_world.rs
index 2e3a2ee..ac9d02b 100644
--- a/src/components/hello_world.rs
+++ b/src/components/hello_world.rs
@@ -1,9 +1,7 @@
-use ratatui::prelude::*;
-use ratatui::widgets::Paragraph;
+use ratatui::prelude::{Frame, Rect};
+use ratatui::widgets::{Paragraph, Wrap};
-use crate::app_action::AppAction;
use crate::component::Component;
-use crate::keys::key_commands::*;
#[derive(Default, Clone)]
pub struct HelloWorld {
@@ -16,17 +14,11 @@ impl Component for HelloWorld {
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<()> {
-
- frame.render_widget(Paragraph::new(self.text.clone()), rect);
+ frame.render_widget(
+ Paragraph::new(self.text.clone()).wrap(Wrap { trim: true }),
+ rect,
+ );
Ok(())
}
diff --git a/src/keys/key_commands.rs b/src/keys/key_commands.rs
index 6124560..70e81b1 100644
--- a/src/keys/key_commands.rs
+++ b/src/keys/key_commands.rs
@@ -32,15 +32,14 @@ pub fn serialize_key_event(event: KeyEvent) -> String {
let char;
let key = match event.code {
- KeyCode::Backspace => "del",
+ KeyCode::Backspace | KeyCode::Delete => "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(' ') => "space",
KeyCode::Char(c) => {
char = c.to_string();
&char
@@ -48,7 +47,7 @@ pub fn serialize_key_event(event: KeyEvent) -> String {
KeyCode::Esc => "esc",
_ => "",
};
- let separator = if modifiers.len() > 0 { "-" } else { "" };
+ let separator = if modifiers.is_empty() { "-" } else { "" };
let serialized_event =
format!("{}{}{}", modifiers.join("-"), separator, key);
diff --git a/src/tui.rs b/src/tui.rs
index d8ddb90..546e939 100644
--- a/src/tui.rs
+++ b/src/tui.rs
@@ -1,5 +1,9 @@
-use crossterm::{event, event::Event, execute, terminal::*};
-use ratatui::prelude::*;
+use crossterm::terminal::{
+ 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::{stdout, Stdout};