Merge pull request #143 from juliandescottes/add-keyboard-shortcuts

Add keyboard shortcuts
This commit is contained in:
grosbouddha 2013-11-24 06:23:47 -08:00
commit 46b267c2a4
18 changed files with 522 additions and 208 deletions

77
css/cheatsheet.css Normal file
View file

@ -0,0 +1,77 @@
#cheatsheet-wrapper {
position: absolute;
z-index: 10000;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: 50px;
box-sizing: border-box;
color: white;
}
.cheatsheet-container {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 20px 10%;
border-radius: 3px;
background: rgba(0,0,0,0.9);
overflow: auto;
}
.cheatsheet-container h3 {
font-size:24px;
margin-top: 0;
}
.cheatsheet-section {
float: left;
width : 50%;
}
.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;
text-align: center;
font-family:Courier;
font-weight: bold;
font-size : 18px;
color: gold;
}

View file

@ -12,6 +12,7 @@
<link rel="stylesheet" type="text/css" href="css/forms.css"> <link rel="stylesheet" type="text/css" href="css/forms.css">
<link rel="stylesheet" type="text/css" href="css/settings.css"> <link rel="stylesheet" type="text/css" href="css/settings.css">
<link rel="stylesheet" type="text/css" href="css/tools.css"> <link rel="stylesheet" type="text/css" href="css/tools.css">
<link rel="stylesheet" type="text/css" href="css/cheatsheet.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-tooltip-custom.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-tooltip-custom.css">
<link rel="stylesheet" type="text/css" href="css/preview-film-section.css"> <link rel="stylesheet" type="text/css" href="css/preview-film-section.css">
@ -54,9 +55,8 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<iframe src="templates/cheatsheet.html" onload="iframeloader.onLoad(event)" data-iframe-loader="display"></iframe>
<script type="text/javascript" src="piskel-boot.js"></script> <script type="text/javascript" src="piskel-boot.js"></script>
</body> </body>
</html> </html>

View file

