From 591d2b6167af53ce07b060711a4074f1e19c5f3f Mon Sep 17 00:00:00 2001 From: Akshay Date: Sat, 8 May 2021 21:25:47 +0530 Subject: add basic user-definable keybinds --- src/app.rs | 69 ++++++++++++++++++-------- src/keybind.rs | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lisp/error.rs | 13 +++-- src/lisp/eval.rs | 20 +++++++- src/lisp/std.lisp | 15 ++++++ src/main.rs | 1 + 6 files changed, 237 insertions(+), 27 deletions(-) create mode 100644 src/keybind.rs diff --git a/src/app.rs b/src/app.rs index 70d68b8..e69794f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,7 +8,8 @@ use crate::{ error::AppError, grid::Grid, guide::Guide, - lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList}, + keybind::Keybind, + lisp::{eval, expr::LispExpr, lex::Lexer, parse::Parser, prelude, EnvList}, message::Message, rect, symmetry::Symmetry, @@ -59,6 +60,7 @@ pub struct AppState<'ctx> { pub grid: Grid, pub minimap: bool, pub lisp_env: EnvList, + pub keybinds: HashMap, pub message: Message, pub mode: Mode, pub mouse: (i32, i32), @@ -303,21 +305,23 @@ impl<'ctx> AppState<'ctx> { } } + pub fn eval_expr(&mut self, expr: &LispExpr) { + let mut evaluator = eval::Evaluator { + app: self, + context: Vec::new(), + }; + match evaluator.eval(expr) { + Ok(val) => self.message.set_info(format!("{}", val)), + Err(eval_err) => self.message.set_error(format!("{}", eval_err)), + } + } + pub fn eval_command(&mut self) { let lisp_expr = &self.command_box.text; let mut parser = Parser::new(Lexer::new(lisp_expr)); let res = parser.parse_single_expr(); match res { - Ok(expr) => { - let mut evaluator = eval::Evaluator { - app: self, - context: Vec::new(), - }; - match evaluator.eval(&expr) { - Ok(val) => self.message.set_info(format!("{}", val)), - Err(eval_err) => self.message.set_error(format!("{}", eval_err)), - } - } + Ok(expr) => self.eval_expr(&expr), Err(err) => self.message = handle_error(err, &lisp_expr, "repl"), } self.command_box.hist_append(); @@ -612,11 +616,11 @@ impl<'ctx> AppState<'ctx> { self.grid .draw(&mut self.canvas, self.zoom, &self.start, width, height); } + self.draw_guides(); + self.draw_symmetry(); if self.minimap { self.draw_minimap(); } - self.draw_guides(); - self.draw_symmetry(); self.draw_statusline(); self.draw_command_box(); self.draw_brush(); @@ -663,23 +667,24 @@ impl<'ctx> AppState<'ctx> { let mut app = Self { active_color: true, brush: Brush::new(0), + cache: RefCell::new(None), canvas, command_box: CommandBox::new(), context, - cache: RefCell::new(None), - guides: HashMap::new(), current_operation: Vec::new(), dither_level: 16, file_name, grid: Grid::new(), + guides: HashMap::new(), + keybinds: HashMap::new(), lisp_env: vec![prelude::new_env().map_err(AppError::Lisp)?], message: Message::new().text(" "), - mode: Mode::Draw, minimap: false, + mode: Mode::Draw, mouse: (0, 0), + pan_start: Point::new(0, 0), pixmap, start: Point::new(60, 60), - pan_start: Point::new(0, 0), symmetry: Default::default(), ttf_context, undo_stack: UndoStack::new(), @@ -732,7 +737,7 @@ impl<'ctx> AppState<'ctx> { match event { Event::KeyDown { keycode: Some(k), - keymod, + keymod: Mod::NOMOD, .. } => { match k { @@ -742,9 +747,9 @@ impl<'ctx> AppState<'ctx> { Keycode::S => self.pan((0, -10)), Keycode::D => self.pan((-10, 0)), // zoom - Keycode::C if keymod == Mod::LSHIFTMOD => { - self.center_grid(); - } + // Keycode::C if keymod == Mod::LSHIFTMOD => { + // self.center_grid(); + // } Keycode::C => { let cursor = (mouse.x(), mouse.y()); self.zoom_in(cursor); @@ -799,7 +804,7 @@ impl<'ctx> AppState<'ctx> { } continue; } - _ if keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD => { + Keycode::LCtrl | Keycode::RCtrl => { self.brush = Brush::line( self.cache .borrow() @@ -812,6 +817,26 @@ impl<'ctx> AppState<'ctx> { _ => (), } } + Event::KeyDown { + keycode: Some(k), + keymod, + .. + } if self.keybinds.contains_key(&Keybind::new(k, keymod)) => { + let body = self.keybinds.get(&Keybind::new(k, keymod)).unwrap(); + self.eval_expr(&body.clone()); + } + Event::KeyDown { + keycode: Some(k), .. + } if k == Keycode::LCtrl || k == Keycode::RCtrl => { + self.brush = Brush::line( + self.cache + .borrow() + .as_ref() + .map(|c| c.last_brush.size().unwrap_or(0)) + .unwrap_or(0), + true, + ); + } Event::KeyUp { keycode: Some(k), .. } if k == Keycode::LCtrl || k == Keycode::RCtrl => { diff --git a/src/keybind.rs b/src/keybind.rs new file mode 100644 index 0000000..8ab479a --- /dev/null +++ b/src/keybind.rs @@ -0,0 +1,146 @@ +use std::{fmt, str::FromStr}; + +use sdl2::keyboard::{Keycode, Mod}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Keybind { + keycode: Keycode, + keymod: Mod, +} + +impl Keybind { + pub fn new(keycode: Keycode, keymod: Mod) -> Self { + Self { keycode, keymod } + } +} + +// ctrl keys: C- len 3 +// shift keys: S- len 3 +// normal keys: len 1 + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum KeybindError { + Parse, + InvalidMod, + InvalidKey, +} + +impl fmt::Display for KeybindError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parse => write!(f, "error parsing keybind"), + Self::InvalidKey => write!(f, "unsupported keybind, try `A-Z0-9`"), + Self::InvalidMod => { + write!(f, "unsupported keybind modifier, try `C- or S-`") + } + } + } +} + +impl std::error::Error for KeybindError {} + +impl FromStr for Keybind { + type Err = KeybindError; + fn from_str(s: &str) -> Result { + parse_from_str(s) + } +} + +fn parse_from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.len() { + // either ctrl or shift key + 3 => { + let keymod = parse_modifier(&s[..2])?; + let keybind = parse_keybind(&s[2..])?; + Ok(Keybind::new(keybind, keymod)) + } + 1 => { + let keybind = parse_keybind(&s)?; + Ok(Keybind::new(keybind, Mod::NOMOD)) + } + _ => Err(KeybindError::Parse), + } +} + +fn parse_modifier(s: &str) -> Result { + match s { + "c-" => Ok(Mod::LCTRLMOD), + "s-" => Ok(Mod::LSHIFTMOD), + _ => Err(KeybindError::InvalidMod), + } +} + +fn parse_keybind(s: &str) -> Result { + match s.to_uppercase().as_str() { + "0" => Ok(Keycode::Num0), + "1" => Ok(Keycode::Num1), + "2" => Ok(Keycode::Num2), + "3" => Ok(Keycode::Num3), + "4" => Ok(Keycode::Num4), + "5" => Ok(Keycode::Num5), + "6" => Ok(Keycode::Num6), + "7" => Ok(Keycode::Num7), + "8" => Ok(Keycode::Num8), + "9" => Ok(Keycode::Num9), + "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" + | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" => { + Ok(Keycode::from_name(s).unwrap()) + } + _ => Err(KeybindError::InvalidKey), + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn without_mod() { + assert_eq!( + Keybind::from_str("x").unwrap(), + Keybind::new(Keycode::X, Mod::NOMOD) + ); + assert_eq!( + Keybind::from_str("X").unwrap(), + Keybind::new(Keycode::X, Mod::NOMOD) + ); + } + #[test] + fn with_ctrl_mod() { + assert_eq!( + Keybind::from_str("c-x").unwrap(), + Keybind::new(Keycode::X, Mod::LCTRLMOD) + ); + assert_eq!( + Keybind::from_str("C-x").unwrap(), + Keybind::new(Keycode::X, Mod::LCTRLMOD) + ); + assert_eq!( + Keybind::from_str("c-X").unwrap(), + Keybind::new(Keycode::X, Mod::LCTRLMOD) + ); + assert_eq!( + Keybind::from_str("C-X").unwrap(), + Keybind::new(Keycode::X, Mod::LCTRLMOD) + ); + } + #[test] + fn with_shift_mod() { + assert_eq!( + Keybind::from_str("s-x").unwrap(), + Keybind::new(Keycode::X, Mod::LSHIFTMOD) + ); + assert_eq!( + Keybind::from_str("S-x").unwrap(), + Keybind::new(Keycode::X, Mod::LSHIFTMOD) + ); + assert_eq!( + Keybind::from_str("s-X").unwrap(), + Keybind::new(Keycode::X, Mod::LSHIFTMOD) + ); + assert_eq!( + Keybind::from_str("S-X").unwrap(), + Keybind::new(Keycode::X, Mod::LSHIFTMOD) + ); + } +} diff --git a/src/lisp/error.rs b/src/lisp/error.rs index 9e9dc90..7bde872 100644 --- a/src/lisp/error.rs +++ b/src/lisp/error.rs @@ -1,7 +1,10 @@ -use crate::lisp::{ - expr::{Arity, LispExpr}, - lex::{Span, SpanDisplay}, - number::LispNumber, +use crate::{ + keybind::KeybindError, + lisp::{ + expr::{Arity, LispExpr}, + lex::{Span, SpanDisplay}, + number::LispNumber, + }, }; use std::{fmt, io}; @@ -104,6 +107,7 @@ pub enum EvalError { AssertionError { expected: LispExpr, got: LispExpr }, ScriptLoadError(io::Error), CustomInternal(&'static str), + KeybindError(KeybindError), Custom(String), } @@ -134,6 +138,7 @@ impl fmt::Display for EvalError { write!(f, "assertion error: expected `{}` got `{}`", expected, got) } Self::ScriptLoadError(s) => write!(f, "error while loading script: {}", s), + Self::KeybindError(k) => write!(f, "keybind error: {}", k), Self::CustomInternal(s) => write!(f, "{}", s), Self::Custom(s) => write!(f, "error: {}", s), } diff --git a/src/lisp/eval.rs b/src/lisp/eval.rs index 75cb5c9..677fa23 100644 --- a/src/lisp/eval.rs +++ b/src/lisp/eval.rs @@ -1,5 +1,6 @@ use crate::{ app::AppState, + keybind::Keybind, lisp::{ error::{EvalError, LispError}, expr::{Arity, Ident, LispExpr, LispFunction}, @@ -8,7 +9,7 @@ use crate::{ type_match, }; -use std::convert::TryInto; +use std::{convert::TryInto, str::FromStr}; use log::{error, info}; @@ -44,6 +45,7 @@ where "for" => self.eval_for(&li[1..]), "quote" => Ok(apply_quote(&li[1])), "let" => self.eval_let(&li[1..]), + "bind-key" => self.eval_bind_key(&li[1..]), _ => { let mut new_ls = vec![self.eval(&func_expr)?]; new_ls.extend(li[1..].to_vec()); @@ -302,6 +304,22 @@ where } } } + + pub fn eval_bind_key(&mut self, args: &[LispExpr]) -> Result { + let arity = Arity::Exact(2); + if !arity.check(args) { + Err(arity.to_error()) + } else { + match args { + [LispExpr::StringLit(s), body] => { + let bind = Keybind::from_str(&s).map_err(EvalError::KeybindError)?; + self.app.keybinds.insert(bind, body.clone()); + Ok(LispExpr::Unit) + } + _ => Err(EvalError::BadForm.into()), + } + } + } } pub fn apply_quote(arg: &LispExpr) -> LispExpr { diff --git a/src/lisp/std.lisp b/src/lisp/std.lisp index a256125..fe09a8a 100644 --- a/src/lisp/std.lisp +++ b/src/lisp/std.lisp @@ -72,3 +72,18 @@ acc (rev-helper (cdr p) (cons (car p) acc)))) (rev-helper ls '()))) + +(define (append l1 l2) + (if (null? l1) + l2 + (cons (car l1) + (append (cdr l1) l2)))) + +(define (cross xs ys) + (if (or (null? xs) + (null? ys)) + '() + (fold '() + append + (map (lambda (x) + (map (lambda (y) (list x y)) ys)) xs)))) diff --git a/src/main.rs b/src/main.rs index 225c37c..c933e2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ mod dither; mod error; mod grid; mod guide; +mod keybind; mod lisp; mod message; mod symmetry; -- cgit v1.2.3