diff --git a/src/css/animations.css b/src/css/animations.css new file mode 100644 index 00000000..83463b4a --- /dev/null +++ b/src/css/animations.css @@ -0,0 +1,3 @@ +@keyframes fade { + 50% { opacity: 0.5; } +} diff --git a/src/css/cheatsheet.css b/src/css/cheatsheet.css index dc90fa93..144161ee 100644 --- a/src/css/cheatsheet.css +++ b/src/css/cheatsheet.css @@ -14,6 +14,7 @@ background-image:url('../img/keyboard.png'); background-size:35px 20px; background-repeat:no-repeat; + opacity: 0.5; z-index: 11000; transition : opacity 0.3s; @@ -25,68 +26,78 @@ .cheatsheet-container { box-sizing: border-box; - -moz-box-sizing : border-box; padding: 20px 3%; border-radius: 3px; - background: rgba(0,0,0,0.9); + background-color: rgba(0,0,0,0.9); overflow: auto; } .cheatsheet-container .cheatsheet-title { font-size:24px; - margin-top: 0; -} - -.cheatsheet-container .cheatsheet-title:nth-of-type(2) { margin-top: 30px; } +.cheatsheet-container .cheatsheet-title:nth-of-type(1) { + margin-top: 0; +} + .cheatsheet-section { - float: left; - width : 33%; + display: inline-block; + vertical-align: top; + padding : 0 20px; } .cheatsheet-shortcut { overflow: hidden; margin: 10px 0; + cursor : pointer; } .cheatsheet-icon.tool-icon { float: left; - display: inline-block; height: 30px; width: 30px; - margin: 0 20px 0 0; + margin: 0 10px 0 0; background-size: 20px 20px; background-position: 5px 5px; } .cheatsheet-description { - font-family:Courier; color: white; - font-size : 13px; - margin-left: 20px; + font-size : 14px; + margin-left: 10px; line-height : 30px; } .cheatsheet-key { + box-sizing: border-box; display : inline-block; height: 30px; - line-height: 30px; + line-height: 26px; padding: 0 10px; - border : 1px solid gold; + border : 2px solid gold; border-radius: 2px; - box-sizing: border-box; - -moz-box-sizing : border-box; - text-align: center; font-family:Courier; font-weight: bold; font-size : 18px; color: gold; +} + +.cheatsheet-shorcut-conflict .cheatsheet-key { + border-color: red; + color: red; +} + +.cheatsheet-shortcut-editing .cheatsheet-key{ + animation: fade .5s infinite; +} +.cheatsheet-shortcut-undefined .cheatsheet-key{ + border-color: red; + color: red; } \ No newline at end of file diff --git a/src/js/controller/ToolController.js b/src/js/controller/ToolController.js index 4f3803dc..4773c299 100644 --- a/src/js/controller/ToolController.js +++ b/src/js/controller/ToolController.js @@ -38,6 +38,7 @@ $('#tool-section').mousedown($.proxy(this.onToolIconClicked_, this)); $.subscribe(Events.SELECT_TOOL, this.onSelectToolEvent_.bind(this)); + $.subscribe(Events.SHORTCUTS_CHANGED, this.createToolsDom_.bind(this)); }; /** diff --git a/src/js/controller/dialogs/CheatsheetController.js b/src/js/controller/dialogs/CheatsheetController.js index c96e751b..3fe337b6 100644 --- a/src/js/controller/dialogs/CheatsheetController.js +++ b/src/js/controller/dialogs/CheatsheetController.js @@ -1,94 +1,96 @@ (function () { var ns = $.namespace('pskl.controller.dialogs'); - ns.CheatsheetController = function () { - this.shortcuts = pskl.service.keyboard.Shortcuts; - }; + ns.CheatsheetController = function () {}; pskl.utils.inherit(ns.CheatsheetController, ns.AbstractDialogController); ns.CheatsheetController.prototype.init = function () { - this.cheatsheetEl = document.getElementById('cheatsheetContainer'); - if (!this.cheatsheetEl) { - throw 'cheatsheetEl DOM element could not be retrieved'; - } + this.superclass.init.call(this); + this.cheatsheetEl = document.getElementById('cheatsheetContainer'); + this.eventTrapInput = document.getElementById('cheatsheet-event-trap'); + + pskl.utils.Event.addEventListener('.cheatsheet-restore-defaults', 'click', this.onRestoreDefaultsClick_, this); pskl.utils.Event.addEventListener(this.cheatsheetEl, 'click', this.onCheatsheetClick_, this); + pskl.utils.Event.addEventListener(this.eventTrapInput, 'keydown', this.onEventTrapKeydown_, this); + $.subscribe(Events.SHORTCUTS_CHANGED, this.onShortcutsChanged_.bind(this)); this.initMarkup_(); }; + ns.CheatsheetController.prototype.destroy = function () { + this.eventTrapInput.blur(); + pskl.utils.Event.removeAllEventListeners(); + this.cheatsheetEl = null; + }; + + ns.CheatsheetController.prototype.onRestoreDefaultsClick_ = function () { + pskl.service.keyboard.Shortcuts.restoreDefaultShortcuts(); + }; + ns.CheatsheetController.prototype.onShortcutsChanged_ = function () { this.initMarkup_(); }; ns.CheatsheetController.prototype.onCheatsheetClick_ = function (evt) { - var shortcutId = pskl.utils.Dom.getData(evt.target, 'shortcutId'); - if (!shortcutId) { + pskl.utils.Dom.removeClass('cheatsheet-shortcut-editing'); + + var shortcutEl = pskl.utils.Dom.getParentWithData(evt.target, 'shortcutId'); + if (!shortcutEl) { return; } + shortcutEl.classList.add('cheatsheet-shortcut-editing'); + this.eventTrapInput.focus(); + }; + + ns.CheatsheetController.prototype.onEventTrapKeydown_ = function (evt) { + var editedShortcutEl = document.querySelector('.cheatsheet-shortcut-editing'); + if (!editedShortcutEl) { + return; + } + + var shortcutKeyObject = pskl.service.keyboard.KeyUtils.createKeyFromEvent(evt); + var shortcutKeyString = pskl.service.keyboard.KeyUtils.stringify(shortcutKeyObject); + + var shortcutId = editedShortcutEl.dataset.shortcutId; var shortcut = pskl.service.keyboard.Shortcuts.getShortcutById(shortcutId); - var newKeys = window.prompt('Please enter the new key', shortcut.getKeys().join(', ')); - shortcut.updateKeys(newKeys); + pskl.service.keyboard.Shortcuts.updateShortcut(shortcut, shortcutKeyString); + + this.eventTrapInput.blur(); + + evt.preventDefault(); }; ns.CheatsheetController.prototype.initMarkup_ = function () { - this.initMarkupForTools_(); - this.initMarkupForMisc_(); - this.initMarkupForColors_(); - this.initMarkupForSelection_(); + this.initMarkupForCategory_('TOOL', '.cheatsheet-tool-shortcuts', this.getToolIconClass_); + this.initMarkupForCategory_('MISC', '.cheatsheet-misc-shortcuts'); + this.initMarkupForCategory_('COLOR', '.cheatsheet-color-shortcuts'); + this.initMarkupForCategory_('SELECTION', '.cheatsheet-selection-shortcuts'); + this.initMarkupForCategory_('STORAGE', '.cheatsheet-storage-shortcuts'); }; - ns.CheatsheetController.prototype.initMarkupForTools_ = function () { - var descriptors = this.createShortcutDescriptors_(this.shortcuts.TOOL, this.getToolShortcutClassname_); - this.initMarkupForDescriptors_(descriptors, '.cheatsheet-tool-shortcuts'); - }; - - ns.CheatsheetController.prototype.getToolShortcutClassname_ = function (shortcut) { + ns.CheatsheetController.prototype.getToolIconClass_ = function (shortcut) { return 'tool-icon ' + shortcut.getId(); }; - ns.CheatsheetController.prototype.initMarkupForMisc_ = function () { - var descriptors = this.createShortcutDescriptors_(this.shortcuts.MISC); - this.initMarkupForDescriptors_(descriptors, '.cheatsheet-misc-shortcuts'); - }; + ns.CheatsheetController.prototype.initMarkupForCategory_ = function (category, container, iconClassProvider) { + var shortcutMap = pskl.service.keyboard.Shortcuts[category]; - ns.CheatsheetController.prototype.initMarkupForColors_ = function () { - var descriptors = this.createShortcutDescriptors_(this.shortcuts.COLOR); - this.initMarkupForDescriptors_(descriptors, '.cheatsheet-colors-shortcuts'); - }; - - ns.CheatsheetController.prototype.initMarkupForSelection_ = function () { - var descriptors = this.createShortcutDescriptors_(this.shortcuts.SELECTION); - this.initMarkupForDescriptors_(descriptors, '.cheatsheet-selection-shortcuts'); - }; - - ns.CheatsheetController.prototype.createShortcutDescriptors_ = function (shortcutMap, classnameProvider) { - return Object.keys(shortcutMap).map(function (shortcutKey) { - var shortcut = shortcutMap[shortcutKey]; - var classname = typeof classnameProvider == 'function' ? classnameProvider(shortcut) : ''; - return this.toDescriptor_(shortcut, classname); + var descriptors = Object.keys(shortcutMap).map(function (shortcutKey) { + return this.toDescriptor_(shortcutMap[shortcutKey], iconClassProvider); }.bind(this)); + + this.initMarkupForDescriptors_(descriptors, container); }; - ns.CheatsheetController.prototype.toDescriptor_ = function (shortcut, icon) { - var key = shortcut.getKey(); - if (pskl.utils.UserAgent.isMac) { - key = key.replace('ctrl', 'cmd'); - } - key = key.replace('up', '↑'); - key = key.replace('down', '↓'); - key = key.replace(/>/g, '>'); - key = key.replace(//g, '>'); + key = key.replace(/} defaultKey combination of modifiers + ([a-z0-9] or a special key) + * @param {String|Array} defaultKeys combination of modifiers + ([a-z0-9] or a special key) * Special keys are defined in KeycodeTranslator. If the shortcut supports several keys, * use an array of String keys */ - ns.Shortcut = function (id, description, defaultKey, displayKey) { + ns.Shortcut = function (id, description, defaultKeys, displayKey) { this.id_ = id; this.description_ = description; - this.defaultKey_ = defaultKey; + if (typeof defaultKeys === 'string') { + defaultKeys = [defaultKeys]; + } + this.defaultKeys_ = defaultKeys; this.displayKey_ = displayKey; }; @@ -32,34 +35,75 @@ * @return {Array} array of keys */ ns.Shortcut.prototype.getKeys = function () { - var keys = pskl.UserSettings.get(this.getLocalStorageKey_()) || this.defaultKey_; + var keys = pskl.UserSettings.get(this.getLocalStorageKey_()) || this.defaultKeys_; + if (typeof keys === 'string') { - keys = [keys]; + return [keys]; + } + + if (!Array.isArray(keys)) { + return []; } return keys; }; + ns.Shortcut.prototype.isCustom = function () { + var keys = this.getKeys(); + if (keys.length !== this.defaultKeys_.length) { + return true; + } + + // for some default keys + return this.defaultKeys_.some(function (defaultKey) { + // no match can be found in the current keys + return !keys.some(function (key) { + return ns.KeyUtils.equals(key, defaultKey); + }); + }); + }; + + ns.Shortcut.prototype.isUndefined = function () { + return this.getKeys().length === 0; + }; + /** * Get the key to be displayed for this shortcut, if * @return {[type]} [description] */ - ns.Shortcut.prototype.getKey = function () { + ns.Shortcut.prototype.getDisplayKey = function () { + if (this.isUndefined()) { + return '???'; + } + if (this.displayKey_) { return this.displayKey_; } - var keys = this.getKeys(); - if (Array.isArray(keys) && keys.length > 0) { - return keys[0]; - } + return this.getKeys()[0]; + }; - return ''; + ns.Shortcut.prototype.restoreDefault = function (keys) { + pskl.UserSettings.set(this.getLocalStorageKey_(), ''); }; ns.Shortcut.prototype.updateKeys = function (keys) { - pskl.UserSettings.set(this.getLocalStorageKey_(), keys.split(', ')); - $.publish(Events.SHORTCUTS_CHANGED); + pskl.UserSettings.set(this.getLocalStorageKey_(), keys); + }; + + ns.Shortcut.prototype.removeKeys = function (keysToRemove) { + var keys = this.getKeys(); + var updatedKeys = keys.filter(function (key) { + return !keysToRemove.some(function (keyToRemove) { + return ns.KeyUtils.equals(key, keyToRemove); + }); + }); + + if (updatedKeys.length !== keys.length) { + this.updateKeys(updatedKeys); + return true; + } + return false; }; ns.Shortcut.prototype.getLocalStorageKey_ = function () { diff --git a/src/js/service/keyboard/ShortcutService.js b/src/js/service/keyboard/ShortcutService.js index 1ad4edd7..47aa048e 100644 --- a/src/js/service/keyboard/ShortcutService.js +++ b/src/js/service/keyboard/ShortcutService.js @@ -9,7 +9,7 @@ * @public */ ns.ShortcutService.prototype.init = function() { - $(document.body).keydown($.proxy(this.onKeyUp_, this)); + $(document.body).keydown($.proxy(this.onKeyDown_, this)); }; /** @@ -44,61 +44,22 @@ } }; - ns.ShortcutService.prototype.parseKey_ = function (key) { - var meta = this.getMetaKey_({ - alt : key.indexOf('alt+') != -1, - shift : key.indexOf('shift+') != -1, - ctrl : key.indexOf('ctrl+') != -1 - }); - - var parts = key.split(/\+(?!$)/); - key = parts[parts.length - 1]; - return {meta : meta, key : key.toLowerCase()}; - }; - - /** - * Retrieve a comparable representation of a meta information for a key - * 'alt' 'ctrl' and 'shift' will always be in the same order for the same meta - */ - ns.ShortcutService.prototype.getMetaKey_ = function (meta) { - var keyBuffer = []; - - if (meta.alt) { - keyBuffer.push('alt'); - } - if (meta.ctrl) { - keyBuffer.push('ctrl'); - } - if (meta.shift) { - keyBuffer.push('shift'); - } - - return keyBuffer.join('+') || 'normal'; - }; - /** * @private */ - ns.ShortcutService.prototype.onKeyUp_ = function(evt) { - if (this.isInInput_(evt)) { + ns.ShortcutService.prototype.onKeyDown_ = function(evt) { + var eventKey = ns.KeyUtils.createKeyFromEvent(evt); + if (this.isInInput_(evt) || !eventKey) { return; } - var keycode = evt.which; - var eventKey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode); - var eventMeta = this.getMetaKey_({ - alt : evt.altKey, - shift : evt.shiftKey, - ctrl : this.isCtrlKeyPressed_(evt) - }); - this.shortcuts_.forEach(function (shortcutInfo) { - shortcutInfo.shortcut.getKeys().forEach(function (key) { - if (!this.isKeyMatching_(key, eventKey, eventMeta)) { + shortcutInfo.shortcut.getKeys().forEach(function (shortcutKey) { + if (!ns.KeyUtils.equals(shortcutKey, eventKey)) { return; } - var bubble = shortcutInfo.callback(eventKey); + var bubble = shortcutInfo.callback(eventKey.key); if (bubble !== true) { evt.preventDefault(); } @@ -111,13 +72,4 @@ var targetTagName = evt.target.nodeName.toUpperCase(); return targetTagName === 'INPUT' || targetTagName === 'TEXTAREA'; }; - - ns.ShortcutService.prototype.isKeyMatching_ = function (key, eventKey, eventMeta) { - var parsedKey = this.parseKey_(key); - return parsedKey.key === eventKey && parsedKey.meta === eventMeta; - }; - - ns.ShortcutService.prototype.isCtrlKeyPressed_ = function (evt) { - return pskl.utils.UserAgent.isMac ? evt.metaKey : evt.ctrlKey; - }; })(); diff --git a/src/js/service/keyboard/Shortcuts.js b/src/js/service/keyboard/Shortcuts.js index 9cad63f7..922e52bd 100644 --- a/src/js/service/keyboard/Shortcuts.js +++ b/src/js/service/keyboard/Shortcuts.js @@ -30,7 +30,7 @@ SELECTION : { CUT : createShortcut('selection-cut', 'Cut selection', 'ctrl+X'), COPY : createShortcut('selection-copy', 'Copy selection', 'ctrl+C'), - PASTE : createShortcut('selection-cut', 'Paste selection', 'ctrl+V'), + PASTE : createShortcut('selection-paste', 'Paste selection', 'ctrl+V'), DELETE : createShortcut('selection-delete', 'Delete selection', ['del', 'back']) }, @@ -52,9 +52,9 @@ }, STORAGE : { - OPEN : createShortcut('open', '(Desktop only) Open a .piskel file', 'ctrl+O'), SAVE : createShortcut('save', 'Save the current sprite', 'ctrl+S'), - SAVE_AS : createShortcut('save-as', '(Desktop only) Save as a new .piskel file', 'ctrl+shift+S') + OPEN : createShortcut('open', '(desktop) Open a .piskel file', 'ctrl+O'), + SAVE_AS : createShortcut('save-as', '(desktop) Save as new', 'ctrl+shift+S') }, COLOR : { @@ -70,18 +70,43 @@ CATEGORIES : ['TOOL', 'SELECTION', 'MISC', 'STORAGE', 'COLOR'], getShortcutById : function (id) { - var shortcut = null; + return pskl.utils.Array.find(ns.Shortcuts.getShortcuts(), function (shortcut) { + return shortcut.getId() === id; + }); + }, + + getShortcuts : function () { + var shortcuts = []; ns.Shortcuts.CATEGORIES.forEach(function (category) { - var shortcuts = ns.Shortcuts[category]; - Object.keys(shortcuts).forEach(function (shortcutKey) { - if (shortcuts[shortcutKey].getId() === id) { - shortcut = shortcuts[shortcutKey]; - } + var shortcutMap = ns.Shortcuts[category]; + Object.keys(shortcutMap).forEach(function (shortcutKey) { + shortcuts.push(shortcutMap[shortcutKey]); }); }); + return shortcuts; + }, - return shortcut; + updateShortcut : function (shortcut, keysString) { + keysString = keysString.replace(/\s/g, ''); + var keys = keysString.split(','); + ns.Shortcuts.getShortcuts().forEach(function (s) { + if (s === shortcut) { + return; + } + + if (s.removeKeys(keys)) { + $.publish(Events.SHOW_NOTIFICATION, [{'content': 'Shortcut key removed for ' + s.getId()}]); + } + }); + shortcut.updateKeys(keys); + $.publish(Events.SHORTCUTS_CHANGED); + }, + + restoreDefaultShortcuts : function () { + ns.Shortcuts.getShortcuts().forEach(function (shortcut) { + shortcut.restoreDefault(); + }); + $.publish(Events.SHORTCUTS_CHANGED); } - }; })(); diff --git a/src/js/utils/Dom.js b/src/js/utils/Dom.js index 2d7e7adb..1a62cef4 100644 --- a/src/js/utils/Dom.js +++ b/src/js/utils/Dom.js @@ -41,6 +41,14 @@ if (parent !== null) { return parent.dataset[dataName]; } + }, + + removeClass : function (className, container) { + container = container || document; + var elements = container.querySelectorAll('.' + className); + for (var i = 0 ; i < elements.length ; i++) { + elements[i].classList.remove(className); + } } }; })(); diff --git a/src/js/utils/TooltipFormatter.js b/src/js/utils/TooltipFormatter.js index be3a3aec..49ab24e3 100644 --- a/src/js/utils/TooltipFormatter.js +++ b/src/js/utils/TooltipFormatter.js @@ -5,7 +5,7 @@ ns.TooltipFormatter.format = function(helpText, shortcut, descriptors) { var tpl = pskl.utils.Template.get('tooltip-container-template'); - shortcut = shortcut ? '(' + shortcut.getKey() + ')' : ''; + shortcut = shortcut ? '(' + shortcut.getDisplayKey() + ')' : ''; return pskl.utils.Template.replace(tpl, { helptext : helpText, shortcut : shortcut, diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 75e608ec..c88b3c66 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -28,8 +28,8 @@ "js/utils/FileUtils.js", "js/utils/FileUtilsDesktop.js", "js/utils/FrameUtils.js", - "js/utils/LayerUtils.js", "js/utils/ImageResizer.js", + "js/utils/LayerUtils.js", "js/utils/PixelUtils.js", "js/utils/PiskelFileUtils.js", "js/utils/Template.js", @@ -158,10 +158,11 @@ "js/service/palette/reader/PaletteTxtReader.js", "js/service/palette/PaletteImportService.js", "js/service/SavedStatusService.js", + "js/service/keyboard/KeycodeTranslator.js", + "js/service/keyboard/KeyUtils.js", "js/service/keyboard/Shortcut.js", "js/service/keyboard/Shortcuts.js", "js/service/keyboard/ShortcutService.js", - "js/service/keyboard/KeycodeTranslator.js", "js/service/ImageUploadService.js", "js/service/CurrentColorsService.js", "js/service/FileDropperService.js", diff --git a/src/piskel-style-list.js b/src/piskel-style-list.js index 06519cc3..7d894894 100644 --- a/src/piskel-style-list.js +++ b/src/piskel-style-list.js @@ -3,6 +3,7 @@ (typeof exports != "undefined" ? exports : pskl_exports).styles = [ "css/reset.css", "css/style.css", + "css/animations.css", "css/layout.css", "css/font-icon.css", "css/forms.css", diff --git a/src/templates/dialogs/cheatsheet.html b/src/templates/dialogs/cheatsheet.html index 2c4efc2a..3d80b29e 100644 --- a/src/templates/dialogs/cheatsheet.html +++ b/src/templates/dialogs/cheatsheet.html @@ -16,12 +16,20 @@

Selection shortcuts

Color shortcuts

- + +

Storage shortcuts

+ + +
+ +
+
+