@ -3,10 +3,8 @@ var Events = {
TOOL_SELECTED : "TOOL_SELECTED", TOOL_SELECTED : "TOOL_SELECTED",
TOOL_RELEASED : "TOOL_RELEASED", TOOL_RELEASED : "TOOL_RELEASED",
PRIMARY_COLOR_SELECTED: "PRIMARY_COLOR_SELECTED", SELECT_PRIMARY_COLOR: "SELECT_PRIMARY_COLOR",
PRIMARY_COLOR_UPDATED: "PRIMARY_COLOR_UPDATED", SELECT_SECONDARY_COLOR: "SELECT_SECONDARY_COLOR",
SECONDARY_COLOR_SELECTED: "SECONDARY_COLOR_SELECTED",
SECONDARY_COLOR_UPDATED: "SECONDARY_COLOR_UPDATED",
/** /**
* When this event is emitted, a request is sent to the localstorage * When this event is emitted, a request is sent to the localstorage
@ -15,22 +13,6 @@ var Events = {
*/ */
LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST", LOCALSTORAGE_REQUEST: "LOCALSTORAGE_REQUEST",
CANVAS_RIGHT_CLICKED: "CANVAS_RIGHT_CLICKED",
/**
* Event to request a refresh of the display.
* A bit overkill but, it's just workaround in our current drawing system.
* TODO: Remove or rework when redraw system is refactored.
*/
REFRESH: "REFRESH",
/**
* Temporary event to bind the redraw of right preview film to the canvas.
* This redraw should be driven by model updates.
* TODO(vincz): Remove.
*/
REDRAW_PREVIEWFILM: "REDRAW_PREVIEWFILM",
/** /**
* Fired each time a user setting change. * Fired each time a user setting change.
* The payload will be: * The payload will be:
@ -39,7 +21,6 @@ var Events = {
*/ */
USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED", USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED",
/* Listened to by SettingsController */
CLOSE_SETTINGS_DRAWER : "CLOSE_SETTINGS_DRAWER", CLOSE_SETTINGS_DRAWER : "CLOSE_SETTINGS_DRAWER",
/** /**
@ -50,8 +31,6 @@ var Events = {
FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED", FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED",
CURRENT_FRAME_SET: "CURRENT_FRAME_SET",
SELECTION_CREATED: "SELECTION_CREATED", SELECTION_CREATED: "SELECTION_CREATED",
SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST", SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST",
SELECTION_DISMISSED: "SELECTION_DISMISSED", SELECTION_DISMISSED: "SELECTION_DISMISSED",
@ -59,9 +38,6 @@ var Events = {
SHOW_NOTIFICATION: "SHOW_NOTIFICATION", SHOW_NOTIFICATION: "SHOW_NOTIFICATION",
HIDE_NOTIFICATION: "HIDE_NOTIFICATION", HIDE_NOTIFICATION: "HIDE_NOTIFICATION",
UNDO: "UNDO", // Events triggered by keyboard
REDO: "REDO", SELECT_TOOL : "SELECT_TOOL"
CUT: "CUT",
COPY: "COPY",
PASTE: "PASTE"
}; };

View file

@ -10,6 +10,9 @@
ns.app = { ns.app = {
init : function () { init : function () {
this.shortcutService = new pskl.service.keyboard.ShortcutService();
this.shortcutService.init();
var size = this.readSizeFromURL_(); var size = this.readSizeFromURL_();
var piskel = new pskl.model.Piskel(size.width, size.height); var piskel = new pskl.model.Piskel(size.width, size.height);
@ -20,8 +23,12 @@
piskel.addLayer(layer); piskel.addLayer(layer);
this.piskelController = new pskl.controller.PiskelController(piskel); this.piskelController = new pskl.controller.PiskelController(piskel);
this.piskelController.init();
this.drawingController = new pskl.controller.DrawingController(this.piskelController, $('#drawing-canvas-container')); this.paletteController = new pskl.controller.PaletteController();
this.paletteController.init();
this.drawingController = new pskl.controller.DrawingController(this.piskelController, this.paletteController, $('#drawing-canvas-container'));
this.drawingController.init(); this.drawingController.init();
this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container')); this.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container'));
@ -39,15 +46,15 @@
this.settingsController = new pskl.controller.settings.SettingsController(this.piskelController); this.settingsController = new pskl.controller.settings.SettingsController(this.piskelController);
this.settingsController.init(); this.settingsController.init();
this.toolController = new pskl.controller.ToolController();
this.toolController.init();
this.selectionManager = new pskl.selection.SelectionManager(this.piskelController); this.selectionManager = new pskl.selection.SelectionManager(this.piskelController);
this.selectionManager.init(); this.selectionManager.init();
this.historyService = new pskl.service.HistoryService(this.piskelController); this.historyService = new pskl.service.HistoryService(this.piskelController);
this.historyService.init(); this.historyService.init();
this.keyboardEventService = new pskl.service.KeyboardEventService();
this.keyboardEventService.init();
this.notificationController = new pskl.controller.NotificationController(); this.notificationController = new pskl.controller.NotificationController();
this.notificationController.init(); this.notificationController.init();
@ -57,11 +64,10 @@
this.imageUploadService = new pskl.service.ImageUploadService(); this.imageUploadService = new pskl.service.ImageUploadService();
this.imageUploadService.init(); this.imageUploadService.init();
this.toolController = new pskl.controller.ToolController();
this.toolController.init();
this.paletteController = new pskl.controller.PaletteController(); this.cheatsheetService = new pskl.service.keyboard.CheatsheetService();
this.paletteController.init(); this.cheatsheetService.init();
var drawingLoop = new pskl.rendering.DrawingLoop(); var drawingLoop = new pskl.rendering.DrawingLoop();
drawingLoop.addCallback(this.render, this); drawingLoop.addCallback(this.render, this);

View file

@ -1,11 +1,13 @@
(function () { (function () {
var ns = $.namespace("pskl.controller"); var ns = $.namespace("pskl.controller");
ns.DrawingController = function (piskelController, container) { ns.DrawingController = function (piskelController, paletteController,container) {
/** /**
* @public * @public
*/ */
this.piskelController = piskelController; this.piskelController = piskelController;
this.paletteController = paletteController;
/** /**
* @public * @public
*/ */
@ -41,34 +43,16 @@
this.isRightClicked = false; this.isRightClicked = false;
this.previousMousemoveTime = 0; this.previousMousemoveTime = 0;
this.currentToolBehavior = null; this.currentToolBehavior = null;
this.primaryColor = Constants.DEFAULT_PEN_COLOR;
this.secondaryColor = Constants.TRANSPARENT_COLOR;
}; };
ns.DrawingController.prototype.init = function () { ns.DrawingController.prototype.init = function () {
this.initMouseBehavior(); this.initMouseBehavior();
$.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) { $.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) {
console.log("Tool selected: ", toolBehavior);
this.currentToolBehavior = toolBehavior; this.currentToolBehavior = toolBehavior;
this.overlayFrame.clear(); this.overlayFrame.clear();
}, this)); }, this));
/**
* TODO(grosbouddha): Primary/secondary color state are kept in this general controller.
* Find a better place to store that. Perhaps PaletteController?
*/
$.subscribe(Events.PRIMARY_COLOR_SELECTED, $.proxy(function(evt, color) {
console.log("Primary color selected: ", color);
this.primaryColor = color;
$.publish(Events.PRIMARY_COLOR_UPDATED, [color]);
}, this));
$.subscribe(Events.SECONDARY_COLOR_SELECTED, $.proxy(function(evt, color) {
console.log("Secondary color selected: ", color);
this.secondaryColor = color;
$.publish(Events.SECONDARY_COLOR_UPDATED, [color]);
}, this));
$(window).resize($.proxy(this.startResizeTimer_, this)); $(window).resize($.proxy(this.startResizeTimer_, this));
$.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this));
@ -127,7 +111,6 @@
if(event.button == 2) { // right click if(event.button == 2) { // right click
this.isRightClicked = true; this.isRightClicked = true;
$.publish(Events.CANVAS_RIGHT_CLICKED);
} }
var coords = this.renderer.getCoordinates(event.clientX, event.clientY); var coords = this.renderer.getCoordinates(event.clientX, event.clientY);
@ -249,9 +232,9 @@
*/ */
ns.DrawingController.prototype.getCurrentColor_ = function () { ns.DrawingController.prototype.getCurrentColor_ = function () {
if(this.isRightClicked) { if(this.isRightClicked) {
return this.secondaryColor; return this.paletteController.getSecondaryColor();
} else { } else {
return this.primaryColor; return this.paletteController.getPrimaryColor();
} }
}; };

