Merge pull request #334 from juliandescottes/add-custom-shortcuts

Close #287
This commit is contained in:
Julian Descottes 2015-11-13 00:13:34 +01:00
commit 0dff1f7a9a
60 changed files with 1290 additions and 537 deletions

3
src/css/animations.css Normal file
View File

@ -0,0 +1,3 @@
@keyframes fade {
50% { opacity: 0.5; }
}

View File

@ -1,110 +0,0 @@
.cheatsheet-link {
position: fixed;
bottom: 10px;
left: 10px;
padding: 1px 0 0 45px;
color : gold;
font-weight: bold;
font-size : 1.25em;
line-height: 20px;
cursor : pointer;
background-image:url('../img/keyboard.png');
background-size:35px 20px;
background-repeat:no-repeat;
opacity: 0.5;
z-index: 11000;
transition : opacity 0.3s;
}
.cheatsheet-link:hover {
opacity: 1;
}
#cheatsheet-wrapper {
position: absolute;
z-index: 10000;
top: 50px;
right: 50px;
bottom: 50px;
left: 50px;
box-sizing: border-box;
-moz-box-sizing : border-box;
color: white;
}
.cheatsheet-container {
width: 100%;
height: 100%;
box-sizing: border-box;
-moz-box-sizing : border-box;
padding: 20px 3%;
border-radius: 3px;
background: 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-section {
float: left;
width : 33%;
}
.cheatsheet-shortcut {
overflow: hidden;
margin: 10px 0;
}
.cheatsheet-icon.tool-icon {
float: left;
display: inline-block;
height: 30px;
width: 30px;
margin: 0 20px 0 0;
background-size: 20px 20px;
background-position: 5px 5px;
}
.cheatsheet-description {
font-family:Courier;
color: white;
font-size : 13px;
margin-left: 20px;
line-height : 30px;
}
.cheatsheet-key {
display : inline-block;
height: 30px;
line-height: 30px;
padding: 0 10px;
border : 1px 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;
}

View File

@ -0,0 +1,173 @@
.cheatsheet-link {
position: fixed;
bottom: 10px;
left: 10px;
padding: 1px 0 0 45px;
color : gold;
font-weight: bold;
font-size : 1.25em;
line-height: 20px;
cursor : pointer;
background-image:url('../img/keyboard.png');
background-size:35px 20px;
background-repeat:no-repeat;
opacity: 0.5;
z-index: 11000;
transition : opacity 0.3s;
}
.cheatsheet-link:hover {
opacity: 1;
}
.cheatsheet-container {
box-sizing: border-box;
display: block;
bottom: 46px;
padding: 20px 3%;
border-radius: 3px;
background-color: rgba(0,0,0,0.9);
overflow: auto;
}
.cheatsheet-container .cheatsheet-title {
font-size:24px;
margin-top: 30px;
}
.cheatsheet-container .cheatsheet-title:nth-of-type(1) {
margin-top: 0;
}
.cheatsheet-section {
box-sizing: border-box;
float: left;
vertical-align: top;
padding : 0 20px 20px 20px;
min-width: 300px;
}
.cheatsheet-boxes {
display: flex;
flex-direction: row;
flex-wrap: wrap;
float: left;
}
@media (min-width: 1300px) {
.cheatsheet-container > div {
width: 33%;
}
.cheatsheet-boxes {
flex-direction: column;
}
}
.cheatsheet-shortcut {
overflow: hidden;
margin: 10px 0;
}
.cheatsheet-icon.tool-icon {
float: left;
height: 30px;
width: 30px;
margin: 0 10px 0 0;
background-size: 20px 20px;
background-position: 5px 5px;
}
.cheatsheet-description {
color: white;
font-size : 14px;
margin-left: 10px;
line-height : 30px;
}
.cheatsheet-key {
box-sizing: border-box;
display : inline-block;
height: 30px;
line-height: 26px;
padding: 0 10px;
border : 2px solid white;
border-radius: 2px;
text-align: center;
font-family:Courier;
font-weight: bold;
font-size : 18px;
color: white;
}
.cheatsheet-shorcut-conflict .cheatsheet-key {
border-color: red;
color: red;
}
.cheatsheet-shortcut-editable {
cursor : pointer;
}
.cheatsheet-shortcut-editable .cheatsheet-key {
border-color: gold;
color: gold;
}
.cheatsheet-shortcut-editing .cheatsheet-key {
animation: fade .5s infinite;
}
.cheatsheet-shortcut-undefined .cheatsheet-key {
border-color: red;
color: red;
}
/*Cheatsheet actions*/
.cheatsheet-actions {
position: absolute;
box-sizing: border-box;
bottom : 0;
left : 0;
right : 0;
height : 46px;
padding : 10px;
overflow: hidden;
background-color : gold;
}
.cheatsheet-helptext {
font-size: 14px;
height : 26px;
line-height : 26px;
color: black;
}
.cheatsheet-helptext-tooltip {
text-align: left;
}
.cheatsheet-helptext-tooltip-item {
line-height: 0.9em;
margin: 10px 0;
}
.cheatsheet-button {
position: absolute;
bottom: 10px;
right: 10px;
}

View File

@ -44,10 +44,6 @@
overflow: auto;
}
.animated #dialog-container {
transition:margin-top 0.2s;
}
.show #dialog-container {
margin-top: 0;
}
@ -70,6 +66,14 @@
position : relative;
}
.dialog-content {
position: absolute;
top: 45px;
bottom: 0;
width: 100%;
box-sizing: border-box;
}
.dialog-head {
width: 100%;
background: gold;
@ -77,6 +81,7 @@
padding: 10px;
color: black;
font-size: 1.8em;
height: 45px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}

View File

