aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2021-05-08 16:55:47 +0100
committerAkshay <[email protected]>2021-05-08 16:55:47 +0100
commit591d2b6167af53ce07b060711a4074f1e19c5f3f (patch)
tree6299e12574732b063aa57c977104eacf938e4eda
parent225351e7c98e91451e0b02fd2b9c3060ebd153a7 (diff)
add basic user-definable keybinds
-rw-r--r--src/app.rs69
-rw-r--r--src/keybind.rs146
-rw-r--r--src/lisp/error.rs13
-rw-r--r--src/lisp/eval.rs20
-rw-r--r--src/lisp/std.lisp15
-rw-r--r--src/main.rs1
6 files changed, 237 insertions, 27 deletions
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::{
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 @@
1use std::{fmt, str::FromStr};
2
3use sdl2::keyboard::{Keycode, Mod};
4
5#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
6pub struct Keybind {
7 keycode: Keycode,
8 keymod: Mod,
9}
10
11impl 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)]
22pub enum KeybindError {
23 Parse,
24 InvalidMod,
25 InvalidKey,
26}
27
28impl 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
40impl std::error::Error for KeybindError {}
41
42impl 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
49fn 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
66fn 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
74fn 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)]
95mod 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 @@
1use crate::lisp::{ 1use 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
7use std::{fmt, io}; 10use 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 @@
1use crate::{ 1use 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
11use std::convert::TryInto; 12use std::{convert::TryInto, str::FromStr};
12 13
13use log::{error, info}; 14use 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
307pub fn apply_quote(arg: &LispExpr) -> LispExpr { 325pub 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;
16mod error; 16mod error;
17mod grid; 17mod grid;
18mod guide; 18mod guide;
19mod keybind;
19mod lisp; 20mod lisp;
20mod message; 21mod message;
21mod symmetry; 22mod symmetry;