View file

@ -1,7 +1,10 @@
(function () { (function () {
var ns = $.namespace("pskl.controller"); var ns = $.namespace("pskl.controller");
ns.PaletteController = function () {}; ns.PaletteController = function () {
this.primaryColor = Constants.DEFAULT_PEN_COLOR;
this.secondaryColor = Constants.TRANSPARENT_COLOR;
};
/** /**
* @public * @public
@ -10,22 +13,20 @@
var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]"); var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]");
transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this)); transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this));
$.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) { $.subscribe(Events.SELECT_PRIMARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:true}));
this.updateColorPicker_(color, $('#color-picker')); $.subscribe(Events.SELECT_SECONDARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:false}));
}, this));
$.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) { pskl.app.shortcutService.addShortcut('X', this.swapColors.bind(this));
this.updateColorPicker_(color, $('#secondary-color-picker')); pskl.app.shortcutService.addShortcut('D', this.resetColors.bind(this));
}, this));
// Initialize colorpickers: // Initialize colorpickers:
var colorPicker = $('#color-picker'); var colorPicker = $('#color-picker');
colorPicker.val(Constants.DEFAULT_PEN_COLOR); colorPicker.val(this.primaryColor);
colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this)); colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this));
var secondaryColorPicker = $('#secondary-color-picker'); var secondaryColorPicker = $('#secondary-color-picker');
secondaryColorPicker.val(Constants.TRANSPARENT_COLOR); secondaryColorPicker.val(this.secondaryColor);
secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this)); secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this));
window.jscolor.install(); window.jscolor.install();
@ -37,12 +38,53 @@
ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) { ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) {
var inputPicker = $(evt.target); var inputPicker = $(evt.target);
if(evt.data.isPrimary) { if(evt.data.isPrimary) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]); this.setPrimaryColor(inputPicker.val());
} else { } else {
$.publish(Events.SECONDARY_COLOR_SELECTED, [inputPicker.val()]); this.setSecondaryColor(inputPicker.val());
} }
}; };
/**
* @private
*/
ns.PaletteController.prototype.onColorSelected_ = function(args, evt, color) {
var inputPicker = $(evt.target);
if(args.isPrimary) {
this.setPrimaryColor(color);
} else {
this.setSecondaryColor(color);
}
};
ns.PaletteController.prototype.setPrimaryColor = function (color) {
this.primaryColor = color;
this.updateColorPicker_(color, $('#color-picker'));
};
ns.PaletteController.prototype.setSecondaryColor = function (color) {
this.secondaryColor = color;
this.updateColorPicker_(color, $('#secondary-color-picker'));
};
ns.PaletteController.prototype.getPrimaryColor = function () {
return this.primaryColor;
};
ns.PaletteController.prototype.getSecondaryColor = function () {
return this.secondaryColor;
};
ns.PaletteController.prototype.swapColors = function () {
var primaryColor = this.getPrimaryColor();
this.setPrimaryColor(this.getSecondaryColor());
this.setSecondaryColor(primaryColor);
};
ns.PaletteController.prototype.resetColors = function () {
this.setPrimaryColor(Constants.DEFAULT_PEN_COLOR);
this.setSecondaryColor(Constants.TRANSPARENT_COLOR);
};
/** /**
* @private * @private
*/ */

View file

@ -15,6 +15,13 @@
this.currentFrameIndex = 0; this.currentFrameIndex = 0;
this.layerIdCounter = 1; this.layerIdCounter = 1;
};
ns.PiskelController.prototype.init = function () {
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));
$.publish(Events.PISKEL_RESET); $.publish(Events.PISKEL_RESET);
$.publish(Events.FRAME_SIZE_CHANGED); $.publish(Events.FRAME_SIZE_CHANGED);
@ -63,15 +70,21 @@
return !!this.getCurrentLayer().getFrameAt(index); return !!this.getCurrentLayer().getFrameAt(index);
}; };
// backward from framesheet ns.PiskelController.prototype.addFrame = function () {
ns.PiskelController.prototype.getFrameByIndex = this.addFrameAt(this.getFrameCount());
ns.PiskelController.prototype.getMergedFrameAt; };
ns.PiskelController.prototype.addEmptyFrame = function () { ns.PiskelController.prototype.addFrameAtCurrentIndex = function () {
this.addFrameAt(this.currentFrameIndex + 1);
};
ns.PiskelController.prototype.addFrameAt = function (index) {
var layers = this.getLayers(); var layers = this.getLayers();
layers.forEach(function (l) { layers.forEach(function (l) {
l.addFrame(this.createEmptyFrame_()); l.addFrameAt(this.createEmptyFrame_(), index);
}.bind(this)); }.bind(this));
$.publish(Events.PISKEL_RESET);
}; };
ns.PiskelController.prototype.createEmptyFrame_ = function () { ns.PiskelController.prototype.createEmptyFrame_ = function () {
@ -92,11 +105,17 @@
$.publish(Events.PISKEL_RESET); $.publish(Events.PISKEL_RESET);
}; };
ns.PiskelController.prototype.duplicateCurrentFrame = function () {
this.duplicateFrameAt(this.currentFrameIndex);
};
ns.PiskelController.prototype.duplicateFrameAt = function (index) { ns.PiskelController.prototype.duplicateFrameAt = function (index) {
var layers = this.getLayers(); var layers = this.getLayers();
layers.forEach(function (l) { layers.forEach(function (l) {
l.duplicateFrameAt(index); l.duplicateFrameAt(index);
}); });
$.publish(Events.PISKEL_RESET);
}; };
ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
@ -116,6 +135,20 @@
$.publish(Events.PISKEL_RESET); $.publish(Events.PISKEL_RESET);
}; };
ns.PiskelController.prototype.selectNextFrame = function () {
var nextIndex = this.currentFrameIndex + 1;
if (nextIndex < this.getFrameCount()) {
this.setCurrentFrameIndex(nextIndex);
}
};
ns.PiskelController.prototype.selectPreviousFrame = function () {
var nextIndex = this.currentFrameIndex - 1;
if (nextIndex >= 0) {
this.setCurrentFrameIndex(nextIndex);
}
};
ns.PiskelController.prototype.setCurrentLayerIndex = function (index) { ns.PiskelController.prototype.setCurrentLayerIndex = function (index) {
this.currentLayerIndex = index; this.currentLayerIndex = index;
$.publish(Events.PISKEL_RESET); $.publish(Events.PISKEL_RESET);

View file

@ -19,7 +19,7 @@
}; };
ns.PreviewFilmController.prototype.addFrame = function () { ns.PreviewFilmController.prototype.addFrame = function () {
this.piskelController.addEmptyFrame(); this.piskelController.addFrame();
this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1); this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1);
this.updateScrollerOverflows(); this.updateScrollerOverflows();
}; };