@ -32,7 +32,6 @@
border-radius: 0 0 0 2px;
}
.palettes-list-color:nth-child(1):after {
content: "1";
}
@ -64,6 +63,7 @@
.palettes-list-color:nth-child(-n+5) {
margin-top: 5px;
}
.palettes-list-color div {
width: 32px;
height: 32px;

View File

@ -90,13 +90,15 @@
<iframe src="templates/dialogs/create-palette.html" onload="iframeloader.onLoad(event)" data-iframe-loader="store"></iframe>
<iframe src="templates/dialogs/import-image.html" onload="iframeloader.onLoad(event)" data-iframe-loader="store"></iframe>
<iframe src="templates/dialogs/browse-local.html" onload="iframeloader.onLoad(event)" data-iframe-loader="store"></iframe>
<iframe src="templates/dialogs/cheatsheet.html" onload="iframeloader.onLoad(event)" data-iframe-loader="store"></iframe>
</div>
</div>
<iframe src="templates/cheatsheet.html" onload="iframeloader.onLoad(event)" data-iframe-loader="display"></iframe>
<iframe src="templates/misc-templates.html" onload="iframeloader.onLoad(event)" data-iframe-loader="display"></iframe>
<iframe src="templates/popup-preview.html" onload="iframeloader.onLoad(event)" data-iframe-loader="display"></iframe>
<span class="cheatsheet-link" rel="tooltip" data-placement="right" title="Keyboard shortcuts">&nbsp;</span>
<script type="text/javascript" src="piskel-boot.js"></script>
<!--body-main-end-->
<!-- the comment above indicates the end of the markup reused by the editor integrated in piskelapp.com -->

View File

@ -27,6 +27,7 @@ var Events = {
* 2nd argument: New value
*/
USER_SETTINGS_CHANGED: 'USER_SETTINGS_CHANGED',
SHORTCUTS_CHANGED: 'SHORTCUTS_CHANGED',
CLOSE_SETTINGS_DRAWER : 'CLOSE_SETTINGS_DRAWER',

View File

@ -127,9 +127,6 @@
this.imageUploadService = new pskl.service.ImageUploadService();
this.imageUploadService.init();
this.cheatsheetService = new pskl.service.keyboard.CheatsheetService();
this.cheatsheetService.init();
this.savedStatusService = new pskl.service.SavedStatusService(this.piskelController);
this.savedStatusService.init();

View File

@ -62,9 +62,10 @@
$.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this));
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
pskl.app.shortcutService.addShortcut('0', this.resetZoom_.bind(this));
pskl.app.shortcutService.addShortcut('+', this.increaseZoom_.bind(this, 1));
pskl.app.shortcutService.addShortcut('-', this.decreaseZoom_.bind(this, 1));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.RESET_ZOOM, this.resetZoom_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.INCREASE_ZOOM, this.increaseZoom_.bind(this, 1));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DECREASE_ZOOM, this.decreaseZoom_.bind(this, 1));
window.setTimeout(function () {
this.afterWindowResize_();

View File

@ -5,6 +5,7 @@
ns.LayersListController = function (piskelController) {
this.piskelController = piskelController;
this.layerPreviewShortcut = pskl.service.keyboard.Shortcuts.MISC.LAYER_PREVIEW ;
};
ns.LayersListController.prototype.init = function () {
@ -36,10 +37,9 @@
var descriptors = [{description : 'Opacity defined in PREFERENCES'}];
var helpText = 'Preview all layers';
var toggleLayerPreviewTooltip = pskl.utils.TooltipFormatter.format(helpText, TOGGLE_LAYER_SHORTCUT, descriptors);
this.toggleLayerPreviewEl.setAttribute('title', toggleLayerPreviewTooltip);
pskl.app.shortcutService.addShortcut(TOGGLE_LAYER_SHORTCUT, this.toggleLayerPreview_.bind(this));
pskl.app.shortcutService.registerShortcut(this.layerPreviewShortcut, this.toggleLayerPreview_.bind(this));
var tooltip = pskl.utils.TooltipFormatter.format(helpText, this.layerPreviewShortcut, descriptors);
this.toggleLayerPreviewEl.setAttribute('title', tooltip);
};
ns.LayersListController.prototype.updateButtonStatus_ = function () {

View File

@ -25,9 +25,6 @@
document.body.appendChild(message);
message.querySelector('.close').addEventListener('click', this.removeMessage_.bind(this));
if (messageInfo.behavior) {
messageInfo.behavior(message);
}
if (messageInfo.hideDelay) {
window.setTimeout(this.removeMessage_.bind(this), messageInfo.hideDelay);

View File

@ -10,8 +10,9 @@
$.subscribe(Events.SELECT_PRIMARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:true}));
$.subscribe(Events.SELECT_SECONDARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:false}));
pskl.app.shortcutService.addShortcut('X', this.swapColors.bind(this));
pskl.app.shortcutService.addShortcut('D', this.resetColors.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.SWAP, this.swapColors.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.RESET, this.resetColors.bind(this));
var spectrumCfg = {
showPalette: true,

View File

@ -37,9 +37,10 @@
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.highlightSelectedColors.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
pskl.app.shortcutService.addShortcuts(['>', 'shift+>'], this.selectNextColor_.bind(this));
pskl.app.shortcutService.addShortcut('<', this.selectPreviousColor_.bind(this));
pskl.app.shortcutService.addShortcuts('123465789'.split(''), this.selectColorForKey_.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.PREVIOUS_COLOR, this.selectPreviousColor_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.NEXT_COLOR, this.selectNextColor_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.COLOR.SELECT_COLOR, this.selectColorForKey_.bind(this));
this.fillPaletteList();
this.updateFromUserSettings();
@ -151,7 +152,9 @@
};
ns.PalettesListController.prototype.onCreatePaletteClick_ = function (evt) {
$.publish(Events.DIALOG_DISPLAY, 'create-palette');
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'create-palette'
});
};
ns.PalettesListController.prototype.onEditPaletteClick_ = function (evt) {

View File

@ -2,29 +2,26 @@
var ns = $.namespace('pskl.controller');
ns.ToolController = function () {
var toDescriptor = function (id, shortcut, instance) {
return {id:id, shortcut:shortcut, instance:instance};
};
this.tools = [
toDescriptor('simplePen', 'P', new pskl.tools.drawing.SimplePen()),
toDescriptor('verticalMirrorPen', 'V', new pskl.tools.drawing.VerticalMirrorPen()),
toDescriptor('paintBucket', 'B', new pskl.tools.drawing.PaintBucket()),
toDescriptor('colorSwap', 'A', new pskl.tools.drawing.ColorSwap()),
toDescriptor('eraser', 'E', new pskl.tools.drawing.Eraser()),
toDescriptor('stroke', 'L', new pskl.tools.drawing.Stroke()),
toDescriptor('rectangle', 'R', new pskl.tools.drawing.Rectangle()),
toDescriptor('circle', 'C', new pskl.tools.drawing.Circle()),
toDescriptor('move', 'M', new pskl.tools.drawing.Move()),
toDescriptor('shapeSelect', 'Z', new pskl.tools.drawing.selection.ShapeSelect()),
toDescriptor('rectangleSelect', 'S', new pskl.tools.drawing.selection.RectangleSelect()),
toDescriptor('lassoSelect', 'H', new pskl.tools.drawing.selection.LassoSelect()),
toDescriptor('lighten', 'U', new pskl.tools.drawing.Lighten()),
toDescriptor('dithering', 'T', new pskl.tools.drawing.DitheringTool()),
toDescriptor('colorPicker', 'O', new pskl.tools.drawing.ColorPicker())
new pskl.tools.drawing.SimplePen(),
new pskl.tools.drawing.VerticalMirrorPen(),
new pskl.tools.drawing.PaintBucket(),
new pskl.tools.drawing.ColorSwap(),
new pskl.tools.drawing.Eraser(),
new pskl.tools.drawing.Stroke(),
new pskl.tools.drawing.Rectangle(),
new pskl.tools.drawing.Circle(),
new pskl.tools.drawing.Move(),
new pskl.tools.drawing.selection.ShapeSelect(),
new pskl.tools.drawing.selection.RectangleSelect(),
new pskl.tools.drawing.selection.LassoSelect(),
new pskl.tools.drawing.Lighten(),
new pskl.tools.drawing.DitheringTool(),
new pskl.tools.drawing.ColorPicker()
];
this.toolIconRenderer = new pskl.tools.IconMarkupRenderer();
this.iconMarkupRenderer = new pskl.tools.IconMarkupRenderer();
};
/**
@ -41,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));
};
/**
@ -53,8 +51,8 @@
stage.removeClass(previousSelectedToolClass);
stage.removeClass(pskl.tools.drawing.Move.TOOL_ID);
}
stage.addClass(tool.instance.toolId);
stage.data('selected-tool-class', tool.instance.toolId);
stage.addClass(tool.toolId);
stage.data('selected-tool-class', tool.toolId);
};
ns.ToolController.prototype.onSelectToolEvent_ = function(event, toolId) {
@ -72,12 +70,12 @@
this.activateToolOnStage_(this.currentSelectedTool);
var selectedToolElement = $('#tool-section .tool-icon.selected');
var toolElement = $('[data-tool-id=' + tool.instance.toolId + ']');
var toolElement = $('[data-tool-id=' + tool.toolId + ']');
selectedToolElement.removeClass('selected');
toolElement.addClass('selected');
$.publish(Events.TOOL_SELECTED, [tool.instance]);
$.publish(Events.TOOL_SELECTED, [tool]);
};
/**
@ -96,18 +94,16 @@
}
};
ns.ToolController.prototype.onKeyboardShortcut_ = function(charkey) {
for (var i = 0 ; i < this.tools.length ; i++) {
var tool = this.tools[i];
if (tool.shortcut.toLowerCase() === charkey.toLowerCase()) {
this.selectTool_(tool);
}
ns.ToolController.prototype.onKeyboardShortcut_ = function(toolId, charkey) {
var tool = this.getToolById_(toolId);
if (tool !== null) {
this.selectTool_(tool);
}
};
ns.ToolController.prototype.getToolById_ = function (toolId) {
return pskl.utils.Array.find(this.tools, function (tool) {
return tool.instance.toolId == toolId;
return tool.toolId == toolId;
});
};
@ -118,14 +114,15 @@
var html = '';
for (var i = 0 ; i < this.tools.length ; i++) {
var tool = this.tools[i];
html += this.toolIconRenderer.render(tool.instance, tool.shortcut);
html += this.iconMarkupRenderer.render(tool);
}
$('#tools-container').html(html);
};
ns.ToolController.prototype.addKeyboardShortcuts_ = function () {
for (var i = 0 ; i < this.tools.length ; i++) {
pskl.app.shortcutService.addShortcut(this.tools[i].shortcut, this.onKeyboardShortcut_.bind(this));
var tool = this.tools[i];
pskl.app.shortcutService.registerShortcut(tool.shortcut, this.onKeyboardShortcut_.bind(this, tool.toolId));
}
};
})();

View File

@ -2,15 +2,10 @@
var ns = $.namespace('pskl.controller');
ns.TransformationsController = function () {
var toDescriptor = function (id, shortcut, instance) {
return {id:id, shortcut:shortcut, instance:instance};
};
this.tools = [
toDescriptor('flip', '', new pskl.tools.transform.Flip()),
toDescriptor('rotate', '', new pskl.tools.transform.Rotate()),
toDescriptor('clone', '', new pskl.tools.transform.Clone())
new pskl.tools.transform.Flip(),
new pskl.tools.transform.Rotate(),
new pskl.tools.transform.Clone()
];
this.toolIconRenderer = new pskl.tools.IconMarkupRenderer();
@ -25,9 +20,9 @@
ns.TransformationsController.prototype.applyTool = function (toolId, evt) {
this.tools.forEach(function (tool) {
if (tool.instance.toolId === toolId) {
if (tool.toolId === toolId) {
$.publish(Events.TRANSFORMATION_EVENT, [toolId, evt]);
tool.instance.apply(evt);
tool.applyTransformation(evt);
}
}.bind(this));
};
@ -39,7 +34,7 @@
ns.TransformationsController.prototype.createToolsDom_ = function() {
var html = this.tools.reduce(function (p, tool) {
return p + this.toolIconRenderer.render(tool.instance, tool.shortcut, 'left');
return p + this.toolIconRenderer.render(tool, tool.shortcut, 'left');
}.bind(this), '');
this.toolsContainer.innerHTML = html;
};

View File

@ -0,0 +1,173 @@
(function () {
var ns = $.namespace('pskl.controller.dialogs');
var SHORTCUT_EDITING_CLASSNAME = 'cheatsheet-shortcut-editing';
ns.CheatsheetController = function () {};
pskl.utils.inherit(ns.CheatsheetController, ns.AbstractDialogController);
ns.CheatsheetController.prototype.init = function () {
this.superclass.init.call(this);
this.cheatsheetEl = document.getElementById('cheatsheetContainer');
this.eventTrapInput = document.getElementById('cheatsheetEventTrap');
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_();
document.querySelector('.cheatsheet-helptext').setAttribute('title', this.getHelptextTitle_());
};
ns.CheatsheetController.prototype.destroy = function () {
this.eventTrapInput.blur();
pskl.utils.Event.removeAllEventListeners();
this.cheatsheetEl = null;
};
ns.CheatsheetController.prototype.onRestoreDefaultsClick_ = function () {
if (window.confirm('Replace all custom shortcuts by the default Piskel shortcuts ?')) {
pskl.app.shortcutService.restoreDefaultShortcuts();
}
};
ns.CheatsheetController.prototype.onShortcutsChanged_ = function () {
this.initMarkup_();
};
ns.CheatsheetController.prototype.onCheatsheetClick_ = function (evt) {
var shortcutEl = pskl.utils.Dom.getParentWithData(evt.target, 'shortcutId');
if (!shortcutEl) {
pskl.utils.Dom.removeClass(SHORTCUT_EDITING_CLASSNAME);
return;
}
var shortcutId = shortcutEl.dataset.shortcutId;
var shortcut = pskl.app.shortcutService.getShortcutById(shortcutId);
if (shortcutEl.classList.contains(SHORTCUT_EDITING_CLASSNAME)) {
shortcutEl.classList.remove(SHORTCUT_EDITING_CLASSNAME);
this.eventTrapInput.blur();
} else if (shortcut.isEditable()) {
shortcutEl.classList.add(SHORTCUT_EDITING_CLASSNAME);
this.eventTrapInput.focus();
}
};
ns.CheatsheetController.prototype.onEventTrapKeydown_ = function (evt) {
var shortcutEl = document.querySelector('.' + SHORTCUT_EDITING_CLASSNAME);
if (!shortcutEl) {
return;
}
var shortcutId = shortcutEl.dataset.shortcutId;
var shortcut = pskl.app.shortcutService.getShortcutById(shortcutId);
var shortcutKeyObject = pskl.service.keyboard.KeyUtils.createKeyFromEvent(evt);
var shortcutKeyString = pskl.service.keyboard.KeyUtils.stringify(shortcutKeyObject);
pskl.app.shortcutService.updateShortcut(shortcut, shortcutKeyString);
shortcutEl.classList.remove(SHORTCUT_EDITING_CLASSNAME);
this.eventTrapInput.blur();
evt.preventDefault();
};
ns.CheatsheetController.prototype.initMarkup_ = function () {
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.getToolIconClass_ = function (shortcut) {
return 'tool-icon ' + shortcut.getId();
};
ns.CheatsheetController.prototype.initMarkupForCategory_ = function (category, container, iconClassProvider) {
var shortcutMap = pskl.service.keyboard.Shortcuts[category];
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, iconClassProvider) {
var iconClass = typeof iconClassProvider == 'function' ? iconClassProvider(shortcut) : '';
return {
'shortcut' : shortcut,
'iconClass' : iconClass
};
};
ns.CheatsheetController.prototype.initMarkupForDescriptors_ = function (descriptors, containerSelector) {
var container = document.querySelector(containerSelector);
if (!container) {
return;
}
var markupArray = descriptors.map(this.getMarkupForDescriptor_.bind(this));
container.innerHTML = markupArray.join('');
};
ns.CheatsheetController.prototype.getMarkupForDescriptor_ = function (descriptor) {
var shortcutTemplate = pskl.utils.Template.get('cheatsheet-shortcut-template');
var shortcut = descriptor.shortcut;
var description = shortcut.isCustom() ? shortcut.getDescription() + ' *' : shortcut.getDescription();
var shortcutClasses = [];
if (shortcut.isUndefined()) {
shortcutClasses.push('cheatsheet-shortcut-undefined');
}
if (shortcut.isEditable()) {
shortcutClasses.push('cheatsheet-shortcut-editable');
}
var title = shortcut.isEditable() ? 'Click to edit the key' : 'Shortcut cannot be remapped';
var markup = pskl.utils.Template.replace(shortcutTemplate, {
id : shortcut.getId(),
title : title,
icon : descriptor.iconClass,
description : description,
key : this.formatKey_(shortcut.getDisplayKey()),
className : shortcutClasses.join(' ')
});
return markup;
};
ns.CheatsheetController.prototype.formatKey_ = function (key) {
if (pskl.utils.UserAgent.isMac) {
key = key.replace('ctrl', 'cmd');
}
key = key.replace(/up/i, '&#65514;');
key = key.replace(/down/i, '&#65516;');
key = key.replace(/>/g, '&gt;');
key = key.replace(/</g, '&lt;');
// add spaces around '+' delimiters
key = key.replace(/([^ ])\+([^ ])/g, '$1 + $2');
return key;
};
ns.CheatsheetController.prototype.getHelptextTitle_ = function () {
var helpItems = [
'Click on a shortcut to change the key.',
'When the shortcut blinks, press the key on your keyboard to assign it.',
'White shortcuts can not be edited.',
'Click on \'Restore default shortcuts\' to erase all custom shortcuts.'
];
var helptextTitle = helpItems.reduce(function (p, n) {
return p + '<div class="cheatsheet-helptext-tooltip-item">' + n + '</div>';
}, '');
helptextTitle = '<div class="cheatsheet-helptext-tooltip">' + helptextTitle + '</div>';
return helptextTitle;
};
})();

View File

@ -2,6 +2,10 @@
var ns = $.namespace('pskl.controller.dialogs');
var dialogs = {
'cheatsheet' : {
template : 'templates/dialogs/cheatsheet.html',
controller : ns.CheatsheetController
},
'create-palette' : {
template : 'templates/dialogs/create-palette.html',
controller : ns.CreatePaletteController
@ -18,78 +22,108 @@
ns.DialogsController = function (piskelController) {
this.piskelController = piskelController;
this.closePopupShortcut = pskl.service.keyboard.Shortcuts.MISC.CLOSE_POPUP;
this.currentDialog_ = null;
};
ns.DialogsController.prototype.init = function () {
this.dialogContainer_ = document.getElementById('dialog-container');
this.dialogWrapper_ = document.getElementById('dialog-container-wrapper');
$.subscribe(Events.DIALOG_DISPLAY, this.onDialogDisplayEvent_.bind(this));
$.subscribe(Events.DIALOG_HIDE, this.onDialogHideEvent_.bind(this));
$.subscribe(Events.DIALOG_HIDE, this.hideDialog.bind(this));
pskl.app.shortcutService.addShortcut('alt+P', this.onDialogDisplayEvent_.bind(this, null, 'create-palette'));
var createPaletteShortcut = pskl.service.keyboard.Shortcuts.COLOR.CREATE_PALETTE;
pskl.app.shortcutService.registerShortcut(createPaletteShortcut, this.onCreatePaletteShortcut_.bind(this));
var cheatsheetShortcut = pskl.service.keyboard.Shortcuts.MISC.CHEATSHEET;
pskl.app.shortcutService.registerShortcut(cheatsheetShortcut, this.onCheatsheetShortcut_.bind(this));
pskl.utils.Event.addEventListener('.cheatsheet-link', 'click', this.onCheatsheetShortcut_, this);
// adding the .animated class here instead of in the markup to avoid an animation during app startup
this.dialogWrapper_.classList.add('animated');
};
ns.DialogsController.prototype.onCreatePaletteShortcut_ = function () {
this.toggleDialog_('create-palette');
};
ns.DialogsController.prototype.onCheatsheetShortcut_ = function () {
this.toggleDialog_('cheatsheet');
};
/**
* If no dialog is currently displayed, the dialog with the provided id will be displayed.
* If a dialog is displayed and has the same id as the provided id, hide it.
* Otherwise, no-op.
*/
ns.DialogsController.prototype.toggleDialog_ = function (dialogId) {
if (!this.isDisplayingDialog_()) {
this.showDialog(dialogId);
} else if (this.getCurrentDialogId_() === dialogId) {
this.hideDialog();
}
};
ns.DialogsController.prototype.onDialogDisplayEvent_ = function (evt, args) {
var dialogId, initArgs;
if (typeof args === 'string') {
dialogId = args;
} else {
dialogId = args.dialogId;
initArgs = args.initArgs;
}
if (!this.isDisplayed()) {
var config = dialogs[dialogId];
if (config) {
this.dialogContainer_.classList.add(dialogId);
this.dialogContainer_.innerHTML = pskl.utils.Template.get(config.template);
var controller = new config.controller(this.piskelController);
controller.init(initArgs);
this.showDialogWrapper_();
this.currentDialog_ = {
id : dialogId,
controller : controller
};
} else {
console.error('Could not find dialog configuration for dialogId : ' + dialogId);
}
}
this.showDialog(args.dialogId, args.initArgs);
};
ns.DialogsController.prototype.onDialogHideEvent_ = function () {
this.hideDialog();
};
ns.DialogsController.prototype.showDialog = function (dialogId, initArgs) {
if (this.isDisplayingDialog_()) {
return;
}
ns.DialogsController.prototype.showDialogWrapper_ = function () {
pskl.app.shortcutService.addShortcut('ESC', this.hideDialog.bind(this));
var config = dialogs[dialogId];
if (!config) {
console.error('Could not find dialog configuration for dialogId : ' + dialogId);
return;
}
this.dialogContainer_.classList.add(dialogId);
this.dialogContainer_.innerHTML = pskl.utils.Template.get(config.template);
var controller = new config.controller(this.piskelController);
controller.init(initArgs);
this.currentDialog_ = {
id : dialogId,
controller : controller
};
pskl.app.shortcutService.registerShortcut(this.closePopupShortcut, this.hideDialog.bind(this));
this.dialogWrapper_.classList.add('show');
};
ns.DialogsController.prototype.hideDialog = function () {
var currentDialog = this.currentDialog_;
if (currentDialog) {
currentDialog.controller.destroy();
var dialogId = this.currentDialog_.id;
window.setTimeout(function () {
this.dialogContainer_.classList.remove(dialogId);
}.bind(this), 800);
if (this.isHiding_ || !this.isDisplayingDialog_()) {
return;
}
this.hideDialogWrapper_();
this.currentDialog_ = null;
};
ns.DialogsController.prototype.hideDialogWrapper_ = function () {
pskl.app.shortcutService.removeShortcut('ESC');
pskl.app.shortcutService.unregisterShortcut(this.closePopupShortcut);
this.dialogWrapper_.classList.remove('show');
window.setTimeout(this.cleanupDialogContainer_.bind(this), 500);
this.isHiding_ = true;
};
ns.DialogsController.prototype.isDisplayed = function () {
ns.DialogsController.prototype.cleanupDialogContainer_ = function () {
this.dialogContainer_.classList.remove(this.currentDialog_.id);
this.currentDialog_.controller.destroy();
this.currentDialog_ = null;
this.dialogContainer_.innerHTML = '';
this.isHiding_ = false;
};
ns.DialogsController.prototype.isDisplayingDialog_ = function () {
return this.currentDialog_ !== null;
};
ns.DialogsController.prototype.getCurrentDialogId_ = function () {
if (this.currentDialog_) {
return this.currentDialog_.id;
}
return null;
};
})();

View File

@ -30,10 +30,11 @@
this.saveWrap_('moveLayerDown', true);
this.saveWrap_('removeCurrentLayer', true);
pskl.app.shortcutService.addShortcut('up', this.selectPreviousFrame.bind(this));
pskl.app.shortcutService.addShortcut('down', this.selectNextFrame.bind(this));
pskl.app.shortcutService.addShortcut('n', this.addFrameAtCurrentIndex.bind(this));
pskl.app.shortcutService.addShortcut('shift+n', this.duplicateCurrentFrame.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.PREVIOUS_FRAME, this.selectPreviousFrame.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.NEXT_FRAME, this.selectNextFrame.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.NEW_FRAME, this.addFrameAtCurrentIndex.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.MISC.DUPLICATE_FRAME, this.duplicateCurrentFrame.bind(this));
};
ns.PublicPiskelController.prototype.setPiskel = function (piskel, preserveState) {

View File

@ -14,6 +14,9 @@
this.elapsedTime = 0;
this.currentIndex = 0;
this.onionSkinShortcut = pskl.service.keyboard.Shortcuts.MISC.ONION_SKIN;
this.originalSizeShortcut = pskl.service.keyboard.Shortcuts.MISC.X1_PREVIEW;
this.renderFlag = true;
/**
@ -47,8 +50,8 @@
pskl.utils.Event.addEventListener(this.openPopupPreview, 'click', this.onOpenPopupPreviewClick_, this);
pskl.utils.Event.addEventListener(this.originalSizeButton, 'click', this.onOriginalSizeButtonClick_, this);
pskl.app.shortcutService.addShortcut(ONION_SKIN_SHORTCUT, this.toggleOnionSkin_.bind(this));
pskl.app.shortcutService.addShortcut(ORIGINAL_SIZE_SHORTCUT, this.onOriginalSizeButtonClick_.bind(this));
pskl.app.shortcutService.registerShortcut(this.onionSkinShortcut, this.toggleOnionSkin_.bind(this));
pskl.app.shortcutService.registerShortcut(this.originalSizeShortcut, this.onOriginalSizeButtonClick_.bind(this));
$.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
@ -66,9 +69,9 @@
};
ns.PreviewController.prototype.initTooltips_ = function () {
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', ONION_SKIN_SHORTCUT);
var onionSkinTooltip = pskl.utils.TooltipFormatter.format('Toggle onion skin', this.onionSkinShortcut);
this.toggleOnionSkinButton.setAttribute('title', onionSkinTooltip);
var originalSizeTooltip = pskl.utils.TooltipFormatter.format('Original size preview', ORIGINAL_SIZE_SHORTCUT);
var originalSizeTooltip = pskl.utils.TooltipFormatter.format('Original size preview', this.originalSizeShortcut);
this.originalSizeButton.setAttribute('title', originalSizeTooltip);
};

View File

@ -71,7 +71,9 @@
};
ns.ImportController.prototype.onBrowseLocalClick_ = function (evt) {
$.publish(Events.DIALOG_DISPLAY, 'browse-local');
$.publish(Events.DIALOG_DISPLAY, {
dialogId : 'browse-local'
});
this.closeDrawer_();
};

View File

@ -129,7 +129,7 @@
}
event.preventDefault = function () {};
pskl.app.shortcutService.onKeyUp_(event);
pskl.app.shortcutService.onKeyDown_(event);
};
ns.DrawingTestPlayer.prototype.playColorEvent_ = function (recordEvent) {

View File

@ -18,11 +18,11 @@
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this));
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this));
pskl.app.shortcutService.addShortcut('ctrl+V', this.paste.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+X', this.cut.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+C', this.copy.bind(this));
pskl.app.shortcutService.addShortcut('del', this.erase.bind(this));
pskl.app.shortcutService.addShortcut('back', this.onBackPressed_.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.PASTE, this.paste.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.CUT, this.cut.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COPY, this.copy.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.DELETE, this.onDeleteShortcut_.bind(this));
$.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this));
};
@ -54,7 +54,7 @@
this.cleanSelection_();
};
ns.SelectionManager.prototype.onBackPressed_ = function(evt) {
ns.SelectionManager.prototype.onDeleteShortcut_ = function(evt) {
if (this.currentSelection) {
this.erase();
} else {

View File

@ -26,9 +26,9 @@
ns.HistoryService.prototype.init = function () {
$.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this));
this.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this));
this.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this));
this.shortcutService.addShortcut('ctrl+shift+Z', this.redo.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
this.shortcutService.registerShortcut(shortcuts.MISC.UNDO, this.undo.bind(this));
this.shortcutService.registerShortcut(shortcuts.MISC.REDO, this.redo.bind(this));
this.saveState({
type : ns.HistoryService.SNAPSHOT

View File

@ -1,150 +0,0 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
ns.CheatsheetService = function () {
this.isDisplayed = false;
};
ns.CheatsheetService.prototype.init = function () {
this.cheatsheetLinkEl = document.querySelector('.cheatsheet-link');
this.cheatsheetEl = document.getElementById('cheatsheet-wrapper');
if (!this.cheatsheetEl) {
throw 'cheatsheetEl DOM element could not be retrieved';
}
this.initMarkup_();
pskl.app.shortcutService.addShortcuts(['?', 'shift+?'], this.toggleCheatsheet_.bind(this));
pskl.utils.Event.addEventListener(document.body, 'click', this.onBodyClick_, this);
$.subscribe(Events.TOGGLE_HELP, this.toggleCheatsheet_.bind(this));
$.subscribe(Events.ESCAPE, this.onEscape_.bind(this));
};
ns.CheatsheetService.prototype.onBodyClick_ = function (evt) {
var isOnCheatsheet = this.cheatsheetEl.contains(evt.target);
var isOnLink = this.cheatsheetLinkEl.contains(evt.target);
if (isOnLink) {
this.toggleCheatsheet_();
} else if (!isOnCheatsheet) {
this.hideCheatsheet_();
}
};
ns.CheatsheetService.prototype.toggleCheatsheet_ = function () {
if (this.isDisplayed) {
this.hideCheatsheet_();
} else {
this.showCheatsheet_();
}
};
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;
};
ns.CheatsheetService.prototype.initMarkup_ = function () {
this.initMarkupForTools_();
this.initMarkupForMisc_();
this.initMarkupForColors_();
this.initMarkupForSelection_();
};
ns.CheatsheetService.prototype.initMarkupForTools_ = function () {
var descriptors = pskl.app.toolController.tools.map(function (tool) {
return this.toDescriptor_(tool.shortcut, tool.instance.getHelpText(), 'tool-icon ' + tool.instance.toolId);
}.bind(this));
var container = this.cheatsheetEl.querySelector('.cheatsheet-tool-shortcuts');
this.initMarkupForDescriptors_(descriptors, container);
};
ns.CheatsheetService.prototype.initMarkupForMisc_ = function () {
var descriptors = [
this.toDescriptor_('0', 'Reset zoom level'),
this.toDescriptor_('+/-', 'Zoom in/Zoom out'),
this.toDescriptor_('ctrl + Z', 'Undo'),
this.toDescriptor_('ctrl + Y', 'Redo'),
this.toDescriptor_('&#65514;', 'Select previous frame'), /* ASCII for up-arrow */
this.toDescriptor_('&#65516;', 'Select next frame'), /* ASCII for down-arrow */
this.toDescriptor_('N', 'Create new frame'),
this.toDescriptor_('shift + N', 'Duplicate selected frame'),
this.toDescriptor_('shift + ?', 'Open/Close this popup'),
this.toDescriptor_('alt + 1', 'Toggle original size preview'),
this.toDescriptor_('alt + O', 'Toggle Onion Skin'),
this.toDescriptor_('alt + L', 'Toggle Layer Preview')
];
var container = this.cheatsheetEl.querySelector('.cheatsheet-misc-shortcuts');
this.initMarkupForDescriptors_(descriptors, container);
};
ns.CheatsheetService.prototype.initMarkupForColors_ = function () {
var descriptors = [
this.toDescriptor_('X', 'Swap primary/secondary colors'),
this.toDescriptor_('D', 'Reset default colors'),
this.toDescriptor_('alt + P', 'Create a Palette'),
this.toDescriptor_('&lt;/&gt;', 'Select prev/next palette color'),
this.toDescriptor_('1 to 9', 'Select palette color at index')
];
var container = this.cheatsheetEl.querySelector('.cheatsheet-colors-shortcuts');
this.initMarkupForDescriptors_(descriptors, container);
};
ns.CheatsheetService.prototype.initMarkupForSelection_ = function () {
var descriptors = [
this.toDescriptor_('ctrl + X', 'Cut selection'),
this.toDescriptor_('ctrl + C', 'Copy selection'),
this.toDescriptor_('ctrl + V', 'Paste selection'),
this.toDescriptor_('del', 'Delete selection')
];
var container = this.cheatsheetEl.querySelector('.cheatsheet-selection-shortcuts');
this.initMarkupForDescriptors_(descriptors, container);
};
ns.CheatsheetService.prototype.toDescriptor_ = function (shortcut, description, icon) {
if (pskl.utils.UserAgent.isMac) {
shortcut = shortcut.replace('ctrl', 'cmd');
}
return {
'shortcut' : shortcut,
'description' : description,
'icon' : icon
};
};
ns.CheatsheetService.prototype.initMarkupForDescriptors_ = function (descriptors, container) {
descriptors.forEach(function (descriptor) {
var shortcut = this.getDomFromDescriptor_(descriptor);
container.appendChild(shortcut);
}.bind(this));
};
ns.CheatsheetService.prototype.getDomFromDescriptor_ = function (descriptor) {
var shortcutTemplate = pskl.utils.Template.get('cheatsheet-shortcut-template');
var markup = pskl.utils.Template.replace(shortcutTemplate, {
shortcutIcon : descriptor.icon,
shortcutDescription : descriptor.description,
shortcutKey : descriptor.shortcut
});
return pskl.utils.Template.createFromHTML(markup);
};
})();

