feature : add keyboard shortcuts

+ decentralized shortcut declaration
+ each service/controller is now responsible for declaring its shorcuts
- documentation (cheatsheet) is still to be maintained manually
- init order matters (shortcutService has to be instanciated before
  everyone else) => should have a standalone KeyboardService singleton
  which is ready as soon as it is loaded
This commit is contained in:
jdescottes
2013-11-19 23:46:33 +01:00
parent 9d0f41362b
commit 6eabf01ffc
13 changed files with 190 additions and 146 deletions

View File

@@ -7,8 +7,9 @@
ns.HistoryService.prototype.init = function () {
$.subscribe(Events.TOOL_RELEASED, this.saveState.bind(this));
$.subscribe(Events.UNDO, this.undo.bind(this));
$.subscribe(Events.REDO, this.redo.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this));
};
ns.HistoryService.prototype.saveState = function () {

View File

@@ -1,72 +0,0 @@
(function () {
var ns = $.namespace("pskl.service");
ns.KeyboardEventService = function () {
this.keyboardActions_ = {
"ctrl" : {
"z" : Events.UNDO,
"y" : Events.REDO,
"x" : Events.CUT,
"c" : Events.COPY,
"v" : Events.PASTE
},
"shift" : {
"?" : Events.TOGGLE_HELP
},
"x" : Events.SWAP_COLORS
};
// See ToolController
// TODO : Allow for other classes to register new shortcuts
var toolKeys = 'pveblrcmzso'.split('');
toolKeys.forEach(function (key) {
this.keyboardActions_[key] = Events.SELECT_TOOL;
}.bind(this));
};
/**
* @public
*/
ns.KeyboardEventService.prototype.init = function() {
$(document.body).keydown($.proxy(this.onKeyUp_, this));
};
/**
* @private
*/
ns.KeyboardEventService.prototype.onKeyUp_ = function(evt) {
var eventToTrigger;
// jquery names FTW ...
var keycode = evt.which;
var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode);
if(charkey) {
if (this.isCtrlKeyPressed_(evt)) {
eventToTrigger = this.keyboardActions_.ctrl[charkey];
} else if (this.isShiftKeyPressed_(evt)) {
eventToTrigger = this.keyboardActions_.shift[charkey];
} else {
eventToTrigger = this.keyboardActions_[charkey];
}
if(eventToTrigger) {
$.publish(eventToTrigger, charkey);
evt.preventDefault();
}
}
};
ns.KeyboardEventService.prototype.isCtrlKeyPressed_ = function (evt) {
return this.isMac_() ? evt.metaKey : evt.ctrlKey;
};
ns.KeyboardEventService.prototype.isShiftKeyPressed_ = function (evt) {
return evt.shiftKey;
};
ns.KeyboardEventService.prototype.isMac_ = function () {
return navigator.appVersion.indexOf("Mac") != -1;
};
})();

View File