View file

@ -3,34 +3,38 @@
ns.ToolController = function () { ns.ToolController = function () {
var toDescriptor = function (id, shortcut, instance) {
this.toolInstances = { return {id:id, shortcut:shortcut, instance:instance};
"simplePen" : new pskl.drawingtools.SimplePen(),
"verticalMirrorPen" : new pskl.drawingtools.VerticalMirrorPen(),
"eraser" : new pskl.drawingtools.Eraser(),
"paintBucket" : new pskl.drawingtools.PaintBucket(),
"stroke" : new pskl.drawingtools.Stroke(),
"rectangle" : new pskl.drawingtools.Rectangle(),
"circle" : new pskl.drawingtools.Circle(),
"move" : new pskl.drawingtools.Move(),
"rectangleSelect" : new pskl.drawingtools.RectangleSelect(),
"shapeSelect" : new pskl.drawingtools.ShapeSelect(),
"colorPicker" : new pskl.drawingtools.ColorPicker()
}; };
this.currentSelectedTool = this.toolInstances.simplePen; this.tools = [
this.previousSelectedTool = this.toolInstances.simplePen; toDescriptor('simplePen', 'P', new pskl.drawingtools.SimplePen()),
toDescriptor('verticalMirrorPen', 'V', new pskl.drawingtools.VerticalMirrorPen()),
toDescriptor('eraser', 'E', new pskl.drawingtools.Eraser()),
toDescriptor('paintBucket', 'B', new pskl.drawingtools.PaintBucket()),
toDescriptor('stroke', 'L', new pskl.drawingtools.Stroke()),
toDescriptor('rectangle', 'R', new pskl.drawingtools.Rectangle()),
toDescriptor('circle', 'C', new pskl.drawingtools.Circle()),
toDescriptor('move', 'M', new pskl.drawingtools.Move()),
toDescriptor('rectangleSelect', 'S', new pskl.drawingtools.RectangleSelect()),
toDescriptor('shapeSelect', 'Z', new pskl.drawingtools.ShapeSelect()),
toDescriptor('colorPicker', 'O', new pskl.drawingtools.ColorPicker())
];
this.currentSelectedTool = this.tools[0];
this.previousSelectedTool = this.tools[0];
}; };
/** /**
* @public * @public
*/ */
ns.ToolController.prototype.init = function() { ns.ToolController.prototype.init = function() {
this.createToolMarkup_(); this.createToolsDom_();
this.addKeyboardShortcuts_();
// Initialize tool: // Initialize tool:
// Set SimplePen as default selected tool: // Set SimplePen as default selected tool:
this.selectTool_(this.toolInstances.simplePen); this.selectTool_(this.tools[0]);
// Activate listener on tool panel: // Activate listener on tool panel:
$("#tool-section").click($.proxy(this.onToolIconClicked_, this)); $("#tool-section").click($.proxy(this.onToolIconClicked_, this));
}; };
@ -44,18 +48,24 @@
if(previousSelectedToolClass) { if(previousSelectedToolClass) {
stage.removeClass(previousSelectedToolClass); stage.removeClass(previousSelectedToolClass);
} }
stage.addClass(tool.toolId); stage.addClass(tool.instance.toolId);
stage.data("selected-tool-class", tool.toolId); stage.data("selected-tool-class", tool.instance.toolId);
}; };
/** /**
* @private * @private
*/ */
ns.ToolController.prototype.selectTool_ = function(tool) { ns.ToolController.prototype.selectTool_ = function(tool) {
console.log("Selecting Tool:" , this.currentSelectedTool);
this.currentSelectedTool = tool; this.currentSelectedTool = tool;
this.activateToolOnStage_(this.currentSelectedTool); this.activateToolOnStage_(this.currentSelectedTool);
$.publish(Events.TOOL_SELECTED, [tool]);
var selectedToolElement = $('#tool-section .tool-icon.selected');
var toolElement = $('[data-tool-id=' + tool.instance.toolId + ']');
selectedToolElement.removeClass('selected');
toolElement.addClass('selected');
$.publish(Events.TOOL_SELECTED, [tool.instance]);
}; };
/** /**
@ -70,18 +80,24 @@
var tool = this.getToolById_(toolId); var tool = this.getToolById_(toolId);
if (tool) { if (tool) {
this.selectTool_(tool); this.selectTool_(tool);
}
}
};
// Show tool as selected: ns.ToolController.prototype.onKeyboardShortcut_ = function(charkey) {
$('#tool-section .tool-icon.selected').removeClass('selected'); for (var i = 0 ; i < this.tools.length ; i++) {
clickedTool.addClass('selected'); var tool = this.tools[i];
if (tool.shortcut.toLowerCase() === charkey.toLowerCase()) {
this.selectTool_(tool);
} }
} }
}; };
ns.ToolController.prototype.getToolById_ = function (toolId) { ns.ToolController.prototype.getToolById_ = function (toolId) {
for(var key in this.toolInstances) { for(var i = 0 ; i < this.tools.length ; i++) {
if (this.toolInstances[key].toolId == toolId) { var tool = this.tools[i];
return this.toolInstances[key]; if (tool.instance.toolId == toolId) {
return tool;
} }
} }
return null; return null;
@ -90,18 +106,32 @@
/** /**
* @private * @private
*/ */
ns.ToolController.prototype.createToolMarkup_ = function() { ns.ToolController.prototype.createToolsDom_ = function() {
var currentTool, toolMarkup = '', extraClass; var toolMarkup = '';
// TODO(vincz): Tools rendering order is not enforced by the data stucture (this.toolInstances), fix that. for(var i = 0 ; i < this.tools.length ; i++) {
for (var toolKey in this.toolInstances) { toolMarkup += this.getToolMarkup_(this.tools[i]);
currentTool = this.toolInstances[toolKey];
extraClass = currentTool.toolId;
if (this.currentSelectedTool == currentTool) {
extraClass = extraClass + " selected";
}
toolMarkup += '<li rel="tooltip" data-placement="right" class="tool-icon ' + extraClass + '" data-tool-id="' + currentTool.toolId +
'" title="' + currentTool.helpText + '"></li>';
} }
$('#tools-container').html(toolMarkup); $('#tools-container').html(toolMarkup);
}; };
/**
* @private
*/
ns.ToolController.prototype.getToolMarkup_ = function(tool) {
var instance = tool.instance;
var classList = ['tool-icon', instance.toolId];
if (this.currentSelectedTool == tool) {
classList.push('selected');
}
return '<li rel="tooltip" data-placement="right" class="' + classList.join(' ') + '" data-tool-id="' + instance.toolId +
'" title="' + instance.helpText + '"></li>';
};
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));
}
};
})(); })();