View File

@ -0,0 +1,80 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
ns.KeyUtils = {
createKeyFromString : function (shortcutKeyString) {
shortcutKeyString = shortcutKeyString.toLowerCase();
var modifiers = {
alt : shortcutKeyString.indexOf('alt+') != -1,
shift : shortcutKeyString.indexOf('shift+') != -1,
ctrl : shortcutKeyString.indexOf('ctrl+') != -1
};
var parts = shortcutKeyString.split(/\+(?!$)/);
var key = parts[parts.length - 1];
return {
key : key.toUpperCase(),
modifiers : modifiers
};
},
createKeyFromEvent : function (evt) {
var keycode = evt.which;
var key = ns.KeycodeTranslator.toChar(keycode);
if (!key) {
return null;
}
return {
key : key.toUpperCase(),
modifiers : {
alt : evt.altKey,
shift : evt.shiftKey,
ctrl : ns.KeyUtils.isCtrlKeyPressed_(evt)
}
};
},
equals : function (key1, key2) {
key1 = typeof key1 === 'string' ? ns.KeyUtils.createKeyFromString(key1) : key1;
key2 = typeof key2 === 'string' ? ns.KeyUtils.createKeyFromString(key2) : key2;
var isKeyMatching = key1.key === key2.key &&
key1.modifiers.alt === key2.modifiers.alt &&
key1.modifiers.shift === key2.modifiers.shift &&
key1.modifiers.ctrl === key2.modifiers.ctrl;
return isKeyMatching;
},
stringify : function (shortcutKeyObject) {
var modifierString = ns.KeyUtils.getModifiersString(shortcutKeyObject.modifiers);
if (modifierString) {
return modifierString + '+' + shortcutKeyObject.key;
}
return shortcutKeyObject.key;
},
getModifiersString : function (modifiers) {
var keyBuffer = [];
if (modifiers.alt) {
keyBuffer.push('alt');
}
if (modifiers.ctrl) {
keyBuffer.push('ctrl');
}
if (modifiers.shift) {
keyBuffer.push('shift');
}
return keyBuffer.join('+');
},
isCtrlKeyPressed_ : function (evt) {
return pskl.utils.UserAgent.isMac ? evt.metaKey : evt.ctrlKey;
}
};
})();