@@ -11,7 +11,10 @@
throw 'cheatsheetEl_ DOM element could not be retrieved';
}
this.initMarkup_();
pskl.app.shortcutService.addShortcut('shift+?', this.toggleCheatsheet_.bind(this));
pskl.app.shortcutService.addShortcut('?', this.toggleCheatsheet_.bind(this));
$.subscribe(Events.TOGGLE_HELP, this.toggleCheatsheet_.bind(this));
$.subscribe(Events.ESCAPE, this.onEscape_.bind(this));
};
ns.CheatsheetService.prototype.toggleCheatsheet_ = function () {
@@ -22,13 +25,21 @@
}
};
ns.CheatsheetService.prototype.onEscape_ = function () {
if (this.isDisplayed_) {
this.hideCheatsheet_();
}
};
ns.CheatsheetService.prototype.showCheatsheet_ = function () {
pskl.app.shortcutService.addShortcut('ESC', this.hideCheatsheet_.bind(this));
this.cheatsheetEl_.style.display = 'block';
this.isDisplayed_ = true;
};
ns.CheatsheetService.prototype.hideCheatsheet_ = function () {
pskl.app.shortcutService.removeShortcut('ESC');
this.cheatsheetEl_.style.display = 'none';
this.isDisplayed_ = false;
};
@@ -65,11 +76,16 @@
};
var miscKeys = [
toDescriptor('X', 'Swap primary/secondary colors'),
toDescriptor('D', 'Reset default colors'),
toDescriptor('ctrl + X', 'Cut selection'),
toDescriptor('ctrl + C', 'Copy selection'),
toDescriptor('ctrl + V', 'Paste selection'),
toDescriptor('ctrl + Z', 'Undo'),
toDescriptor('ctrl + Y', 'Redo')
toDescriptor('ctrl + Y', 'Redo'),
toDescriptor('↑', 'Select previous frame'), /* ASCII for up-arrow */
toDescriptor('↓', 'Select next frame'), /* ASCII for down-arrow */
toDescriptor('N', 'Create new frame'),
toDescriptor('shift + ?', 'Open/Close this popup')
];
for (var i = 0 ; i < miscKeys.length ; i++) {
var key = miscKeys[i];

View File

@@ -1,17 +0,0 @@
(function () {
var ns = $.namespace('service.keyboard');
ns.KeyboardEvent = function (eventName, args, description) {
this.eventName = eventName;
this.args = args;
this.description = description;
};
ns.KeyboardEvent.prototype.fire = function () {
$.publish(this.eventName, this.args);
};
ns.KeyboardEvent.prototype.getDescription = function () {
return this.description;
};
})();

View File

@@ -1,6 +1,9 @@
(function () {
var specialKeys = {
191 : "?"
191 : "?",
27 : "esc",
38 : "up",
40 : "down"
};
var ns = $.namespace('pskl.service.keyboard');
@@ -11,7 +14,7 @@
// key is 0-9
return (keycode - 48) + "";
} else if (keycode >= 65 && keycode <= 90) {
// key is a-z, we'll use base 36 to get the string representation
// key is a-z, use base 36 to get the string representation
return (keycode - 65 + 10).toString(36);
} else {
return specialKeys[keycode];

View File

@@ -0,0 +1,90 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
ns.ShortcutService = function () {
this.shortcuts_ = {};
};
/**
* @public
*/
ns.ShortcutService.prototype.init = function() {
$(document.body).keydown($.proxy(this.onKeyUp_, this));
};
ns.ShortcutService.prototype.addShortcut = function (rawKey, callback) {
var parsedKey = this.parseKey_(rawKey.toLowerCase());
var key = parsedKey.key,
meta = parsedKey.meta;
this.shortcuts_[key] = this.shortcuts_[key] || {};
if (this.shortcuts_[key][meta]) {
throw 'Shortcut ' + meta + ' + ' + key + ' already registered';
} else {
this.shortcuts_[key][meta] = callback;
}
};
ns.ShortcutService.prototype.removeShortcut = function (rawKey) {
var parsedKey = this.parseKey_(rawKey.toLowerCase());
var key = parsedKey.key,
meta = parsedKey.meta;
this.shortcuts_[key] = this.shortcuts_[key] || {};
this.shortcuts_[key][meta] = null;
};
ns.ShortcutService.prototype.parseKey_ = function (key) {
var meta = 'normal';
if (key.indexOf('ctrl+') === 0) {
meta = 'ctrl';
key = key.replace('ctrl+', '');
} else if (key.indexOf('shift+') === 0) {
meta = 'shift';
key = key.replace('shift+', '');
}
return {meta : meta, key : key};
};
/**
* @private
*/
ns.ShortcutService.prototype.onKeyUp_ = function(evt) {
// jquery names FTW ...
var keycode = evt.which;
var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode);
var keyShortcuts = this.shortcuts_[charkey];
if(keyShortcuts) {
var cb;
if (this.isCtrlKeyPressed_(evt)) {
cb = keyShortcuts.ctrl;
} else if (this.isShiftKeyPressed_(evt)) {
cb = keyShortcuts.shift;
} else {
cb = keyShortcuts.normal;
}
if(cb) {
cb(charkey);
evt.preventDefault();
}
}
};
ns.ShortcutService.prototype.isCtrlKeyPressed_ = function (evt) {
return this.isMac_() ? evt.metaKey : evt.ctrlKey;
};
ns.ShortcutService.prototype.isShiftKeyPressed_ = function (evt) {
return evt.shiftKey;
};
ns.ShortcutService.prototype.isMac_ = function () {
return navigator.appVersion.indexOf("Mac") != -1;
};
})();