View file

@ -20,9 +20,9 @@
if (frame.containsPixel(col, row)) { if (frame.containsPixel(col, row)) {
var sampledColor = frame.getPixel(col, row); var sampledColor = frame.getPixel(col, row);
if (context.button == Constants.LEFT_BUTTON) { if (context.button == Constants.LEFT_BUTTON) {
$.publish(Events.PRIMARY_COLOR_SELECTED, [sampledColor]); $.publish(Events.SELECT_PRIMARY_COLOR, [sampledColor]);
} else if (context.button == Constants.RIGHT_BUTTON) { } else if (context.button == Constants.RIGHT_BUTTON) {
$.publish(Events.SECONDARY_COLOR_SELECTED, [sampledColor]); $.publish(Events.SELECT_SECONDARY_COLOR, [sampledColor]);
} }
} }
}; };

View file

@ -13,9 +13,9 @@
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this)); $.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this));
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this)); $.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this));
$.subscribe(Events.PASTE, $.proxy(this.onPaste_, this)); pskl.app.shortcutService.addShortcut('ctrl+V', this.paste.bind(this));
$.subscribe(Events.COPY, $.proxy(this.onCopy_, this)); pskl.app.shortcutService.addShortcut('ctrl+X', this.cut.bind(this));
$.subscribe(Events.CUT, $.proxy(this.onCut_, this)); pskl.app.shortcutService.addShortcut('ctrl+C', this.copy.bind(this));
$.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this)); $.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this));
}; };
@ -46,10 +46,7 @@
this.cleanSelection_(); this.cleanSelection_();
}; };
/** ns.SelectionManager.prototype.cut = function() {
* @private
*/
ns.SelectionManager.prototype.onCut_ = function(evt) {
if(this.currentSelection) { if(this.currentSelection) {
// Put cut target into the selection: // Put cut target into the selection:
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame()); this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
@ -59,9 +56,8 @@
for(var i=0, l=pixels.length; i<l; i++) { for(var i=0, l=pixels.length; i<l; i++) {
try { try {
currentFrame.setPixel(pixels[i].col, pixels[i].row, Constants.TRANSPARENT_COLOR); currentFrame.setPixel(pixels[i].col, pixels[i].row, Constants.TRANSPARENT_COLOR);
} } catch(e) {
catch(e) { // Catching out of frame's bound pixels without testing
// Catchng out of frame's bound pixels without testing
} }
} }
} }
@ -70,7 +66,7 @@
} }
}; };
ns.SelectionManager.prototype.onPaste_ = function(evt) { ns.SelectionManager.prototype.paste = function() {
if(this.currentSelection && this.currentSelection.hasPastedContent) { if(this.currentSelection && this.currentSelection.hasPastedContent) {
var pixels = this.currentSelection.pixels; var pixels = this.currentSelection.pixels;
var currentFrame = this.piskelController.getCurrentFrame(); var currentFrame = this.piskelController.getCurrentFrame();
@ -79,22 +75,17 @@
currentFrame.setPixel( currentFrame.setPixel(
pixels[i].col, pixels[i].row, pixels[i].col, pixels[i].row,
pixels[i].copiedColor); pixels[i].copiedColor);
} } catch(e) {
catch(e) { // Catching out of frame's bound pixels without testing
// Catchng out of frame's bound pixels without testing
} }
} }
} }
}; };
/** ns.SelectionManager.prototype.copy = function() {
* @private
*/
ns.SelectionManager.prototype.onCopy_ = function(evt) {
if(this.currentSelection && this.piskelController.getCurrentFrame()) { if(this.currentSelection && this.piskelController.getCurrentFrame()) {
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame()); this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
} } else {
else {
throw "Bad state for CUT callback in SelectionManager"; throw "Bad state for CUT callback in SelectionManager";
} }
}; };