View File

@ -17,7 +17,9 @@
// 61 on Firefox for =/+ key
61 : '+',
188 : '<',
190 : '>'
190 : '>',
219 : '[',
221 : ']'
};
var ns = $.namespace('pskl.service.keyboard');

View File

@ -0,0 +1,124 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
/**
* Keyboard shortcut wrapper, use it to register on the ShortcutService.
*
* @param {String} id Shortcut identifier
* @param {String} description Shortcut description
* @param {String|Array<String>} 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, defaultKeys, displayKey) {
this.id_ = id;
this.description_ = description;
if (typeof defaultKeys === 'string') {
defaultKeys = [defaultKeys];
}
this.defaultKeys_ = defaultKeys;
this.displayKey_ = displayKey;
};
ns.Shortcut.USER_SETTINGS_PREFIX = 'shortcut.';
ns.Shortcut.prototype.getId = function () {
return this.id_;
};
ns.Shortcut.prototype.getDescription = function () {
return this.description_;
};
/**
* Retrieve the array of String keys that match this shortcut
* @return {Array<String>} array of keys
*/
ns.Shortcut.prototype.getKeys = function () {
var keys = pskl.UserSettings.get(this.getLocalStorageKey_()) || this.defaultKeys_;
if (typeof keys === 'string') {
return [keys];
}
if (!Array.isArray(keys)) {
return [];
}
return keys;
};
/**
* For now, only shortcuts with a single key mapped can be edited
* @return {Boolean} true if the shortcut can be updated
*/
ns.Shortcut.prototype.isEditable = function () {
return this.getKeys().length < 2;
};
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.getDisplayKey = function () {
if (this.isUndefined()) {
return '???';
}
if (this.displayKey_) {
return this.displayKey_;
}
return this.getKeys()[0];
};
ns.Shortcut.prototype.restoreDefault = function (keys) {
pskl.UserSettings.set(this.getLocalStorageKey_(), '');
};
ns.Shortcut.prototype.updateKeys = function (keys) {
pskl.UserSettings.set(this.getLocalStorageKey_(), keys);
};
ns.Shortcut.prototype.removeKeys = function (keysToRemove) {
if (!this.isEditable()) {
return;
}
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 () {
return ns.Shortcut.USER_SETTINGS_PREFIX + this.id_;
};
})();

