diff options
author | Akshay <[email protected]> | 2021-05-08 16:55:47 +0100 |
---|---|---|
committer | Akshay <[email protected]> | 2021-05-08 16:55:47 +0100 |
commit | 591d2b6167af53ce07b060711a4074f1e19c5f3f (patch) | |
tree | 6299e12574732b063aa57c977104eacf938e4eda | |
parent | 225351e7c98e91451e0b02fd2b9c3060ebd153a7 (diff) |
add basic user-definable keybinds
-rw-r--r-- | src/app.rs | 69 | ||||
-rw-r--r-- | src/keybind.rs | 146 | ||||
-rw-r--r-- | src/lisp/error.rs | 13 | ||||
-rw-r--r-- | src/lisp/eval.rs | 20 | ||||
-rw-r--r-- | src/lisp/std.lisp | 15 | ||||
-rw-r--r-- | src/main.rs | 1 |
6 files changed, 237 insertions, 27 deletions
@@ -8,7 +8,8 @@ use crate::{ | |||
8 | error::AppError, | 8 | error::AppError, |
9 | grid::Grid, | 9 | grid::Grid, |
10 | guide::Guide, | 10 | guide::Guide, |
11 | lisp::{eval, lex::Lexer, parse::Parser, prelude, EnvList}, | 11 | keybind::Keybind, |
12 | lisp::{eval, expr::LispExpr, lex::Lexer, parse::Parser, prelude, EnvList}, | ||
12 | message::Message, | 13 | message::Message, |
13 | rect, | 14 | rect, |
14 | symmetry::Symmetry, | 15 | symmetry::Symmetry, |
@@ -59,6 +60,7 @@ pub struct AppState<'ctx> { | |||
59 | pub grid: Grid, | 60 | pub grid: Grid, |
60 | pub minimap: bool, | 61 | pub minimap: bool, |
61 | pub lisp_env: EnvList, | 62 | pub lisp_env: EnvList, |
63 | pub keybinds: HashMap<Keybind, LispExpr>, | ||
62 | pub message: Message, | 64 | pub message: Message, |
63 | pub mode: Mode, | 65 | pub mode: Mode, |
64 | pub mouse: (i32, i32), | 66 | pub mouse: (i32, i32), |
@@ -303,21 +305,23 @@ impl<'ctx> AppState<'ctx> { | |||
303 | } | 305 | } |
304 | } | 306 | } |
305 | 307 | ||
308 | pub fn eval_expr(&mut self, expr: &LispExpr) { | ||
309 | let mut evaluator = eval::Evaluator { | ||
310 | app: self, | ||
311 | context: Vec::new(), | ||
312 | }; | ||
313 | match evaluator.eval(expr) { | ||
314 | Ok(val) => self.message.set_info(format!("{}", val)), | ||
315 | Err(eval_err) => self.message.set_error(format!("{}", eval_err)), | ||
316 | } | ||
317 | } | ||
318 | |||
306 | pub fn eval_command(&mut self) { | 319 | pub fn eval_command(&mut self) { |
307 | let lisp_expr = &self.command_box.text; | 320 | let lisp_expr = &self.command_box.text; |
308 | let mut parser = Parser::new(Lexer::new(lisp_expr)); | 321 | let mut parser = Parser::new(Lexer::new(lisp_expr)); |
309 | let res = parser.parse_single_expr(); | 322 | let res = parser.parse_single_expr(); |
310 | match res { | 323 | match res { |
311 | Ok(expr) => { | 324 | Ok(expr) => self.eval_expr(&expr), |
312 | let mut evaluator = eval::Evaluator { | ||
313 | app: self, | ||
314 | context: Vec::new(), | ||
315 | }; | ||
316 | match evaluator.eval(&expr) { | ||
317 | Ok(val) => self.message.set_info(format!("{}", val)), | ||
318 | Err(eval_err) => self.message.set_error(format!("{}", eval_err)), | ||
319 | } | ||
320 | } | ||
321 | Err(err) => self.message = handle_error(err, &lisp_expr, "repl"), | 325 | Err(err) => self.message = handle_error(err, &lisp_expr, "repl"), |
322 | } | 326 | } |
323 | self.command_box.hist_append(); | 327 | self.command_box.hist_append(); |
@@ -612,11 +616,11 @@ impl<'ctx> AppState<'ctx> { | |||
612 | self.grid | 616 | self.grid |
613 | .draw(&mut self.canvas, self.zoom, &self.start, width, height); | 617 | .draw(&mut self.canvas, self.zoom, &self.start, width, height); |
614 | } | 618 | } |
619 | self.draw_guides(); | ||
620 | self.draw_symmetry(); | ||
615 | if self.minimap { | 621 | if self.minimap { |
616 | self.draw_minimap(); | 622 | self.draw_minimap(); |
617 | } | 623 | } |
618 | self.draw_guides(); | ||
619 | self.draw_symmetry(); | ||
620 | self.draw_statusline(); | 624 | self.draw_statusline(); |
621 | self.draw_command_box(); | 625 | self.draw_command_box(); |
622 | self.draw_brush(); | 626 | self.draw_brush(); |
@@ -663,23 +667,24 @@ impl<'ctx> AppState<'ctx> { | |||
663 | let mut app = Self { | 667 | let mut app = Self { |
664 | active_color: true, | 668 | active_color: true, |
665 | brush: Brush::new(0), | 669 | brush: Brush::new(0), |
670 | cache: RefCell::new(None), | ||
666 | canvas, | 671 | canvas, |
667 | command_box: CommandBox::new(), | 672 | command_box: CommandBox::new(), |
668 | context, | 673 | context, |
669 | cache: RefCell::new(None), | ||
670 | guides: HashMap::new(), | ||
671 | current_operation: Vec::new(), | 674 | current_operation: Vec::new(), |
672 | dither_level: 16, | 675 | dither_level: 16, |
673 | file_name, | 676 | file_name, |
674 | grid: Grid::new(), | 677 | grid: Grid::new(), |
678 | guides: HashMap::new(), | ||
679 | keybinds: HashMap::new(), | ||
675 | lisp_env: vec![prelude::new_env().map_err(AppError::Lisp)?], | 680 | lisp_env: vec![prelude::new_env().map_err(AppError::Lisp)?], |
676 | message: Message::new().text(" "), | 681 | message: Message::new().text(" "), |
677 | mode: Mode::Draw, | ||
678 | minimap: false, | 682 | minimap: false, |
683 | mode: Mode::Draw, | ||
679 | mouse: (0, 0), | 684 | mouse: (0, 0), |
685 | pan_start: Point::new(0, 0), | ||
680 | pixmap, | 686 | pixmap, |
681 | start: Point::new(60, 60), | 687 | start: Point::new(60, 60), |
682 | pan_start: Point::new(0, 0), | ||
683 | symmetry: Default::default(), | 688 | symmetry: Default::default(), |
684 | ttf_context, | 689 | ttf_context, |
685 | undo_stack: UndoStack::new(), | 690 | undo_stack: UndoStack::new(), |
@@ -732,7 +737,7 @@ impl<'ctx> AppState<'ctx> { | |||
732 | match event { | 737 | match event { |
733 | Event::KeyDown { | 738 | Event::KeyDown { |
734 | keycode: Some(k), | 739 | keycode: Some(k), |
735 | keymod, | 740 | keymod: Mod::NOMOD, |
736 | .. | 741 | .. |
737 | } => { | 742 | } => { |
738 | match k { | 743 | match k { |
@@ -742,9 +747,9 @@ impl<'ctx> AppState<'ctx> { | |||
742 | Keycode::S => self.pan((0, -10)), | 747 | Keycode::S => self.pan((0, -10)), |
743 | Keycode::D => self.pan((-10, 0)), | 748 | Keycode::D => self.pan((-10, 0)), |
744 | // zoom | 749 | // zoom |
745 | Keycode::C if keymod == Mod::LSHIFTMOD => { | 750 | // Keycode::C if keymod == Mod::LSHIFTMOD => { |
746 | self.center_grid(); | 751 | // self.center_grid(); |
747 | } | 752 | // } |
748 | Keycode::C => { | 753 | Keycode::C => { |
749 | let cursor = (mouse.x(), mouse.y()); | 754 | let cursor = (mouse.x(), mouse.y()); |
750 | self.zoom_in(cursor); | 755 | self.zoom_in(cursor); |
@@ -799,7 +804,7 @@ impl<'ctx> AppState<'ctx> { | |||
799 | } | 804 | } |
800 | continue; | 805 | continue; |
801 | } | 806 | } |
802 | _ if keymod == Mod::LCTRLMOD || keymod == Mod::RCTRLMOD => { | 807 | Keycode::LCtrl | Keycode::RCtrl => { |
803 | self.brush = Brush::line( | 808 | self.brush = Brush::line( |
804 | self.cache | 809 | self.cache |
805 | .borrow() | 810 | .borrow() |
@@ -812,6 +817,26 @@ impl<'ctx> AppState<'ctx> { | |||
812 | _ => (), | 817 | _ => (), |
813 | } | 818 | } |
814 | } | 819 | } |
820 | Event::KeyDown { | ||
821 | keycode: Some(k), | ||
822 | keymod, | ||
823 | .. | ||
824 | } if self.keybinds.contains_key(&Keybind::new(k, keymod)) => { | ||
825 | let body = self.keybinds.get(&Keybind::new(k, keymod)).unwrap(); | ||
826 | self.eval_expr(&body.clone()); | ||
827 | } | ||
828 | Event::KeyDown { | ||
829 | keycode: Some(k), .. | ||
830 | } if k == Keycode::LCtrl || k == Keycode::RCtrl => { | ||
831 | self.brush = Brush::line( | ||
832 | self.cache | ||
833 | .borrow() | ||
834 | .as_ref() | ||
835 | .map(|c| c.last_brush.size().unwrap_or(0)) | ||
836 | .unwrap_or(0), | ||
837 | true, | ||
838 | ); | ||
839 | } | ||
815 | Event::KeyUp { | 840 | Event::KeyUp { |
816 | keycode: Some(k), .. | 841 | keycode: Some(k), .. |
817 | } if k == Keycode::LCtrl || k == Keycode::RCtrl => { | 842 | } 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 @@ | |||
1 | use std::{fmt, str::FromStr}; | ||
2 | |||
3 | use sdl2::keyboard::{Keycode, Mod}; | ||
4 | |||
5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] | ||
6 | pub struct Keybind { | ||
7 | keycode: Keycode, | ||
8 | keymod: Mod, | ||
9 | } | ||
10 | |||
11 | impl Keybind { | ||
12 | pub fn new(keycode: Keycode, keymod: Mod) -> Self { | ||
13 | Self { keycode, keymod } | ||
14 | } | ||
15 | } | ||
16 | |||
17 | // ctrl keys: C-<letter> len 3 | ||
18 | // shift keys: S-<letter> len 3 | ||
19 | // normal keys: <letter> len 1 | ||
20 | |||
21 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
22 | pub enum KeybindError { | ||
23 | Parse, | ||
24 | InvalidMod, | ||
25 | InvalidKey, | ||
26 | } | ||
27 | |||
28 | impl fmt::Display for KeybindError { | ||
29 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
30 | match self { | ||
31 | Self::Parse => write!(f, "error parsing keybind"), | ||
32 | Self::InvalidKey => write!(f, "unsupported keybind, try `A-Z0-9`"), | ||
33 | Self::InvalidMod => { | ||
34 | write!(f, "unsupported keybind modifier, try `C-<key> or S-<key>`") | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | impl std::error::Error for KeybindError {} | ||
41 | |||
42 | impl FromStr for Keybind { | ||
43 | type Err = KeybindError; | ||
44 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
45 | parse_from_str(s) | ||
46 | } | ||
47 | } | ||
48 | |||
49 | fn parse_from_str(s: &str) -> Result<Keybind, KeybindError> { | ||
50 | let s = s.to_lowercase(); | ||
51 | match s.len() { | ||
52 | // either ctrl or shift key | ||
53 | 3 => { | ||
54 | let keymod = parse_modifier(&s[..2])?; | ||
55 | let keybind = parse_keybind(&s[2..])?; | ||
56 | Ok(Keybind::new(keybind, keymod)) | ||
57 | } | ||
58 | 1 => { | ||
59 | let keybind = parse_keybind(&s)?; | ||
60 | Ok(Keybind::new(keybind, Mod::NOMOD)) | ||
61 | } | ||
62 | _ => Err(KeybindError::Parse), | ||
63 | } | ||
64 | } | ||
65 | |||
66 | fn parse_modifier(s: &str) -> Result<Mod, KeybindError> { | ||
67 | match s { | ||
68 | "c-" => Ok(Mod::LCTRLMOD), | ||
69 | "s-" => Ok(Mod::LSHIFTMOD), | ||
70 | _ => Err(KeybindError::InvalidMod), | ||
71 | } | ||
72 | } | ||
73 | |||
74 | fn parse_keybind(s: &str) -> Result<Keycode, KeybindError> { | ||
75 | match s.to_uppercase().as_str() { | ||
76 | "0" => Ok(Keycode::Num0), | ||
77 | "1" => Ok(Keycode::Num1), | ||
78 | "2" => Ok(Keycode::Num2), | ||
79 | "3" => Ok(Keycode::Num3), | ||
80 | "4" => Ok(Keycode::Num4), | ||
81 | "5" => Ok(Keycode::Num5), | ||
82 | "6" => Ok(Keycode::Num6), | ||
83 | "7" => Ok(Keycode::Num7), | ||
84 | "8" => Ok(Keycode::Num8), | ||
85 | "9" => Ok(Keycode::Num9), | ||
86 | "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | ||
87 | | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" => { | ||
88 | Ok(Keycode::from_name(s).unwrap()) | ||
89 | } | ||
90 | _ => Err(KeybindError::InvalidKey), | ||
91 | } | ||
92 | } | ||
93 | |||
94 | #[cfg(test)] | ||
95 | mod tests { | ||
96 | use super::*; | ||
97 | #[test] | ||
98 | fn without_mod() { | ||
99 | assert_eq!( | ||
100 | Keybind::from_str("x").unwrap(), | ||
101 | Keybind::new(Keycode::X, Mod::NOMOD) | ||
102 | ); | ||
103 | assert_eq!( | ||
104 | Keybind::from_str("X").unwrap(), | ||
105 | Keybind::new(Keycode::X, Mod::NOMOD) | ||
106 | ); | ||
107 | } | ||
108 | #[test] | ||
109 | fn with_ctrl_mod() { | ||
110 | assert_eq!( | ||
111 | Keybind::from_str("c-x").unwrap(), | ||
112 | Keybind::new(Keycode::X, Mod::LCTRLMOD) | ||
113 | ); | ||
114 | assert_eq!( | ||
115 | Keybind::from_str("C-x").unwrap(), | ||
116 | Keybind::new(Keycode::X, Mod::LCTRLMOD) | ||
117 | ); | ||
118 | assert_eq!( | ||
119 | Keybind::from_str("c-X").unwrap(), | ||
120 | Keybind::new(Keycode::X, Mod::LCTRLMOD) | ||
121 | ); | ||
122 | assert_eq!( | ||
123 | Keybind::from_str("C-X").unwrap(), | ||
124 | Keybind::new(Keycode::X, Mod::LCTRLMOD) | ||
125 | ); | ||
126 | } | ||
127 | #[test] | ||
128 | fn with_shift_mod() { | ||
129 | assert_eq!( | ||
130 | Keybind::from_str("s-x").unwrap(), | ||
131 | Keybind::new(Keycode::X, Mod::LSHIFTMOD) | ||
132 | ); | ||
133 | assert_eq!( | ||
134 | Keybind::from_str("S-x").unwrap(), | ||
135 | Keybind::new(Keycode::X, Mod::LSHIFTMOD) | ||
136 | ); | ||
137 | assert_eq!( | ||
138 | Keybind::from_str("s-X").unwrap(), | ||
139 | Keybind::new(Keycode::X, Mod::LSHIFTMOD) | ||
140 | ); | ||
141 | assert_eq!( | ||
142 | Keybind::from_str("S-X").unwrap(), | ||
143 | Keybind::new(Keycode::X, Mod::LSHIFTMOD) | ||
144 | ); | ||
145 | } | ||
146 | } | ||
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 @@ | |||
1 | use crate::lisp::{ | 1 | use crate::{ |
2 | expr::{Arity, LispExpr}, | 2 | keybind::KeybindError, |
3 | lex::{Span, SpanDisplay}, | 3 | lisp::{ |
4 | number::LispNumber, | 4 | expr::{Arity, LispExpr}, |
5 | lex::{Span, SpanDisplay}, | ||
6 | number::LispNumber, | ||
7 | }, | ||
5 | }; | 8 | }; |
6 | 9 | ||
7 | use std::{fmt, io}; | 10 | use std::{fmt, io}; |
@@ -104,6 +107,7 @@ pub enum EvalError { | |||
104 | AssertionError { expected: LispExpr, got: LispExpr }, | 107 | AssertionError { expected: LispExpr, got: LispExpr }, |
105 | ScriptLoadError(io::Error), | 108 | ScriptLoadError(io::Error), |
106 | CustomInternal(&'static str), | 109 | CustomInternal(&'static str), |
110 | KeybindError(KeybindError), | ||
107 | Custom(String), | 111 | Custom(String), |
108 | } | 112 | } |
109 | 113 | ||
@@ -134,6 +138,7 @@ impl fmt::Display for EvalError { | |||
134 | write!(f, "assertion error: expected `{}` got `{}`", expected, got) | 138 | write!(f, "assertion error: expected `{}` got `{}`", expected, got) |
135 | } | 139 | } |
136 | Self::ScriptLoadError(s) => write!(f, "error while loading script: {}", s), | 140 | Self::ScriptLoadError(s) => write!(f, "error while loading script: {}", s), |
141 | Self::KeybindError(k) => write!(f, "keybind error: {}", k), | ||
137 | Self::CustomInternal(s) => write!(f, "{}", s), | 142 | Self::CustomInternal(s) => write!(f, "{}", s), |
138 | Self::Custom(s) => write!(f, "error: {}", s), | 143 | Self::Custom(s) => write!(f, "error: {}", s), |
139 | } | 144 | } |
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 @@ | |||
1 | use crate::{ | 1 | use crate::{ |
2 | app::AppState, | 2 | app::AppState, |
3 | keybind::Keybind, | ||
3 | lisp::{ | 4 | lisp::{ |
4 | error::{EvalError, LispError}, | 5 | error::{EvalError, LispError}, |
5 | expr::{Arity, Ident, LispExpr, LispFunction}, | 6 | expr::{Arity, Ident, LispExpr, LispFunction}, |
@@ -8,7 +9,7 @@ use crate::{ | |||
8 | type_match, | 9 | type_match, |
9 | }; | 10 | }; |
10 | 11 | ||
11 | use std::convert::TryInto; | 12 | use std::{convert::TryInto, str::FromStr}; |
12 | 13 | ||
13 | use log::{error, info}; | 14 | use log::{error, info}; |
14 | 15 | ||
@@ -44,6 +45,7 @@ where | |||
44 | "for" => self.eval_for(&li[1..]), | 45 | "for" => self.eval_for(&li[1..]), |
45 | "quote" => Ok(apply_quote(&li[1])), | 46 | "quote" => Ok(apply_quote(&li[1])), |
46 | "let" => self.eval_let(&li[1..]), | 47 | "let" => self.eval_let(&li[1..]), |
48 | "bind-key" => self.eval_bind_key(&li[1..]), | ||
47 | _ => { | 49 | _ => { |
48 | let mut new_ls = vec![self.eval(&func_expr)?]; | 50 | let mut new_ls = vec![self.eval(&func_expr)?]; |
49 | new_ls.extend(li[1..].to_vec()); | 51 | new_ls.extend(li[1..].to_vec()); |
@@ -302,6 +304,22 @@ where | |||
302 | } | 304 | } |
303 | } | 305 | } |
304 | } | 306 | } |
307 | |||
308 | pub fn eval_bind_key(&mut self, args: &[LispExpr]) -> Result<LispExpr, LispError> { | ||
309 | let arity = Arity::Exact(2); | ||
310 | if !arity.check(args) { | ||
311 | Err(arity.to_error()) | ||
312 | } else { | ||
313 | match args { | ||
314 | [LispExpr::StringLit(s), body] => { | ||
315 | let bind = Keybind::from_str(&s).map_err(EvalError::KeybindError)?; | ||
316 | self.app.keybinds.insert(bind, body.clone()); | ||
317 | Ok(LispExpr::Unit) | ||
318 | } | ||
319 | _ => Err(EvalError::BadForm.into()), | ||
320 | } | ||
321 | } | ||
322 | } | ||
305 | } | 323 | } |
306 | 324 | ||
307 | pub fn apply_quote(arg: &LispExpr) -> LispExpr { | 325 | 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 @@ | |||
72 | acc | 72 | acc |
73 | (rev-helper (cdr p) (cons (car p) acc)))) | 73 | (rev-helper (cdr p) (cons (car p) acc)))) |
74 | (rev-helper ls '()))) | 74 | (rev-helper ls '()))) |
75 | |||
76 | (define (append l1 l2) | ||
77 | (if (null? l1) | ||
78 | l2 | ||
79 | (cons (car l1) | ||
80 | (append (cdr l1) l2)))) | ||
81 | |||
82 | (define (cross xs ys) | ||
83 | (if (or (null? xs) | ||
84 | (null? ys)) | ||
85 | '() | ||
86 | (fold '() | ||
87 | append | ||
88 | (map (lambda (x) | ||
89 | (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; | |||
16 | mod error; | 16 | mod error; |
17 | mod grid; | 17 | mod grid; |
18 | mod guide; | 18 | mod guide; |
19 | mod keybind; | ||
19 | mod lisp; | 20 | mod lisp; |
20 | mod message; | 21 | mod message; |
21 | mod symmetry; | 22 | mod symmetry; |