View file

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

View file

@ -1,64 +0,0 @@
(function () {
var ns = $.namespace("pskl.service");
ns.KeyboardEventService = function () {};
/**
* @private
*/
ns.KeyboardEventService.prototype.KeyboardActions_ = {
"ctrl" : {
"z" : Events.UNDO,
"y" : Events.REDO,
"x" : Events.CUT,
"c" : Events.COPY,
"v" : Events.PASTE
}
};
/**
* @private
*/
ns.KeyboardEventService.prototype.CharCodeToKeyCodeMap_ = {
90 : "z",
89 : "y",
88 : "x",
67 : "c",
86 : "v"
};
/**
* @private
*/
ns.KeyboardEventService.prototype.onKeyUp_ = function(evt) {
var isMac = false;
if (navigator.appVersion.indexOf("Mac")!=-1) {
// Welcome in mac world where vowels are consons and meta used instead of ctrl:
isMac = true;
}
if (isMac ? evt.metaKey : evt.ctrlKey) {
// Get key pressed:
var letter = this.CharCodeToKeyCodeMap_[evt.which];
if(letter) {
var eventToTrigger = this.KeyboardActions_.ctrl[letter];
if(eventToTrigger) {
$.publish(eventToTrigger);
evt.preventDefault();
return false;
}
}
}
};
/**
* @public
*/
ns.KeyboardEventService.prototype.init = function() {
$(document.body).keydown($.proxy(this.onKeyUp_, this));
};
})();