View File

@ -2,111 +2,70 @@
var ns = $.namespace('pskl.service.keyboard');
ns.ShortcutService = function () {
this.shortcuts_ = {};
this.shortcuts_ = [];
};
/**
* @public
*/
ns.ShortcutService.prototype.init = function() {
$(document.body).keydown($.proxy(this.onKeyUp_, this));
$(document.body).keydown($.proxy(this.onKeyDown_, this));
};
/**
* Add a keyboard shortcut
* @param {String} rawKey (case insensitive) a key is a combination of modifiers + ([a-z0-9] or
* a special key) (check list of supported special keys in KeycodeTranslator)
* eg. 'ctrl+A',
* 'del'
* 'ctrl+shift+S'
* @param {pskl.service.keyboard.Shortcut} shortcut
* @param {Function} callback should return true to let the original event perform its default action
*/
ns.ShortcutService.prototype.addShortcut = function (rawKey, callback) {
var parsedKey = this.parseKey_(rawKey.toLowerCase());
var key = parsedKey.key;
var meta = parsedKey.meta;
this.shortcuts_[key] = this.shortcuts_[key] || {};
if (this.shortcuts_[key][meta]) {
var keyStr = (meta !== 'normal' ? meta + ' + ' : '') + key;
console.error('[ShortcutService] >>> Shortcut [' + keyStr + '] already registered');
} else {
this.shortcuts_[key][meta] = callback;
ns.ShortcutService.prototype.registerShortcut = function (shortcut, callback) {
if (!(shortcut instanceof ns.Shortcut)) {
throw 'Invalid shortcut argument, please use instances of pskl.service.keyboard.Shortcut';
}
};
ns.ShortcutService.prototype.addShortcuts = function (keys, callback) {
keys.forEach(function (key) {
this.addShortcut(key, callback);
}.bind(this));
};
if (typeof callback != 'function') {
throw 'Invalid callback argument, please provide a function';
}
ns.ShortcutService.prototype.removeShortcut = function (rawKey) {
var parsedKey = this.parseKey_(rawKey.toLowerCase());
var key = parsedKey.key;
var meta = parsedKey.meta;
this.shortcuts_[key] = this.shortcuts_[key] || {};
this.shortcuts_[key][meta] = null;
};
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
this.shortcuts_.push({
shortcut : shortcut,
callback : callback
});
var parts = key.split(/\+(?!$)/);
key = parts[parts.length - 1];
return {meta : meta, key : key};
};
ns.ShortcutService.prototype.getMetaKey_ = function (meta) {
var keyBuffer = [];
['alt', 'ctrl', 'shift'].forEach(function (metaKey) {
if (meta[metaKey]) {
keyBuffer.push(metaKey);
ns.ShortcutService.prototype.unregisterShortcut = function (shortcut) {
var index = -1;
this.shortcuts_.forEach(function (s, i) {
if (s.shortcut === shortcut) {
index = i;
}
});
if (keyBuffer.length > 0) {
return keyBuffer.join('+');
} else {
return 'normal';
if (index != -1) {
this.shortcuts_.splice(index, 1);
}
};
/**
* @private
*/
ns.ShortcutService.prototype.onKeyUp_ = function(evt) {
if (!this.isInInput_(evt)) {
// jquery names FTW ...
var keycode = evt.which;
var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode);
var keyShortcuts = this.shortcuts_[charkey];
if (keyShortcuts) {
var meta = this.getMetaKey_({
alt : this.isAltKeyPressed_(evt),
shift : this.isShiftKeyPressed_(evt),
ctrl : this.isCtrlKeyPressed_(evt)
});
var cb = keyShortcuts[meta];
if (cb) {
var bubble = cb(charkey);
if (bubble !== true) {
evt.preventDefault();
}
$.publish(Events.KEYBOARD_EVENT, [evt]);
}
}
ns.ShortcutService.prototype.onKeyDown_ = function(evt) {
var eventKey = ns.KeyUtils.createKeyFromEvent(evt);
if (this.isInInput_(evt) || !eventKey) {
return;
}
this.shortcuts_.forEach(function (shortcutInfo) {
shortcutInfo.shortcut.getKeys().forEach(function (shortcutKey) {
if (!ns.KeyUtils.equals(shortcutKey, eventKey)) {
return;
}
var bubble = shortcutInfo.callback(eventKey.key);
if (bubble !== true) {
evt.preventDefault();
}
$.publish(Events.KEYBOARD_EVENT, [evt]);
}.bind(this));
}.bind(this));
};
ns.ShortcutService.prototype.isInInput_ = function (evt) {
@ -114,15 +73,58 @@
return targetTagName === 'INPUT' || targetTagName === 'TEXTAREA';
};
ns.ShortcutService.prototype.isCtrlKeyPressed_ = function (evt) {
return pskl.utils.UserAgent.isMac ? evt.metaKey : evt.ctrlKey;
ns.ShortcutService.prototype.getShortcutById = function (id) {
return pskl.utils.Array.find(this.getShortcuts(), function (shortcut) {
return shortcut.getId() === id;
});
};
ns.ShortcutService.prototype.isShiftKeyPressed_ = function (evt) {
return evt.shiftKey;
ns.ShortcutService.prototype.getShortcuts = function () {
var shortcuts = [];
ns.Shortcuts.CATEGORIES.forEach(function (category) {
var shortcutMap = ns.Shortcuts[category];
Object.keys(shortcutMap).forEach(function (shortcutKey) {
shortcuts.push(shortcutMap[shortcutKey]);
});
});
return shortcuts;
};
ns.ShortcutService.prototype.isAltKeyPressed_ = function (evt) {
return evt.altKey;
ns.ShortcutService.prototype.updateShortcut = function (shortcut, keyAsString) {
var key = keyAsString.replace(/\s/g, '');
var isForbiddenKey = ns.Shortcuts.FORBIDDEN_KEYS.indexOf(key) != -1;
if (isForbiddenKey) {
$.publish(Events.SHOW_NOTIFICATION, [{
'content': 'Key cannot be remapped (' + keyAsString + ')',
'hideDelay' : 5000
}]);
} else {
this.removeKeyFromAllShortcuts_(key);
shortcut.updateKeys([key]);
$.publish(Events.SHORTCUTS_CHANGED);
}
};
ns.ShortcutService.prototype.removeKeyFromAllShortcuts_ = function (key) {
this.getShortcuts().forEach(function (s) {
if (s.removeKeys([key])) {
$.publish(Events.SHOW_NOTIFICATION, [{
'content': 'Shortcut key removed for ' + s.getId(),
'hideDelay' : 5000
}]);
}
});
};
/**
* Restore the default piskel key for all shortcuts
*/
ns.ShortcutService.prototype.restoreDefaultShortcuts = function () {
this.getShortcuts().forEach(function (shortcut) {
shortcut.restoreDefault();
});
$.publish(Events.SHORTCUTS_CHANGED);
};
})();

View File

@ -0,0 +1,79 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
var createShortcut = function (id, description, defaultKey, displayKey) {
return new ns.Shortcut(id, description, defaultKey, displayKey);
};
ns.Shortcuts = {
/**
* List of keys that cannot be remapped. Either alternate keys, which are not displayed.
* Or really custom shortcuts such as the 1-9 for color palette shorctus
*/
FORBIDDEN_KEYS : ['1', '2', '3', '4', '5', '6', '7', '8', '9', '?', 'shift+?',
'del', 'back', 'ctrl+Y', 'ctrl+shift+Z'],
/**
* Syntax : createShortcut(id, description, default key(s))
*/
TOOL : {
PEN : createShortcut('tool-pen', 'Pen tool', 'P'),
MIRROR_PEN : createShortcut('tool-vertical-mirror-pen', 'Vertical mirror pen tool', 'V'),
PAINT_BUCKET : createShortcut('tool-paint-bucket', 'Paint bucket tool', 'B'),
COLORSWAP : createShortcut('tool-colorswap', 'Magic bucket tool', 'A'),
ERASER : createShortcut('tool-eraser', 'Eraser pen tool', 'E'),
STROKE : createShortcut('tool-stroke', 'Stroke tool', 'L'),
RECTANGLE : createShortcut('tool-rectangle', 'Rectangle tool', 'R'),
CIRCLE : createShortcut('tool-circle', 'Circle tool', 'C'),
MOVE : createShortcut('tool-move', 'Move tool', 'M'),
SHAPE_SELECT : createShortcut('tool-shape-select', 'Shape selection', 'Z'),
RECTANGLE_SELECT : createShortcut('tool-rectangle-select', 'Rectangle selection', 'S'),
LASSO_SELECT : createShortcut('tool-lasso-select', 'Lasso selection', 'H'),
LIGHTEN : createShortcut('tool-lighten', 'Lighten tool', 'U'),
DITHERING : createShortcut('tool-dithering', 'Dithering tool', 'T'),
COLORPICKER : createShortcut('tool-colorpicker', 'Color picker', 'O')
},
SELECTION : {
CUT : createShortcut('selection-cut', 'Cut selection', 'ctrl+X'),
COPY : createShortcut('selection-copy', 'Copy selection', 'ctrl+C'),
PASTE : createShortcut('selection-paste', 'Paste selection', 'ctrl+V'),
DELETE : createShortcut('selection-delete', 'Delete selection', ['del', 'back'])
},
MISC : {
RESET_ZOOM : createShortcut('reset-zoom', 'Reset zoom level', '0'),
INCREASE_ZOOM : createShortcut('increase-zoom', 'Increase zoom level', '+'),
DECREASE_ZOOM : createShortcut('decrease-zoom', 'Decrease zoom level', '-'),
UNDO : createShortcut('undo', 'Undo', 'ctrl+Z'),
REDO : createShortcut('redo', 'Redo', ['ctrl+Y', 'ctrl+shift+Z']),
PREVIOUS_FRAME : createShortcut('previous-frame', 'Select previous frame', 'up'),
NEXT_FRAME : createShortcut('next-frame', 'Select next frame', 'down'),
NEW_FRAME : createShortcut('new-frame', 'Create new empty frame', 'N'),
DUPLICATE_FRAME : createShortcut('duplicate-frame', 'Duplicate selected frame', 'shift+N'),
CHEATSHEET : createShortcut('cheatsheet', 'Open the keyboard shortcut cheatsheet', ['?', 'shift+?']),
X1_PREVIEW : createShortcut('x1-preview', 'Toggle original size preview', 'alt+1'),
ONION_SKIN : createShortcut('onion-skin', 'Toggle onion skin', 'alt+O'),
LAYER_PREVIEW : createShortcut('layer-preview', 'Toggle layer preview', 'alt+L'),
CLOSE_POPUP : createShortcut('close-popup', 'Close an opened popup', 'ESC')
},
STORAGE : {
SAVE : createShortcut('save', 'Save the current sprite', 'ctrl+S'),
OPEN : createShortcut('open', '(desktop) Open a .piskel file', 'ctrl+O'),
SAVE_AS : createShortcut('save-as', '(desktop) Save as new', 'ctrl+shift+S')
},
COLOR : {
SWAP : createShortcut('swap-colors', 'Swap primary/secondary colors', 'X'),
RESET : createShortcut('reset-colors', 'Reset default colors', 'D'),
CREATE_PALETTE : createShortcut('create-palette', 'Open the palette creation popup', 'alt+P'),
PREVIOUS_COLOR : createShortcut('previous-color', 'Select the previous color in the current palette', '<'),
NEXT_COLOR : createShortcut('next-color', 'Select the next color in the current palette', '>'),
SELECT_COLOR : createShortcut('select-color', 'Select a palette color in the current palette',
'123456789'.split(''), '1 to 9')
},
CATEGORIES : ['TOOL', 'SELECTION', 'MISC', 'STORAGE', 'COLOR']
};
})();

View File

@ -10,9 +10,10 @@
};
ns.StorageService.prototype.init = function () {
pskl.app.shortcutService.addShortcut('ctrl+o', this.onOpenKey_.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+s', this.onSaveKey_.bind(this));
pskl.app.shortcutService.addShortcut('ctrl+shift+s', this.onSaveAsKey_.bind(this));
var shortcuts = pskl.service.keyboard.Shortcuts;
pskl.app.shortcutService.registerShortcut(shortcuts.STORAGE.OPEN, this.onOpenKey_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.STORAGE.SAVE, this.onSaveKey_.bind(this));
pskl.app.shortcutService.registerShortcut(shortcuts.STORAGE.SAVE_AS, this.onSaveAsKey_.bind(this));
$.subscribe(Events.BEFORE_SAVING_PISKEL, this.setSavingFlag_.bind(this, true));
$.subscribe(Events.AFTER_SAVING_PISKEL, this.setSavingFlag_.bind(this, false));

View File

@ -3,19 +3,19 @@
ns.IconMarkupRenderer = function () {};
ns.IconMarkupRenderer.prototype.render = function (tool, shortcut, tooltipPosition) {
ns.IconMarkupRenderer.prototype.render = function (tool, tooltipPosition) {
tooltipPosition = tooltipPosition || 'right';
var tpl = pskl.utils.Template.get('drawingTool-item-template');
return pskl.utils.Template.replace(tpl, {
cssclass : ['tool-icon', tool.toolId].join(' '),
toolid : tool.toolId,
title : this.getTooltipText(tool, shortcut),
title : this.getTooltipText(tool),
tooltipposition : tooltipPosition
});
};
ns.IconMarkupRenderer.prototype.getTooltipText = function(tool, shortcut) {
ns.IconMarkupRenderer.prototype.getTooltipText = function(tool) {
var descriptors = tool.tooltipDescriptors;
return pskl.utils.TooltipFormatter.format(tool.getHelpText(), shortcut, descriptors);
return pskl.utils.TooltipFormatter.format(tool.getHelpText(), tool.shortcut, descriptors);
};
})();

View File

@ -11,6 +11,7 @@
this.toolId = 'tool-circle';
this.helpText = 'Circle tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.CIRCLE;
};
pskl.utils.inherit(ns.Circle, ns.ShapeTool);

View File

@ -9,6 +9,7 @@
ns.ColorPicker = function() {
this.toolId = 'tool-colorpicker';
this.helpText = 'Color picker';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.COLORPICKER;
};
pskl.utils.inherit(ns.ColorPicker, ns.BaseTool);

View File

@ -7,8 +7,8 @@
ns.ColorSwap = function() {
this.toolId = 'tool-colorswap';
this.helpText = 'Paint all pixels of the same color';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.COLORSWAP;
this.tooltipDescriptors = [
{key : 'ctrl', description : 'Apply to all layers'},

View File

@ -10,6 +10,7 @@
ns.SimplePen.call(this);
this.toolId = 'tool-dithering';
this.helpText = 'Dithering tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.DITHERING;
};
pskl.utils.inherit(ns.DitheringTool, ns.SimplePen);

View File

@ -9,8 +9,10 @@
ns.Eraser = function() {
this.superclass.constructor.call(this);
this.toolId = 'tool-eraser';
this.helpText = 'Eraser tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.ERASER;
};
pskl.utils.inherit(ns.Eraser, ns.SimplePen);

View File

@ -10,9 +10,10 @@
ns.Lighten = function() {
this.superclass.constructor.call(this);
this.toolId = 'tool-lighten';
this.toolId = 'tool-lighten';
this.helpText = 'Lighten';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.LIGHTEN;
this.tooltipDescriptors = [
{key : 'ctrl', description : 'Darken'},

View File

@ -9,6 +9,7 @@
ns.Move = function() {
this.toolId = ns.Move.TOOL_ID;
this.helpText = 'Move tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.MOVE;
this.tooltipDescriptors = [
{key : 'ctrl', description : 'Apply to all layers'},
@ -21,6 +22,10 @@
this.startRow = null;
};
/**
* The move tool id is used by the ToolController and the BaseSelect and needs to be
* easliy accessible
*/
ns.Move.TOOL_ID = 'tool-move';
pskl.utils.inherit(ns.Move, ns.BaseTool);

View File

@ -9,6 +9,7 @@
ns.PaintBucket = function() {
this.toolId = 'tool-paint-bucket';
this.helpText = 'Paint bucket tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.PAINT_BUCKET;
};
pskl.utils.inherit(ns.PaintBucket, ns.BaseTool);

View File

@ -11,6 +11,7 @@
this.toolId = 'tool-rectangle';
this.helpText = 'Rectangle tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.RECTANGLE;
};
pskl.utils.inherit(ns.Rectangle, ns.ShapeTool);

View File

@ -9,6 +9,7 @@
ns.SimplePen = function() {
this.toolId = 'tool-pen';
this.helpText = 'Pen tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.PEN;
this.previousCol = null;
this.previousRow = null;

View File

@ -9,6 +9,7 @@
ns.Stroke = function() {
this.toolId = 'tool-stroke';
this.helpText = 'Stroke tool';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.STROKE;
// Stroke's first point coordinates (set in applyToolAt)
this.startCol = null;

View File

@ -6,6 +6,7 @@
this.toolId = 'tool-vertical-mirror-pen';
this.helpText = 'Vertical Mirror pen';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.MIRROR_PEN;
this.tooltipDescriptors = [
{key : 'ctrl', description : 'Use horizontal axis'},

View File

@ -8,6 +8,7 @@
ns.AbstractDragSelect = function () {
ns.BaseSelect.call(this);
this.hasSelection = false;
};

View File

@ -7,10 +7,11 @@
var ns = $.namespace('pskl.tools.drawing.selection');
ns.LassoSelect = function() {
ns.AbstractDragSelect.call(this);
this.toolId = 'tool-lasso-select';
this.helpText = 'Lasso selection';
ns.AbstractDragSelect.call(this);
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.LASSO_SELECT;
};
pskl.utils.inherit(ns.LassoSelect, ns.AbstractDragSelect);

View File

@ -7,10 +7,12 @@
var ns = $.namespace('pskl.tools.drawing.selection');
ns.RectangleSelect = function() {
ns.AbstractDragSelect.call(this);
this.toolId = 'tool-rectangle-select';
this.helpText = 'Rectangle selection';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.RECTANGLE_SELECT;
ns.AbstractDragSelect.call(this);
};
pskl.utils.inherit(ns.RectangleSelect, ns.AbstractDragSelect);

View File

@ -7,11 +7,11 @@
var ns = $.namespace('pskl.tools.drawing.selection');
ns.ShapeSelect = function() {
this.toolId = 'tool-shape-select';
this.helpText = 'Shape selection';
ns.BaseSelect.call(this);
this.toolId = 'tool-shape-select';
this.helpText = 'Shape selection';
this.shortcut = pskl.service.keyboard.Shortcuts.TOOL.SHAPE_SELECT;
};
pskl.utils.inherit(ns.ShapeSelect, ns.BaseSelect);

View File

@ -5,7 +5,7 @@
pskl.utils.inherit(ns.AbstractTransformTool, pskl.tools.Tool);
ns.AbstractTransformTool.prototype.apply = function (evt) {
ns.AbstractTransformTool.prototype.applyTransformation = function (evt) {
var allFrames = evt.shiftKey;
var allLayers = evt.ctrlKey;

View File

@ -26,14 +26,29 @@
return false;
},
getParentWithData : function (node, data) {
getParentWithData : function (node, dataName) {
while (node) {
if (node.dataset && typeof node.dataset[data] !== 'undefined') {
if (node.dataset && typeof node.dataset[dataName] !== 'undefined') {
return node;
}
node = node.parentNode;
}
return null;
},
getData : function (node, dataName) {
var parent = ns.Dom.getParentWithData(node, dataName);
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);
}
}
};
})();

View File

@ -5,7 +5,7 @@
ns.TooltipFormatter.format = function(helpText, shortcut, descriptors) {
var tpl = pskl.utils.Template.get('tooltip-container-template');
shortcut = shortcut ? '(' + shortcut + ')' : '';
shortcut = shortcut ? '(' + shortcut.getDisplayKey() + ')' : '';
return pskl.utils.Template.replace(tpl, {
helptext : helpText,
shortcut : shortcut,

View File

@ -91,6 +91,10 @@
* @private
*/
checkKeyValidity_ : function(key) {
if (key.indexOf(pskl.service.keyboard.Shortcut.USER_SETTINGS_PREFIX) === 0) {
return true;
}
var isValidKey = key in this.KEY_TO_DEFAULT_VALUE_MAP_;
if (!isValidKey) {
console.error('UserSettings key <' + key + '> not found in supported keys.');

View File

@ -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",
@ -128,6 +128,7 @@
"js/controller/dialogs/CreatePaletteController.js",
"js/controller/dialogs/ImportImageController.js",
"js/controller/dialogs/BrowseLocalController.js",
"js/controller/dialogs/CheatsheetController.js",
// Dialogs controller
"js/controller/dialogs/DialogsController.js",
@ -157,9 +158,11 @@
"js/service/palette/reader/PaletteTxtReader.js",
"js/service/palette/PaletteImportService.js",
"js/service/SavedStatusService.js",
"js/service/keyboard/ShortcutService.js",
"js/service/keyboard/KeycodeTranslator.js",
"js/service/keyboard/CheatsheetService.js",
"js/service/keyboard/KeyUtils.js",
"js/service/keyboard/Shortcut.js",
"js/service/keyboard/Shortcuts.js",
"js/service/keyboard/ShortcutService.js",
"js/service/ImageUploadService.js",
"js/service/CurrentColorsService.js",
"js/service/FileDropperService.js",

View File

@ -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",
@ -14,12 +15,12 @@
"css/settings-save.css",
"css/tools.css",
"css/icons.css",
"css/cheatsheet.css",
"css/color-picker-slider.css",
"css/dialogs.css",
"css/dialogs-import-image.css",
"css/dialogs-browse-local.css",
"css/dialogs-cheatsheet.css",
"css/dialogs-create-palette.css",
"css/dialogs-import-image.css",
"css/notifications.css",
"css/toolbox.css",
"css/toolbox-layers-list.css",

View File

@ -1,28 +0,0 @@
<div id="cheatsheet-wrapper" style="display:none">
<div class="cheatsheet-container">
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Tool shortcuts</h3>
<ul class="cheatsheet-tool-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Misc shortcuts</h3>
<ul class="cheatsheet-misc-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Selection shortcuts</h3>
<ul class="cheatsheet-selection-shortcuts"></ul>
<h3 class="cheatsheet-title">Color shortcuts</h3>
<ul class="cheatsheet-colors-shortcuts"></ul>
</div>
</div>
</div>
<span
class="cheatsheet-link"
rel="tooltip" data-placement="right" title="Keyboard shortcuts">&nbsp;</span>
<script type="text/template" id="cheatsheet-shortcut-template">
<li class="cheatsheet-shortcut">
<div class="cheatsheet-icon {{shortcutIcon}}"></div>
<span class="cheatsheet-key">{{shortcutKey}}</span>
<span class="cheatsheet-description">{{shortcutDescription}}</span>
</li>
</script>

View File

@ -3,7 +3,7 @@
Browse Local Piskels
<span class="dialog-close">X</span>
</h3>
<div style="padding:10px 20px; font-size:1.5em">
<div class="dialog-content" style="padding:10px 20px; font-size:1.5em; overflow: auto;">
<table class="local-piskel-list">
<thead>
<tr class="local-piskel-list-head">

View File

@ -0,0 +1,46 @@
<div id="cheatsheetContainer" class="dialog-wrapper">
<h3 class="dialog-head">
<span class="dialog-title">Keyboard shortcuts</span>
<span class="dialog-close">X</span>
</h3>
<div class="cheatsheet-container dialog-content">
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Tool shortcuts</h3>
<ul class="cheatsheet-tool-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Misc shortcuts</h3>
<ul class="cheatsheet-misc-shortcuts"></ul>
</div>
<div class="cheatsheet-boxes">
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Selection shortcuts</h3>
<ul class="cheatsheet-selection-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Color shortcuts</h3>
<ul class="cheatsheet-color-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3 class="cheatsheet-title">Storage shortcuts</h3>
<ul class="cheatsheet-storage-shortcuts"></ul>
</div>
</div>
</div>
<div class="cheatsheet-actions">
<span class="cheatsheet-helptext" rel="tooltip" data-placement="top" title="!!!Set in CheatsheetController!!!"><b>Customize shortcuts ?</b></span>
<button type="button" name="cheatsheet-restore-defaults" data-action="restore-defaults" class="button cheatsheet-button cheatsheet-restore-defaults">Restore default shortcuts</button>
</div>
<!-- Event trap to capture keyboard remaps -->
<div style="position:relative; overflow:hidden; width:1px; height:1px;">
<input type="text" id="cheatsheetEventTrap" style="position:absolute; top:-1000px;" />
</div>
</div>
<script type="text/template" id="cheatsheet-shortcut-template">
<li class="cheatsheet-shortcut {{className}}" data-shortcut-id="{{id}}">
<div class="cheatsheet-icon {{icon}}"></div>
<span class="cheatsheet-key" rel="tooltip" data-placement="top" title="{{title}}">{{key}}</span>
<span class="cheatsheet-description">{{description}}</span>
</li>
</script>

View File

@ -17,7 +17,7 @@ describe("SelectionManager suite", function() {
* @Mock
*/
pskl.app.shortcutService = {
addShortcut : function () {}
registerShortcut : function () {}
};
/**
@ -40,7 +40,7 @@ describe("SelectionManager suite", function() {
selectionManager.init();
selection = new pskl.selection.BaseSelection();
selection.pixels = [];
});
@ -161,7 +161,7 @@ describe("SelectionManager suite", function() {
[R, B, T],
[T, R, B]
]);
selection.move(-1, 0);
console.log('[SelectionManager] ... paste out of bounds');

View File

@ -28,7 +28,8 @@ describe("History Service suite", function() {
}
};
var mockShortcutService = {
addShortcut : function () {}
registerShortcuts : function () {},
registerShortcut : function () {}
};
return new pskl.service.HistoryService(mockPiskelController, mockShortcutService);
};

View File

@ -0,0 +1,275 @@
describe("ShortcutService test suite", function() {
var A_KEY = 'A';
var B_KEY = 'B';
var A_KEYCODE = 65;
var B_KEYCODE = 66;
var service;
beforeEach(function() {
service = new pskl.service.keyboard.ShortcutService();
});
var createEvent = function (keycode) {
return {
which : keycode,
altKey : false,
withAltKey : function () {
this.altKey = true;
return this;
},
ctrlKey : false,
withCtrlKey : function () {
this.ctrlKey = true;
return this;
},
shiftKey : false,
withShiftKey : function () {
this.shiftKey = true;
return this;
},
preventDefaultCalled : false,
preventDefault : function () {
this.preventDefaultCalled = true;
},
target : {
nodeName : 'div'
},
setNodeName : function (nodeName) {
this.target.nodeName = nodeName;
return this;
}
};
};
var setTargetName = function (evt, targetName) {
evt.target = {
nodeName : targetName
};
};
it("accepts only shortcut instances", function() {
console.log('[ShortcutService] accepts only shortcut instances');
console.log('[ShortcutService] ... fails for missing shortcut');
expect(function () {
service.registerShortcut();
}).toThrow('Invalid shortcut argument, please use instances of pskl.service.keyboard.Shortcut');
console.log('[ShortcutService] ... fails for shortcut as empty object');
expect(function () {
service.registerShortcut({});
}).toThrow('Invalid shortcut argument, please use instances of pskl.service.keyboard.Shortcut');
console.log('[ShortcutService] ... fails for shortcut as a string');
expect(function () {
service.registerShortcut('alt+F4');
}).toThrow('Invalid shortcut argument, please use instances of pskl.service.keyboard.Shortcut');
var shortcut = new pskl.service.keyboard.Shortcut('shortcut-id', '', A_KEY);
console.log('[ShortcutService] ... fails for missing callback');
expect(function () {
service.registerShortcut(shortcut);
}).toThrow('Invalid callback argument, please provide a function');
console.log('[ShortcutService] ... fails for invalid callback');
expect(function () {
service.registerShortcut(shortcut, {callback : function () {}});
}).toThrow('Invalid callback argument, please provide a function');
console.log('[ShortcutService] ... is ok for valid arguments');
service.registerShortcut(shortcut, function () {});
});
it ("triggers shortcut", function () {
console.log('[ShortcutService] triggers shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... register shortcut for A');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
service.registerShortcut(shortcutA, function () {
callbackCalled = true;
});
console.log('[ShortcutService] ... verify shortcut is called');
service.onKeyDown_(createEvent(A_KEYCODE));
expect(callbackCalled).toBe(true);
});
it ("triggers shortcuts independently", function () {
console.log('[ShortcutService] registers shortcuts');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
var shortcutB = new pskl.service.keyboard.Shortcut('shortcut-b', '', B_KEY);
var shortcutA_B = new pskl.service.keyboard.Shortcut('shortcut-a&b', '', [A_KEY, B_KEY]);
var counters = {
a : 0,
b : 0,
a_b : 0
};
console.log('[ShortcutService] ... register separate shortcuts for A and B');
service.registerShortcut(shortcutA, function () {
counters.a++;
});
service.registerShortcut(shortcutB, function () {
counters.b++;
});
service.registerShortcut(shortcutA_B, function () {
counters.a_b++;
});
console.log('[ShortcutService] ... trigger A, expect counter A at 1, B at 0, A_B at 1');
service.onKeyDown_(createEvent(A_KEYCODE));
expect(counters.a).toBe(1);
expect(counters.b).toBe(0);
expect(counters.a_b).toBe(1);
console.log('[ShortcutService] ... trigger A, expect counter A at 1, B at 1, A_B at 2');
service.onKeyDown_(createEvent(B_KEYCODE));
expect(counters.a).toBe(1);
expect(counters.b).toBe(1);
expect(counters.a_b).toBe(2);
});
it ("unregisters shortcut", function () {
console.log('[ShortcutService] unregisters shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... register shortcut for A');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
service.registerShortcut(shortcutA, function () {
callbackCalled = true;
});
console.log('[ShortcutService] ... unregister shortcut A');
service.unregisterShortcut(shortcutA);
console.log('[ShortcutService] ... verify shortcut callback is not called');
service.onKeyDown_(createEvent(A_KEYCODE));
expect(callbackCalled).toBe(false);
});
it ("unregisters shortcut without removing other shortcuts", function () {
console.log('[ShortcutService] unregisters shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... register shortcut for A & B');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
var shortcutB = new pskl.service.keyboard.Shortcut('shortcut-b', '', B_KEY);
service.registerShortcut(shortcutA, function () {});
service.registerShortcut(shortcutB, function () {
callbackCalled = true;
});
console.log('[ShortcutService] ... unregister shortcut A');
service.unregisterShortcut(shortcutA);
console.log('[ShortcutService] ... verify shortcut callback for B can still be called');
service.onKeyDown_(createEvent(B_KEYCODE));
expect(callbackCalled).toBe(true);
});
it ("supports unregistering unknown shortcuts", function () {
console.log('[ShortcutService] unregisters shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... register shortcut for A');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
service.registerShortcut(shortcutA, function () {
callbackCalled = true;
});
console.log('[ShortcutService] ... unregister shortcut B, which was not registered in the first place');
var shortcutB = new pskl.service.keyboard.Shortcut('shortcut-b', '', B_KEY);
service.unregisterShortcut(shortcutB);
console.log('[ShortcutService] ... verify shortcut callback for A can still be called');
callbackCalled = false;
service.onKeyDown_(createEvent(A_KEYCODE));
expect(callbackCalled).toBe(true);
});
it ("does not trigger shortcuts from INPUT or TEXTAREA", function () {
console.log('[ShortcutService] triggers shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... register shortcut for A');
var shortcutA = new pskl.service.keyboard.Shortcut('shortcut-a', '', A_KEY);
service.registerShortcut(shortcutA, function () {
callbackCalled = true;
});
console.log('[ShortcutService] ... verify shortcut is not called from event on INPUT');
service.onKeyDown_(createEvent(A_KEYCODE).setNodeName('INPUT'));
expect(callbackCalled).toBe(false);
console.log('[ShortcutService] ... verify shortcut is not called from event on TEXTAREA');
service.onKeyDown_(createEvent(A_KEYCODE).setNodeName('TEXTAREA'));
expect(callbackCalled).toBe(false);
console.log('[ShortcutService] ... verify shortcut is called from event on LINK');
service.onKeyDown_(createEvent(A_KEYCODE).setNodeName('A'));
expect(callbackCalled).toBe(true);
});
it ("supports meta modifiers", function () {
console.log('[ShortcutService] triggers shortcut');
var callbackCalled = false;
console.log('[ShortcutService] ... create various A shortcuts with modifiers');
var shortcuts = [
new pskl.service.keyboard.Shortcut('a', '', A_KEY),
new pskl.service.keyboard.Shortcut('a_ctrl', '', 'ctrl+' + A_KEY),
new pskl.service.keyboard.Shortcut('a_ctrl_shift', '', 'ctrl+shift+' + A_KEY),
new pskl.service.keyboard.Shortcut('a_ctrl_shift_alt', '', 'ctrl+shift+alt+' + A_KEY),
new pskl.service.keyboard.Shortcut('a_alt', '', 'alt+' + A_KEY)
];
var counters = {
a : 0,
a_ctrl : 0,
a_ctrl_shift : 0,
a_ctrl_shift_alt : 0,
a_alt : 0,
};
shortcuts.forEach(function (shortcut) {
service.registerShortcut(shortcut, function () {
counters[shortcut.getId()]++;
});
});
var verifyCounters = function (a, a_c, a_cs, a_csa, a_a) {
expect(counters.a).toBe(a);
expect(counters.a_ctrl).toBe(a_c);
expect(counters.a_ctrl_shift).toBe(a_cs);
expect(counters.a_ctrl_shift_alt).toBe(a_csa);
expect(counters.a_alt).toBe(a_a);
};
console.log('[ShortcutService] ... trigger A, expect counters CTRL+A, CTRL+SHIFT+A, CTRL+SHIFT+ALT+A, ALT+A to remain at 0');
service.onKeyDown_(createEvent(A_KEYCODE));
verifyCounters(1,0,0,0,0);
console.log('[ShortcutService] ... trigger CTRL+A, expect counters CTRL+SHIFT+A, CTRL+SHIFT+ALT+A, ALT+A to remain at 0');
service.onKeyDown_(createEvent(A_KEYCODE).withCtrlKey());
verifyCounters(1,1,0,0,0);
console.log('[ShortcutService] ... trigger CTRL+A, expect counters CTRL+SHIFT+ALT+A, ALT+A to remain at 0');
service.onKeyDown_(createEvent(A_KEYCODE).withCtrlKey().withShiftKey());
verifyCounters(1,1,1,0,0);
console.log('[ShortcutService] ... trigger CTRL+A, expect counter ALT+A to remain at 0');
service.onKeyDown_(createEvent(A_KEYCODE).withCtrlKey().withShiftKey().withAltKey());
verifyCounters(1,1,1,1,0);
console.log('[ShortcutService] ... trigger CTRL+A, expect all counters at 1');
service.onKeyDown_(createEvent(A_KEYCODE).withAltKey());
verifyCounters(1,1,1,1,1);
});
});

View File

@ -16,7 +16,7 @@ describe("Storage Service test suite", function() {
save : function () {}
};
pskl.app.shortcutService = {
addShortcut : function () {}
registerShortcut : function () {}
};
storageService = new pskl.service.storage.StorageService();