From 145827913af42c740433e0a138849f95d0377576 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 12 Jul 2024 15:39:38 +0300 Subject: [PATCH] wip: command palette --- src/control.rs | 18 ++++++---- src/core/render.rs | 28 +++++++++++++++ src/view.rs | 18 +++++----- src/view/help.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 src/view/help.rs diff --git a/src/control.rs b/src/control.rs index 72b1357e..14450e7d 100644 --- a/src/control.rs +++ b/src/control.rs @@ -46,7 +46,11 @@ fn handle_device (state: &mut App, e: &AppEvent) -> Usually { .map(|x|x.unwrap_or(false)) } -const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { +pub const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { + [Char(';'), NONE, "command", "open command palette", |app: &mut App| { + app.modal = Some(Box::new(crate::view::HelpModal::new())); + Ok(true) + }], [Tab, NONE, "focus_next", "focus next area", focus_next], [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], [Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{ @@ -59,8 +63,8 @@ const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { }], }); -const KEYMAP: &'static [KeyBinding] = keymap!(App { - [F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}], +pub const KEYMAP: &'static [KeyBinding] = keymap!(App { + //[F(1), NONE, "help_toggle", "toggle help", |_: &mut App| {Ok(true)}], [Up, NONE, "focus_prev", "focus previous area", |app: &mut App|match app.track_cursor { 0 => {app.section = AppSection::Arranger;Ok(true)}, @@ -88,20 +92,20 @@ const KEYMAP: &'static [KeyBinding] = keymap!(App { Ok(true) }], - [Char('+'), NONE, "quant_inc", "Quantize coarser", |app: &mut App| { + [Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| { app.transport.quant = next_note_length(app.transport.quant); Ok(true) }], - [Char('_'), NONE, "quant_dec", "Quantize finer", |app: &mut App| { + [Char('_'), NONE, "quant_dec", "quantize finer", |app: &mut App| { app.transport.quant = prev_note_length(app.transport.quant); Ok(true) }], - [Char('='), NONE, "zoom_in", "Zoom in", |app: &mut App| { + [Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| { app.seq_buf.time_zoom = prev_note_length(app.seq_buf.time_zoom); Ok(true) }], - [Char('-'), NONE, "zoom_out", "Zoom out", |app: &mut App| { + [Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| { app.seq_buf.time_zoom = next_note_length(app.seq_buf.time_zoom); Ok(true) }], diff --git a/src/core/render.rs b/src/core/render.rs index 0e5ce44b..f8a80d82 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -5,6 +5,20 @@ pub(crate) use ratatui::layout::Rect; pub(crate) use ratatui::buffer::{Buffer, Cell}; use ratatui::widgets::WidgetRef; +pub fn fill_fg (buf: &mut Buffer, area: Rect, color: Color) { + let Rect { x, y, width, height } = area; + for y in y..y+height { + if y >= buf.area.height { + break + } + for x in x..x+width { + if x >= buf.area.width { + break + } + buf.get_mut(x, y).set_fg(color); + } + } +} pub fn fill_bg (buf: &mut Buffer, area: Rect, color: Color) { let Rect { x, y, width, height } = area; for y in y..y+height { @@ -19,6 +33,20 @@ pub fn fill_bg (buf: &mut Buffer, area: Rect, color: Color) { } } } +pub fn fill_char (buf: &mut Buffer, area: Rect, c: char) { + let Rect { x, y, width, height } = area; + for y in y..y+height { + if y >= buf.area.height { + break + } + for x in x..x+width { + if x >= buf.area.width { + break + } + buf.get_mut(x, y).set_char(c); + } + } +} pub trait Blit { // Render something to X, Y coordinates in a buffer, ignoring width/height. diff --git a/src/view.rs b/src/view.rs index 4cc656bd..0477345b 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,18 +1,20 @@ -pub mod chain; pub mod arranger; -pub mod sequencer; -pub mod transport; -pub mod plugin; pub mod border; -pub mod theme; +pub mod chain; +pub mod help; +pub mod plugin; +pub mod sequencer; pub mod split; +pub mod theme; +pub mod transport; -pub use self::split::*; -pub use self::border::*; -pub use self::theme::*; pub use self::arranger::*; +pub use self::border::*; pub use self::chain::ChainView; +pub use self::help::*; pub use self::sequencer::{SequencerView, BufferedSequencerView}; +pub use self::split::*; +pub use self::theme::*; use crate::{render, App, core::*}; diff --git a/src/view/help.rs b/src/view/help.rs new file mode 100644 index 00000000..483b4c26 --- /dev/null +++ b/src/view/help.rs @@ -0,0 +1,88 @@ +use crate::{core::*, view::*}; + +pub struct HelpModal { + cursor: usize, + search: Option, +} + +impl HelpModal { + pub fn new () -> Self { + Self { cursor: 0, search: None } + } +} + +render!(HelpModal |self, buf, area|{ + for cell in buf.content.iter_mut() { + cell.bg = ratatui::style::Color::Rgb(44,44,44); + cell.fg = ratatui::style::Color::Rgb(88,88,88); + cell.modifier = ratatui::style::Modifier::DIM; + } + let width = 64.min(area.width * 3 / 5); + let height = 20.min(area.width * 3 / 5); + let x = area.x + (area.width - width) / 2; + let y = area.y + (area.height - height) / 2; + let area = Rect { x, y, width, height }; + fill_fg(buf, area, Color::Reset); + fill_bg(buf, area, Nord::bg_lo(true, true)); + fill_char(buf, area, ' '); + let x = area.x + 2; + let y = area.y + 1; + "Command:" + .blit(buf, x, y, Some(Style::default().bold()))?; + " ".repeat(area.width as usize - 13) + .blit(buf, x + 9, y, Some(Style::default().bg(Color::Reset)))?; + if let Some(search) = self.search.as_ref() { + search.blit(buf, x + 9, y, Some(Style::default().not_dim()))?; + } + let y = y + 1; + fill_char(buf, Rect { y, height: 1, ..area }, '-'); + let y = y + 1; + for i in 0..height-3 { + let y = y + i; + if let Some(command) = crate::control::KEYMAP_FOCUS.get(i as usize) { + format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().bold()))?; + command.2.blit(buf, x + 11, y, Some(Style::default().bold()))?; + command.3.blit(buf, x + 26, y, None)?; + } else if let Some(command) = crate::control::KEYMAP.get((i as usize) - crate::control::KEYMAP_FOCUS.len()) { + format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().bold()))?; + command.2.blit(buf, x + 11, y, Some(Style::default().bold()))?; + command.3.blit(buf, x + 26, y, None)?; + } else { + break + } + } + let hi_area = Rect { x: area.x + 1, width: area.width - 2, y: area.y + 3 + self.cursor as u16, height: 1 }; + fill_bg(buf, hi_area, Nord::bg_hi(true, true)); + fill_fg(buf, hi_area, Color::Reset); + Lozenge(Style::default()).draw(buf, area) +}); + +handle!(HelpModal |self, e| { + Ok(handle_keymap(self, e, KEYMAP_HELP)? || match e { + AppEvent::Input(Event::Key(KeyEvent { + code: KeyCode::Char(c), + modifiers: KeyModifiers::NONE, .. + })) => { + if self.search.is_none() { + self.search = Some(String::new()); + } + self.search.as_mut().unwrap().push(*c); + false + }, + _ => false + }) +}); + +pub const KEYMAP_HELP: &'static [KeyBinding] = keymap!(HelpModal { + [Esc, NONE, "help_close", "close help dialog", |_: &mut HelpModal|{ + Ok(true) + }], + [Up, NONE, "help_prev", "select previous command", |modal: &mut HelpModal|{ + modal.cursor = modal.cursor.saturating_sub(1); + Ok(false) + }], + [Down, NONE, "help_next", "select next command", |modal: &mut HelpModal|{ + modal.cursor = modal.cursor + 1; + Ok(false) + }], +});