View file

@ -0,0 +1,104 @@
(function () {
var ns = $.namespace('pskl.service.keyboard');
ns.CheatsheetService = function () {
this.isDisplayed_ = false;
};
ns.CheatsheetService.prototype.init = function () {
this.cheatsheetEl_ = document.getElementById('cheatsheet-wrapper');
if (!this.cheatsheetEl_) {
throw 'cheatsheetEl_ DOM element could not be retrieved';
}
this.initMarkup_();
pskl.app.shortcutService.addShortcut('shift+?', this.toggleCheatsheet_.bind(this));
pskl.app.shortcutService.addShortcut('?', this.toggleCheatsheet_.bind(this));
$.subscribe(Events.TOGGLE_HELP, this.toggleCheatsheet_.bind(this));
$.subscribe(Events.ESCAPE, this.onEscape_.bind(this));
};
ns.CheatsheetService.prototype.toggleCheatsheet_ = function () {
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_();
};
ns.CheatsheetService.prototype.initMarkupForTools_ = function () {
var shortcutTemplate = pskl.utils.Template.get('cheatsheet-shortcut-template');
var toolShortcutsContainer = $('.cheatsheet-tool-shortcuts', this.cheatsheetEl_).get(0);
var tools = pskl.app.toolController.tools;
for (var i = 0 ; i < tools.length ; i++) {
var tool = tools[i];
var shortcutEl = pskl.utils.Template.createFromHTML(
pskl.utils.Template.replace(shortcutTemplate, {
shortcutIcon : 'tool-icon ' + tool.instance.toolId,
shortcutDescription : tool.instance.helpText,
shortcutKey : tool.shortcut
})
);
toolShortcutsContainer.appendChild(shortcutEl);
}
};
ns.CheatsheetService.prototype.initMarkupForMisc_ = function () {
var shortcutTemplate = pskl.utils.Template.get('cheatsheet-shortcut-template');
var miscShortcutsContainer = $('.cheatsheet-misc-shortcuts', this.cheatsheetEl_).get(0);
var toDescriptor = function (shortcut, description) {
return {shortcut:shortcut, description:description};
};
var miscKeys = [
toDescriptor('X', 'Swap primary/secondary colors'),
toDescriptor('D', 'Reset default colors'),
toDescriptor('ctrl + X', 'Cut selection'),
toDescriptor('ctrl + C', 'Copy selection'),
toDescriptor('ctrl + V', 'Paste selection'),
toDescriptor('ctrl + Z', 'Undo'),
toDescriptor('ctrl + Y', 'Redo'),
toDescriptor('&#65514;', 'Select previous frame'), /* ASCII for up-arrow */
toDescriptor('&#65516;', 'Select next frame'), /* ASCII for down-arrow */
toDescriptor('N', 'Create new frame'),
toDescriptor('shift + N', 'Duplicate selected frame'),
toDescriptor('shift + ?', 'Open/Close this popup')
];
for (var i = 0 ; i < miscKeys.length ; i++) {
var key = miscKeys[i];
var shortcutEl = pskl.utils.Template.createFromHTML(
pskl.utils.Template.replace(shortcutTemplate, {
shortcutIcon : '',
shortcutDescription : key.description,
shortcutKey : key.shortcut
})
);
miscShortcutsContainer.appendChild(shortcutEl);
}
};
})();

View file

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

View file

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

View file

@ -74,7 +74,9 @@ exports.scripts = [
// Services // Services
"js/service/LocalStorageService.js", "js/service/LocalStorageService.js",
"js/service/HistoryService.js", "js/service/HistoryService.js",
"js/service/KeyboardEventService.js", "js/service/keyboard/ShortcutService.js",
"js/service/keyboard/KeycodeTranslator.js",
"js/service/keyboard/CheatsheetService.js",
"js/service/ImageUploadService.js", "js/service/ImageUploadService.js",
// Tools // Tools

19
templates/cheatsheet.html Normal file
View file

@ -0,0 +1,19 @@
<div id="cheatsheet-wrapper" style="display:none">
<div class="cheatsheet-container">
<div class="cheatsheet-section">
<h3>Tool shortcuts</h3>
<ul class="cheatsheet-tool-shortcuts"></ul>
</div>
<div class="cheatsheet-section">
<h3>Misc shortcuts</h3>
<ul class="cheatsheet-misc-shortcuts"></ul>
</div>
</div>
</div>
<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>