From 4f54715f705090805846eaa2ca64d90ee946ff7e Mon Sep 17 00:00:00 2001 From: jdescottes Date: Fri, 8 Nov 2013 00:44:24 +0100 Subject: [PATCH 01/19] fix : reduce piskel model size - Initial implementation : working but ... - MODEL_VERSION has been bumped to 2 - The loading process is now theoretically asynchronous (loading images to read the content of the layers), but for now, the asynchronous behaviour is hidden behind a nasty hack, which is somehow similar to lazy loading. When loading the piskel, a Piskel is created synchronously, with fake empty frames, and as the images will get loaded, the fake frames will be replaced by the actual frames. I really don't like this, and the asynchronous nature of the loading should be clearly expressed - There is no backward compatible deserializer for the previous version of the model (1) - The Serializer utils is just badly designed. Serialization and deserialization should be splitted into two different classes - Saving & loading are still done in app.js and should be moved to services BUT : the size of the piskels is now pretty small. A piskel which was using 890kB previously is now using only 10kB. Although it should be noted, that after gzip there is no significant difference between this version and the existing one. The only gains we can really expect with this are : less disk space used on appengine, ability to reuse the layers' pngs directly on piskel-website (but to be honest I can't see any valid use case for this) --- js/Constants.js | 2 +- js/app.js | 4 +- js/controller/settings/GifExportController.js | 5 +- js/rendering/CanvasRenderer.js | 18 ++--- js/rendering/FramesheetRenderer.js | 43 +++++++++++ js/rendering/PiskelRenderer.js | 14 ++++ js/rendering/SpritesheetRenderer.js | 44 ------------ js/service/LocalStorageService.js | 7 +- js/utils/FrameUtils.js | 22 +++--- js/utils/LayerUtils.js | 31 ++++++++ js/utils/Serializer.js | 72 ++++++++++++++----- piskel-script-list.js | 4 +- 12 files changed, 175 insertions(+), 91 deletions(-) create mode 100644 js/rendering/FramesheetRenderer.js create mode 100644 js/rendering/PiskelRenderer.js delete mode 100644 js/rendering/SpritesheetRenderer.js create mode 100644 js/utils/LayerUtils.js diff --git a/js/Constants.js b/js/Constants.js index fd0bcdc2..f4a37de4 100644 --- a/js/Constants.js +++ b/js/Constants.js @@ -6,7 +6,7 @@ var Constants = { FPS : 12 }, - MODEL_VERSION : 1, + MODEL_VERSION : 2, MAX_HEIGHT : 128, MAX_WIDTH : 128, diff --git a/js/app.js b/js/app.js index 3262db09..3b76f30f 100644 --- a/js/app.js +++ b/js/app.js @@ -239,8 +239,8 @@ }, getFramesheetAsPng : function () { - var renderer = new pskl.rendering.SpritesheetRenderer(this.piskelController); - var framesheetCanvas = renderer.render(); + var renderer = new pskl.rendering.PiskelRenderer(this.piskelController); + var framesheetCanvas = renderer.renderAsCanvas(); return framesheetCanvas.toDataURL("image/png"); }, diff --git a/js/controller/settings/GifExportController.js b/js/controller/settings/GifExportController.js index 42e57d3d..ae0b6832 100644 --- a/js/controller/settings/GifExportController.js +++ b/js/controller/settings/GifExportController.js @@ -114,8 +114,9 @@ for (var i = 0; i < this.piskelController.getFrameCount(); i++) { var frame = this.piskelController.getFrameAt(i); - var renderer = new pskl.rendering.CanvasRenderer(frame, dpi); - gif.addFrame(renderer.render(), { + var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, dpi); + var canvas = canvasRenderer.render(); + gif.addFrame(canvas.getContext('2d'), { delay: 1000 / fps }); } diff --git a/js/rendering/CanvasRenderer.js b/js/rendering/CanvasRenderer.js index 2153f207..2e5e99af 100644 --- a/js/rendering/CanvasRenderer.js +++ b/js/rendering/CanvasRenderer.js @@ -19,24 +19,20 @@ ns.CanvasRenderer.prototype.render = function () { var canvas = this.createCanvas_(); var context = canvas.getContext('2d'); - for(var col = 0, width = this.frame.getWidth(); col < width; col++) { - for(var row = 0, height = this.frame.getHeight(); row < height; row++) { - var color = this.frame.getPixel(col, row); - this.renderPixel_(color, col, row, context); - } - } - return context; + this.frame.forEachPixel(function (color, x, y) { + this.renderPixel_(color, x, y, context); + }.bind(this)); + + return canvas; }; - ns.CanvasRenderer.prototype.renderPixel_ = function (color, col, row, context) { - + ns.CanvasRenderer.prototype.renderPixel_ = function (color, x, y, context) { if(color == Constants.TRANSPARENT_COLOR) { color = this.transparentColor_; } context.fillStyle = color; - - context.fillRect(col * this.dpi, row * this.dpi, this.dpi, this.dpi); + context.fillRect(x * this.dpi, y * this.dpi, this.dpi, this.dpi); }; ns.CanvasRenderer.prototype.createCanvas_ = function () { diff --git a/js/rendering/FramesheetRenderer.js b/js/rendering/FramesheetRenderer.js new file mode 100644 index 00000000..06eb4d99 --- /dev/null +++ b/js/rendering/FramesheetRenderer.js @@ -0,0 +1,43 @@ +(function () { + var ns = $.namespace('pskl.rendering'); + + /** + * Render an array of frames + * @param {Array.} frames + */ + ns.FramesheetRenderer = function (frames) { + if (frames.length > 0) { + this.frames = frames; + } else { + throw 'FramesheetRenderer : Invalid argument : frames is empty'; + } + }; + + ns.FramesheetRenderer.prototype.renderAsCanvas = function () { + var canvas = this.createCanvas_(); + for (var i = 0 ; i < this.frames.length ; i++) { + var frame = this.frames[i]; + this.drawFrameInCanvas_(frame, canvas, i * frame.getWidth(), 0); + } + return canvas; + }; + + ns.FramesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) { + var context = canvas.getContext('2d'); + frame.forEachPixel(function (color, x, y) { + if(color != Constants.TRANSPARENT_COLOR) { + context.fillStyle = color; + context.fillRect(x + offsetWidth, y + offsetHeight, 1, 1); + } + }); + }; + + ns.FramesheetRenderer.prototype.createCanvas_ = function () { + var sampleFrame = this.frames[0]; + var count = this.frames.length; + var width = count * sampleFrame.getWidth(); + var height = sampleFrame.getHeight(); + return pskl.CanvasUtils.createCanvas(width, height); + }; + +})(); \ No newline at end of file diff --git a/js/rendering/PiskelRenderer.js b/js/rendering/PiskelRenderer.js new file mode 100644 index 00000000..33a0f2f4 --- /dev/null +++ b/js/rendering/PiskelRenderer.js @@ -0,0 +1,14 @@ +(function () { + + var ns = $.namespace("pskl.rendering"); + + ns.PiskelRenderer = function (piskelController) { + var frames = []; + for (var i = 0 ; i < piskelController.getFrameCount() ; i++) { + frames.push(this.piskelController.getFrameAt(i)); + } + ns.FramesheetRenderer.call(this, frames); + }; + + pskl.utils.inherit(ns.PiskelRenderer, ns.FramesheetRenderer); +})(); \ No newline at end of file diff --git a/js/rendering/SpritesheetRenderer.js b/js/rendering/SpritesheetRenderer.js deleted file mode 100644 index d634da0f..00000000 --- a/js/rendering/SpritesheetRenderer.js +++ /dev/null @@ -1,44 +0,0 @@ -(function () { - - var ns = $.namespace("pskl.rendering"); - - ns.SpritesheetRenderer = function (piskelController) { - this.piskelController = piskelController; - }; - - ns.SpritesheetRenderer.prototype.render = function () { - var canvas = this.createCanvas_(); - for (var i = 0 ; i < this.piskelController.getFrameCount() ; i++) { - var frame = this.piskelController.getFrameAt(i); - this.drawFrameInCanvas_(frame, canvas, i * this.piskelController.getWidth(), 0); - } - return canvas; - }; - - /** - * TODO(juliandescottes): Mutualize with code already present in FrameRenderer - */ - ns.SpritesheetRenderer.prototype.drawFrameInCanvas_ = function (frame, canvas, offsetWidth, offsetHeight) { - var context = canvas.getContext('2d'); - for(var col = 0, width = frame.getWidth(); col < width; col++) { - for(var row = 0, height = frame.getHeight(); row < height; row++) { - var color = frame.getPixel(col, row); - if(color != Constants.TRANSPARENT_COLOR) { - context.fillStyle = color; - context.fillRect(col + offsetWidth, row + offsetHeight, 1, 1); - } - } - } - }; - - ns.SpritesheetRenderer.prototype.createCanvas_ = function () { - var frameCount = this.piskelController.getFrameCount(); - if (frameCount > 0){ - var width = frameCount * this.piskelController.getWidth(); - var height = this.piskelController.getHeight(); - return pskl.CanvasUtils.createCanvas(width, height); - } else { - throw "Cannot render empty Spritesheet"; - } - }; -})(); \ No newline at end of file diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js index 6267773f..7f15243a 100644 --- a/js/service/LocalStorageService.js +++ b/js/service/LocalStorageService.js @@ -36,7 +36,6 @@ * @private */ ns.LocalStorageService.prototype.persistToLocalStorage_ = function() { - console.log('[LocalStorage service]: Snapshot stored'); window.localStorage.snapShot = this.piskelController.serialize(); }; @@ -45,9 +44,9 @@ * @private */ ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { - - this.piskelController.deserialize(window.localStorage.snapShot); - this.piskelController.setCurrentFrameIndex(0); + var framesheet = JSON.parse(window.localStorage.snapShot); + var piskel = pskl.utils.Serializer.createPiskel(framesheet); + pskl.app.piskelController.setPiskel(piskel); }; /** diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js index ef14f935..7b55cd76 100644 --- a/js/utils/FrameUtils.js +++ b/js/utils/FrameUtils.js @@ -36,21 +36,25 @@ context.drawImage(image, 0,0,w,h,0,0,w,h); var imgData = context.getImageData(0,0,w,h).data; + return pskl.utils.FrameUtils.createFromImageData(imgData); + }, + + createFromImageData : function (imageData, width, height) { // Draw the zoomed-up pixels to a different canvas context var frame = []; - for (var x=0;x Date: Wed, 13 Nov 2013 22:57:07 +0100 Subject: [PATCH 02/19] fix : reduce piskel model size - added backward compatible implementation for v1 models --- js/utils/Serializer.js | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js index fa706038..6f04af3f 100644 --- a/js/utils/Serializer.js +++ b/js/utils/Serializer.js @@ -30,13 +30,7 @@ }); }, - deserializePiskel : function (piskelString) { - var data = JSON.parse(piskelString); - return this.createPiskel(data); - }, - /** - * Similar to deserializePiskel, but dealing directly with a parsed piskel * @param {Object} data JSON.parse of a serialized piskel * @return {pskl.model.Piskel} a piskel */ @@ -45,13 +39,13 @@ if (data.modelVersion == Constants.MODEL_VERSION) { var piskelData = data.piskel; piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); - layersToLoad = piskelData.layers; + piskelData.layers.forEach(function (serializedLayer) { var layer = pskl.utils.Serializer.deserializeLayer(serializedLayer); piskel.addLayer(layer); }); } else if (data.modelVersion == 1) { - throw 'No backward compatible adapter for modelVersion 1'; + piskel = pskl.utils.Serializer.backwardDeserializer_v1(data); } else { piskel = pskl.utils.Serializer.backwardDeserializer_(data); } @@ -91,6 +85,23 @@ return layer; }, + deserializeFrame : function (frameString) { + var framePixelGrid = JSON.parse(frameString); + return pskl.model.Frame.fromPixelGrid(framePixelGrid); + }, + + backwardDeserializer_v1 : function (data) { + var piskelData = data.piskel; + var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); + + piskelData.layers.forEach(function (serializedLayer) { + var layer = pskl.utils.Serializer.deserializeLayer_v1(serializedLayer); + piskel.addLayer(layer); + }); + + return piskel; + }, + deserializeLayer_v1 : function (layerString) { var layerData = JSON.parse(layerString); var layer = new pskl.model.Layer(layerData.name); @@ -102,11 +113,6 @@ return layer; }, - deserializeFrame : function (frameString) { - var framePixelGrid = JSON.parse(frameString); - return pskl.model.Frame.fromPixelGrid(framePixelGrid); - }, - /** * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". */ From df4978f6afc0767d58688a5c4103b46e80b90461 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Wed, 13 Nov 2013 23:39:43 +0100 Subject: [PATCH 03/19] fix : reduce piskel model size + piskel deserialization is now clearly asynchronous + added utils.Deserializer (not a singleton though, more a builder/loader) + utils.Deserializer constructor expects a callback + when all layers are loaded and piskel is ready, the callback provided by the client is called with piskel as the first argument - Deserializer doesn't fit in the utils package, which should be reserved to singletons : can move it to service as a PiskelLoaderService, and Deserializer could remain with only the purely static methods - ImportController is realying on the Deserializer to build a Piskel but it shouldn't. Find a way to mutualize the code necessary to create a Piskel from an array of pskl.model.Frame - still cleanup to do in app.js - comments to add as well --- js/app.js | 18 +++--- js/controller/settings/ImportController.js | 14 +++-- js/service/LocalStorageService.js | 6 +- js/utils/Deserializer.js | 64 ++++++++++++++++++++++ js/utils/Serializer.js | 35 ++---------- piskel-script-list.js | 1 + 6 files changed, 95 insertions(+), 43 deletions(-) create mode 100644 js/utils/Deserializer.js diff --git a/js/app.js b/js/app.js index 3b76f30f..236063b0 100644 --- a/js/app.js +++ b/js/app.js @@ -93,9 +93,11 @@ finishInitAppEngine_ : function () { if (pskl.framesheetData_ && pskl.framesheetData_.content) { - var piskel = pskl.utils.Serializer.createPiskel(pskl.framesheetData_.content); - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(pskl.framesheetData_.fps); + var deserializer = new pskl.utils.Deserializer(pskl.framesheetData_.content, function (piskel) { + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(pskl.framesheetData_.fps); + }); + deserializer.deserialize(); } }, @@ -156,10 +158,12 @@ xhr.responseType = 'text'; xhr.onload = function (e) { var res = JSON.parse(this.responseText); - var piskel = pskl.utils.Serializer.createPiskel(res.framesheet); - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(res.fps); - $.publish(Events.HIDE_NOTIFICATION); + var deserializer = new pskl.utils.Deserializer(res.framesheet, function (piskel) { + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(res.fps); + $.publish(Events.HIDE_NOTIFICATION); + }); + deserializer.deserialize(); }; xhr.onerror = function () { diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js index 4a7e73cd..3fca0487 100644 --- a/js/controller/settings/ImportController.js +++ b/js/controller/settings/ImportController.js @@ -153,11 +153,15 @@ var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); var frame = pskl.utils.FrameUtils.createFromImage(image); - var piskel = pskl.utils.Serializer.createPiskel([frame]); - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); - - this.reset_(); + // TODO : juliandescottes : here we only want to create a layer from an array of frames, and create a new piskel using this layer + // we shouldn't use the deserializer for this ... it's only working because it's falling back to the old implementation + // bad, very bad + var deserializer = new pskl.utils.Deserializer([frame], function (piskel) { + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); + this.reset_(); + }.bind(this)); + deserializer.deserialize(); } } }; diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js index 7f15243a..7f4d9ed8 100644 --- a/js/service/LocalStorageService.js +++ b/js/service/LocalStorageService.js @@ -45,8 +45,10 @@ */ ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { var framesheet = JSON.parse(window.localStorage.snapShot); - var piskel = pskl.utils.Serializer.createPiskel(framesheet); - pskl.app.piskelController.setPiskel(piskel); + var deserializer = new pskl.utils.Deserializer(framesheet, function (piskel) { + pskl.app.piskelController.setPiskel(piskel); + }); + deserializer.deserialize(); }; /** diff --git a/js/utils/Deserializer.js b/js/utils/Deserializer.js new file mode 100644 index 00000000..5a248bab --- /dev/null +++ b/js/utils/Deserializer.js @@ -0,0 +1,64 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.Deserializer = function (data, callback) { + this.layersToLoad_ = 0; + this.data_ = data; + this.callback_ = callback; + this.piskel_ = null; + }; + + ns.Deserializer.prototype.deserialize = function () { + var data = this.data_; + if (data.modelVersion == Constants.MODEL_VERSION) { + var piskelData = data.piskel; + this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height); + + this.layersToLoad_ = piskelData.layers.length; + + piskelData.layers.forEach(function (serializedLayer) { + var layer = this.deserializeLayer(serializedLayer); + this.piskel_.addLayer(layer); + }.bind(this)); + } else if (data.modelVersion == 1) { + this.callback_(pskl.utils.Serializer.backwardDeserializer_v1(data)); + } else { + this.callback_(pskl.utils.Serializer.backwardDeserializer_(data)); + } + }; + + ns.Deserializer.prototype.deserializeLayer = function (layerString) { + var layerData = JSON.parse(layerString); + var layer = new pskl.model.Layer(layerData.name); + + // 1 - create an image to load the base64PNG representing the layer + var base64PNG = layerData.base64PNG; + var image = new Image(); + + // 2 - attach the onload callback that will be triggered asynchronously + image.onload = function () { + // 5 - extract the frames from the loaded image + var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); + + // 6 - add each image to the layer + frames.forEach(function (frame) { + layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); + }); + + this.onLayerLoaded_(); + }.bind(this); + + // 3 - set the source of the image + image.src = base64PNG; + + // 4 - return a pointer to the new layer instance + return layer; + }; + + ns.Deserializer.prototype.onLayerLoaded_ = function () { + this.layersToLoad_ = this.layersToLoad_ - 1; + if (this.layersToLoad_ === 0) { + this.callback_(this.piskel_); + } + }; +})(); \ No newline at end of file diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js index 6f04af3f..d103ad77 100644 --- a/js/utils/Serializer.js +++ b/js/utils/Serializer.js @@ -30,29 +30,6 @@ }); }, - /** - * @param {Object} data JSON.parse of a serialized piskel - * @return {pskl.model.Piskel} a piskel - */ - createPiskel : function (data) { - var piskel = null; - if (data.modelVersion == Constants.MODEL_VERSION) { - var piskelData = data.piskel; - piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); - - piskelData.layers.forEach(function (serializedLayer) { - var layer = pskl.utils.Serializer.deserializeLayer(serializedLayer); - piskel.addLayer(layer); - }); - } else if (data.modelVersion == 1) { - piskel = pskl.utils.Serializer.backwardDeserializer_v1(data); - } else { - piskel = pskl.utils.Serializer.backwardDeserializer_(data); - } - - return piskel; - }, - deserializeLayer : function (layerString) { var layerData = JSON.parse(layerString); var layer = new pskl.model.Layer(layerData.name); @@ -85,11 +62,6 @@ return layer; }, - deserializeFrame : function (frameString) { - var framePixelGrid = JSON.parse(frameString); - return pskl.model.Frame.fromPixelGrid(framePixelGrid); - }, - backwardDeserializer_v1 : function (data) { var piskelData = data.piskel; var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); @@ -106,13 +78,18 @@ var layerData = JSON.parse(layerString); var layer = new pskl.model.Layer(layerData.name); layerData.frames.forEach(function (serializedFrame) { - var frame = pskl.utils.Serializer.deserializeFrame(serializedFrame); + var frame = pskl.utils.Serializer.deserializeFrame_v1(serializedFrame); layer.addFrame(frame); }); return layer; }, + deserializeFrame_v1 : function (frameString) { + var framePixelGrid = JSON.parse(frameString); + return pskl.model.Frame.fromPixelGrid(framePixelGrid); + }, + /** * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". */ diff --git a/piskel-script-list.js b/piskel-script-list.js index 80df9fa9..f4a02c4d 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -19,6 +19,7 @@ exports.scripts = [ "js/utils/LayerUtils.js", "js/utils/ImageResizer.js", "js/utils/PixelUtils.js", + "js/utils/Deserializer.js", "js/utils/Serializer.js", "js/utils/Template.js", "js/utils/UserSettings.js", From e9a2ccfd1d9c3c7e22006da2d4566a05e883ff45 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Wed, 13 Nov 2013 23:51:27 +0100 Subject: [PATCH 04/19] fix : reduce piskel model size : fix import feature --- js/utils/FrameUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js index 7b55cd76..b20d84fa 100644 --- a/js/utils/FrameUtils.js +++ b/js/utils/FrameUtils.js @@ -36,7 +36,7 @@ context.drawImage(image, 0,0,w,h,0,0,w,h); var imgData = context.getImageData(0,0,w,h).data; - return pskl.utils.FrameUtils.createFromImageData(imgData); + return pskl.utils.FrameUtils.createFromImageData(imgData, w, h); }, createFromImageData : function (imageData, width, height) { From 4a1a7b6c2b2daeded6aabc2042fadff97a472e87 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Thu, 14 Nov 2013 23:03:29 +0100 Subject: [PATCH 05/19] fix : reduce piskel model size : ImportController + ImportController is no longer relying on the deserializer to build a piskel instance + Static builders have been added to Piskel and Layer to help easily create new instances from existing elements --- diff | 179 +++++++++++++++++++++ js/controller/settings/ImportController.js | 15 +- js/model/Layer.js | 13 ++ js/model/Piskel.js | 18 +++ js/utils/Deserializer.js | 4 +- js/utils/FrameUtils.js | 10 +- js/utils/Serializer.js | 48 +----- 7 files changed, 228 insertions(+), 59 deletions(-) create mode 100644 diff diff --git a/diff b/diff new file mode 100644 index 00000000..4da927ae --- /dev/null +++ b/diff @@ -0,0 +1,179 @@ +diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js +index 3fca048..3c94eaa 100644 +--- a/js/controller/settings/ImportController.js ++++ b/js/controller/settings/ImportController.js +@@ -153,15 +153,12 @@ + var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); + var frame = pskl.utils.FrameUtils.createFromImage(image); + +- // TODO : juliandescottes : here we only want to create a layer from an array of frames, and create a new piskel using this layer +- // we shouldn't use the deserializer for this ... it's only working because it's falling back to the old implementation +- // bad, very bad +- var deserializer = new pskl.utils.Deserializer([frame], function (piskel) { +- pskl.app.piskelController.setPiskel(piskel); +- pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); +- this.reset_(); +- }.bind(this)); +- deserializer.deserialize(); ++ var layer = pskl.model.Layer.fromFrames('Layer 1', [frame]); ++ var piskel = pskl.model.Piskel.fromLayers([layer]); ++ ++ pskl.app.piskelController.setPiskel(piskel); ++ pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); ++ this.reset_(); + } + } + }; +diff --git a/js/model/Layer.js b/js/model/Layer.js +index 0132e6a..464b1f9 100644 +--- a/js/model/Layer.js ++++ b/js/model/Layer.js +@@ -10,6 +10,12 @@ + } + }; + ++ ns.Layer.fromFrames = function (name, frames) { ++ var layer = new ns.Layer(name); ++ frames.forEach(layer.addFrame.bind(layer)); ++ return layer; ++ }; ++ + ns.Layer.prototype.getName = function () { + return this.name; + }; +diff --git a/js/model/Piskel.js b/js/model/Piskel.js +index 7748408..223d618 100644 +--- a/js/model/Piskel.js ++++ b/js/model/Piskel.js +@@ -21,6 +21,18 @@ + } + }; + ++ ns.Piskel.fromLayers = function (layers) { ++ var piskel = null; ++ if (layers.length > 0 && layers[0].length() > 0) { ++ var sampleFrame = layers[0].getFrameAt(0); ++ piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight()); ++ layers.forEach(piskel.addLayer.bind(piskel)); ++ } else { ++ throw 'Piskel.fromLayers expects array of non empty pskl.model.Layer as first argument' ++ } ++ return piskel; ++ }; ++ + ns.Piskel.prototype.getLayers = function () { + return this.layers; + }; +diff --git a/js/utils/Deserializer.js b/js/utils/Deserializer.js +index 5a248ba..56f64e4 100644 +--- a/js/utils/Deserializer.js ++++ b/js/utils/Deserializer.js +@@ -41,9 +41,7 @@ + var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); + + // 6 - add each image to the layer +- frames.forEach(function (frame) { +- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); +- }); ++ frames.forEach(layer.addFrame.bind(layer)); + + this.onLayerLoaded_(); + }.bind(this); +diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js +index b20d84f..055c8ed 100644 +--- a/js/utils/FrameUtils.js ++++ b/js/utils/FrameUtils.js +@@ -41,9 +41,9 @@ + + createFromImageData : function (imageData, width, height) { + // Draw the zoomed-up pixels to a different canvas context +- var frame = []; ++ var grid = []; + for (var x = 0 ; x < width ; x++){ +- frame[x] = []; ++ grid[x] = []; + for (var y = 0 ; y < height ; y++){ + // Find the starting index in the one-dimensional image data + var i = (y * width + x)*4; +@@ -52,13 +52,13 @@ + var b = imageData[i+2]; + var a = imageData[i+3]; + if (a < 125) { +- frame[x][y] = Constants.TRANSPARENT_COLOR; ++ grid[x][y] = Constants.TRANSPARENT_COLOR; + } else { +- frame[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); ++ grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); + } + } + } +- return frame; ++ return pskl.model.Frame.fromPixelGrid(grid); + }, + + /** +diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js +index d103ad7..5841e6b 100644 +--- a/js/utils/Serializer.js ++++ b/js/utils/Serializer.js +@@ -30,38 +30,6 @@ + }); + }, + +- deserializeLayer : function (layerString) { +- var layerData = JSON.parse(layerString); +- var layer = new pskl.model.Layer(layerData.name); +- // TODO : nasty trick to keep the whole loading process lazily synchronous +- // 1 - adding a fake frame so that the rendering can start +- layer.addFrame(new pskl.model.Frame(32,32)); +- +- // 2 - create an image to load the base64PNG representing the layer +- var base64PNG = layerData.base64PNG; +- var image = new Image(); +- +- // 3 - attach the onload callback that will be triggered asynchronously +- image.onload = function () { +- // 6 - remove the fake frame +- layer.removeFrameAt(0); +- +- // 7 - extract the frames from the loaded image +- var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); +- +- // 8 - add each image to the layer +- frames.forEach(function (frame) { +- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); +- }); +- }; +- +- // 4 - set the source of the image +- image.src = base64PNG; +- +- // 5 - return a pointer to the new layer instance, which at this point contains a fake frame +- return layer; +- }, +- + backwardDeserializer_v1 : function (data) { + var piskelData = data.piskel; + var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); +@@ -93,16 +61,12 @@ + /** + * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". + */ +- backwardDeserializer_ : function (frames) { +- var layer = new pskl.model.Layer('Layer 1'); +- frames.forEach(function (frame) { +- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); ++ backwardDeserializer_ : function (pixelGrids) { ++ var frames = pixelGrids.map(function (grid) { ++ return pskl.model.Frame.fromPixelGrid(grid); + }); +- var width = layer.getFrameAt(0).getWidth(), height = layer.getFrameAt(0).getHeight(); +- var piskel = new pskl.model.Piskel(width, height); +- piskel.addLayer(layer); +- +- return piskel; ++ var layer = pskl.model.Layer.fromFrames('Layer 1', frames); ++ return pskl.model.Piskel.fromLayers([layer]); + } + }; + })(); diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js index 3fca0487..3c94eaa5 100644 --- a/js/controller/settings/ImportController.js +++ b/js/controller/settings/ImportController.js @@ -153,15 +153,12 @@ var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); var frame = pskl.utils.FrameUtils.createFromImage(image); - // TODO : juliandescottes : here we only want to create a layer from an array of frames, and create a new piskel using this layer - // we shouldn't use the deserializer for this ... it's only working because it's falling back to the old implementation - // bad, very bad - var deserializer = new pskl.utils.Deserializer([frame], function (piskel) { - pskl.app.piskelController.setPiskel(piskel); - pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); - this.reset_(); - }.bind(this)); - deserializer.deserialize(); + var layer = pskl.model.Layer.fromFrames('Layer 1', [frame]); + var piskel = pskl.model.Piskel.fromLayers([layer]); + + pskl.app.piskelController.setPiskel(piskel); + pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); + this.reset_(); } } }; diff --git a/js/model/Layer.js b/js/model/Layer.js index 0132e6a6..3725622b 100644 --- a/js/model/Layer.js +++ b/js/model/Layer.js @@ -10,6 +10,19 @@ } }; + /** + * Create a Layer instance from an already existing set a Frames + * @static + * @param {String} name layer's name + * @param {Array} frames should all have the same dimensions + * @return {pskl.model.Layer} + */ + ns.Layer.fromFrames = function (name, frames) { + var layer = new ns.Layer(name); + frames.forEach(layer.addFrame.bind(layer)); + return layer; + }; + ns.Layer.prototype.getName = function () { return this.name; }; diff --git a/js/model/Piskel.js b/js/model/Piskel.js index 7748408f..77e7f261 100644 --- a/js/model/Piskel.js +++ b/js/model/Piskel.js @@ -21,6 +21,24 @@ } }; + /** + * Create a piskel instance from an existing set of (non empty) layers + * Layers should all be synchronized : same number of frames, same dimensions + * @param {Array} layers + * @return {pskl.model.Piskel} + */ + ns.Piskel.fromLayers = function (layers) { + var piskel = null; + if (layers.length > 0 && layers[0].length() > 0) { + var sampleFrame = layers[0].getFrameAt(0); + piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight()); + layers.forEach(piskel.addLayer.bind(piskel)); + } else { + throw 'Piskel.fromLayers expects array of non empty pskl.model.Layer as first argument'; + } + return piskel; + }; + ns.Piskel.prototype.getLayers = function () { return this.layers; }; diff --git a/js/utils/Deserializer.js b/js/utils/Deserializer.js index 5a248bab..56f64e4f 100644 --- a/js/utils/Deserializer.js +++ b/js/utils/Deserializer.js @@ -41,9 +41,7 @@ var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); // 6 - add each image to the layer - frames.forEach(function (frame) { - layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); - }); + frames.forEach(layer.addFrame.bind(layer)); this.onLayerLoaded_(); }.bind(this); diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js index b20d84fa..055c8edd 100644 --- a/js/utils/FrameUtils.js +++ b/js/utils/FrameUtils.js @@ -41,9 +41,9 @@ createFromImageData : function (imageData, width, height) { // Draw the zoomed-up pixels to a different canvas context - var frame = []; + var grid = []; for (var x = 0 ; x < width ; x++){ - frame[x] = []; + grid[x] = []; for (var y = 0 ; y < height ; y++){ // Find the starting index in the one-dimensional image data var i = (y * width + x)*4; @@ -52,13 +52,13 @@ var b = imageData[i+2]; var a = imageData[i+3]; if (a < 125) { - frame[x][y] = Constants.TRANSPARENT_COLOR; + grid[x][y] = Constants.TRANSPARENT_COLOR; } else { - frame[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); + grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); } } } - return frame; + return pskl.model.Frame.fromPixelGrid(grid); }, /** diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js index d103ad77..cab86bc4 100644 --- a/js/utils/Serializer.js +++ b/js/utils/Serializer.js @@ -30,38 +30,6 @@ }); }, - deserializeLayer : function (layerString) { - var layerData = JSON.parse(layerString); - var layer = new pskl.model.Layer(layerData.name); - // TODO : nasty trick to keep the whole loading process lazily synchronous - // 1 - adding a fake frame so that the rendering can start - layer.addFrame(new pskl.model.Frame(32,32)); - - // 2 - create an image to load the base64PNG representing the layer - var base64PNG = layerData.base64PNG; - var image = new Image(); - - // 3 - attach the onload callback that will be triggered asynchronously - image.onload = function () { - // 6 - remove the fake frame - layer.removeFrameAt(0); - - // 7 - extract the frames from the loaded image - var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); - - // 8 - add each image to the layer - frames.forEach(function (frame) { - layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); - }); - }; - - // 4 - set the source of the image - image.src = base64PNG; - - // 5 - return a pointer to the new layer instance, which at this point contains a fake frame - return layer; - }, - backwardDeserializer_v1 : function (data) { var piskelData = data.piskel; var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); @@ -91,18 +59,14 @@ }, /** - * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". + * Deserialize old piskel framesheets. Initially piskels were stored as arrays of pixelGrids : "[[pixelGrid],[pixelGrid],[pixelGrid]]". */ - backwardDeserializer_ : function (frames) { - var layer = new pskl.model.Layer('Layer 1'); - frames.forEach(function (frame) { - layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); + backwardDeserializer_ : function (pixelGrids) { + var frames = pixelGrids.map(function (grid) { + return pskl.model.Frame.fromPixelGrid(grid); }); - var width = layer.getFrameAt(0).getWidth(), height = layer.getFrameAt(0).getHeight(); - var piskel = new pskl.model.Piskel(width, height); - piskel.addLayer(layer); - - return piskel; + var layer = pskl.model.Layer.fromFrames('Layer 1', frames); + return pskl.model.Piskel.fromLayers([layer]); } }; })(); From 86cd1cdeaadc52b034bb95ba474c813f2ca174dd Mon Sep 17 00:00:00 2001 From: jdescottes Date: Fri, 15 Nov 2013 00:03:05 +0100 Subject: [PATCH 06/19] fix : reduce piskel model size + moved Serializer and Deserializer to utils.serialization package + put all backward code in utils.serialization.backward + added static method on Deserializer to make its usage similar to other utils in the package - still not happy with the names used in Deserializer classes (deserializer.deserialize ...) --- diff | 179 ------------------ js/app.js | 6 +- js/service/LocalStorageService.js | 3 +- js/utils/Serializer.js | 72 ------- js/utils/{ => serialization}/Deserializer.js | 34 ++-- js/utils/serialization/Serializer.js | 31 +++ .../serialization/backward/Deserializer_v0.js | 18 ++ .../serialization/backward/Deserializer_v1.js | 36 ++++ piskel-script-list.js | 6 +- 9 files changed, 112 insertions(+), 273 deletions(-) delete mode 100644 diff delete mode 100644 js/utils/Serializer.js rename js/utils/{ => serialization}/Deserializer.js (66%) create mode 100644 js/utils/serialization/Serializer.js create mode 100644 js/utils/serialization/backward/Deserializer_v0.js create mode 100644 js/utils/serialization/backward/Deserializer_v1.js diff --git a/diff b/diff deleted file mode 100644 index 4da927ae..00000000 --- a/diff +++ /dev/null @@ -1,179 +0,0 @@ -diff --git a/js/controller/settings/ImportController.js b/js/controller/settings/ImportController.js -index 3fca048..3c94eaa 100644 ---- a/js/controller/settings/ImportController.js -+++ b/js/controller/settings/ImportController.js -@@ -153,15 +153,12 @@ - var image = pskl.utils.ImageResizer.resize(this.importedImage_, w, h, smoothing); - var frame = pskl.utils.FrameUtils.createFromImage(image); - -- // TODO : juliandescottes : here we only want to create a layer from an array of frames, and create a new piskel using this layer -- // we shouldn't use the deserializer for this ... it's only working because it's falling back to the old implementation -- // bad, very bad -- var deserializer = new pskl.utils.Deserializer([frame], function (piskel) { -- pskl.app.piskelController.setPiskel(piskel); -- pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); -- this.reset_(); -- }.bind(this)); -- deserializer.deserialize(); -+ var layer = pskl.model.Layer.fromFrames('Layer 1', [frame]); -+ var piskel = pskl.model.Piskel.fromLayers([layer]); -+ -+ pskl.app.piskelController.setPiskel(piskel); -+ pskl.app.animationController.setFPS(Constants.DEFAULT.FPS); -+ this.reset_(); - } - } - }; -diff --git a/js/model/Layer.js b/js/model/Layer.js -index 0132e6a..464b1f9 100644 ---- a/js/model/Layer.js -+++ b/js/model/Layer.js -@@ -10,6 +10,12 @@ - } - }; - -+ ns.Layer.fromFrames = function (name, frames) { -+ var layer = new ns.Layer(name); -+ frames.forEach(layer.addFrame.bind(layer)); -+ return layer; -+ }; -+ - ns.Layer.prototype.getName = function () { - return this.name; - }; -diff --git a/js/model/Piskel.js b/js/model/Piskel.js -index 7748408..223d618 100644 ---- a/js/model/Piskel.js -+++ b/js/model/Piskel.js -@@ -21,6 +21,18 @@ - } - }; - -+ ns.Piskel.fromLayers = function (layers) { -+ var piskel = null; -+ if (layers.length > 0 && layers[0].length() > 0) { -+ var sampleFrame = layers[0].getFrameAt(0); -+ piskel = new pskl.model.Piskel(sampleFrame.getWidth(), sampleFrame.getHeight()); -+ layers.forEach(piskel.addLayer.bind(piskel)); -+ } else { -+ throw 'Piskel.fromLayers expects array of non empty pskl.model.Layer as first argument' -+ } -+ return piskel; -+ }; -+ - ns.Piskel.prototype.getLayers = function () { - return this.layers; - }; -diff --git a/js/utils/Deserializer.js b/js/utils/Deserializer.js -index 5a248ba..56f64e4 100644 ---- a/js/utils/Deserializer.js -+++ b/js/utils/Deserializer.js -@@ -41,9 +41,7 @@ - var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); - - // 6 - add each image to the layer -- frames.forEach(function (frame) { -- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); -- }); -+ frames.forEach(layer.addFrame.bind(layer)); - - this.onLayerLoaded_(); - }.bind(this); -diff --git a/js/utils/FrameUtils.js b/js/utils/FrameUtils.js -index b20d84f..055c8ed 100644 ---- a/js/utils/FrameUtils.js -+++ b/js/utils/FrameUtils.js -@@ -41,9 +41,9 @@ - - createFromImageData : function (imageData, width, height) { - // Draw the zoomed-up pixels to a different canvas context -- var frame = []; -+ var grid = []; - for (var x = 0 ; x < width ; x++){ -- frame[x] = []; -+ grid[x] = []; - for (var y = 0 ; y < height ; y++){ - // Find the starting index in the one-dimensional image data - var i = (y * width + x)*4; -@@ -52,13 +52,13 @@ - var b = imageData[i+2]; - var a = imageData[i+3]; - if (a < 125) { -- frame[x][y] = Constants.TRANSPARENT_COLOR; -+ grid[x][y] = Constants.TRANSPARENT_COLOR; - } else { -- frame[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); -+ grid[x][y] = pskl.utils.FrameUtils.rgbToHex(r,g,b); - } - } - } -- return frame; -+ return pskl.model.Frame.fromPixelGrid(grid); - }, - - /** -diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js -index d103ad7..5841e6b 100644 ---- a/js/utils/Serializer.js -+++ b/js/utils/Serializer.js -@@ -30,38 +30,6 @@ - }); - }, - -- deserializeLayer : function (layerString) { -- var layerData = JSON.parse(layerString); -- var layer = new pskl.model.Layer(layerData.name); -- // TODO : nasty trick to keep the whole loading process lazily synchronous -- // 1 - adding a fake frame so that the rendering can start -- layer.addFrame(new pskl.model.Frame(32,32)); -- -- // 2 - create an image to load the base64PNG representing the layer -- var base64PNG = layerData.base64PNG; -- var image = new Image(); -- -- // 3 - attach the onload callback that will be triggered asynchronously -- image.onload = function () { -- // 6 - remove the fake frame -- layer.removeFrameAt(0); -- -- // 7 - extract the frames from the loaded image -- var frames = pskl.utils.LayerUtils.createFromImage(image, layerData.frameCount); -- -- // 8 - add each image to the layer -- frames.forEach(function (frame) { -- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); -- }); -- }; -- -- // 4 - set the source of the image -- image.src = base64PNG; -- -- // 5 - return a pointer to the new layer instance, which at this point contains a fake frame -- return layer; -- }, -- - backwardDeserializer_v1 : function (data) { - var piskelData = data.piskel; - var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); -@@ -93,16 +61,12 @@ - /** - * Deserialize old piskel framesheets. Initially piskels were stored as arrays of frames : "[[pixelGrid],[pixelGrid],[pixelGrid]]". - */ -- backwardDeserializer_ : function (frames) { -- var layer = new pskl.model.Layer('Layer 1'); -- frames.forEach(function (frame) { -- layer.addFrame(pskl.model.Frame.fromPixelGrid(frame)); -+ backwardDeserializer_ : function (pixelGrids) { -+ var frames = pixelGrids.map(function (grid) { -+ return pskl.model.Frame.fromPixelGrid(grid); - }); -- var width = layer.getFrameAt(0).getWidth(), height = layer.getFrameAt(0).getHeight(); -- var piskel = new pskl.model.Piskel(width, height); -- piskel.addLayer(layer); -- -- return piskel; -+ var layer = pskl.model.Layer.fromFrames('Layer 1', frames); -+ return pskl.model.Piskel.fromLayers([layer]); - } - }; - })(); diff --git a/js/app.js b/js/app.js index 236063b0..b8a739e2 100644 --- a/js/app.js +++ b/js/app.js @@ -93,11 +93,10 @@ finishInitAppEngine_ : function () { if (pskl.framesheetData_ && pskl.framesheetData_.content) { - var deserializer = new pskl.utils.Deserializer(pskl.framesheetData_.content, function (piskel) { + pskl.utils.serialization.Deserializer.deserialize(pskl.framesheetData_.content, function (piskel) { pskl.app.piskelController.setPiskel(piskel); pskl.app.animationController.setFPS(pskl.framesheetData_.fps); }); - deserializer.deserialize(); } }, @@ -158,12 +157,11 @@ xhr.responseType = 'text'; xhr.onload = function (e) { var res = JSON.parse(this.responseText); - var deserializer = new pskl.utils.Deserializer(res.framesheet, function (piskel) { + pskl.utils.serialization.Deserializer.deserialize(res.framesheet, function (piskel) { pskl.app.piskelController.setPiskel(piskel); pskl.app.animationController.setFPS(res.fps); $.publish(Events.HIDE_NOTIFICATION); }); - deserializer.deserialize(); }; xhr.onerror = function () { diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js index 7f4d9ed8..783211c3 100644 --- a/js/service/LocalStorageService.js +++ b/js/service/LocalStorageService.js @@ -45,10 +45,9 @@ */ ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { var framesheet = JSON.parse(window.localStorage.snapShot); - var deserializer = new pskl.utils.Deserializer(framesheet, function (piskel) { + pskl.utils.serialization.Deserializer.deserialize(framesheet, function (piskel) { pskl.app.piskelController.setPiskel(piskel); }); - deserializer.deserialize(); }; /** diff --git a/js/utils/Serializer.js b/js/utils/Serializer.js deleted file mode 100644 index cab86bc4..00000000 --- a/js/utils/Serializer.js +++ /dev/null @@ -1,72 +0,0 @@ -(function () { - var ns = $.namespace('pskl.utils'); - - var layersToLoad = 0; - - ns.Serializer = { - serializePiskel : function (piskel) { - var serializedLayers = piskel.getLayers().map(function (l) { - return pskl.utils.Serializer.serializeLayer(l); - }); - return JSON.stringify({ - modelVersion : Constants.MODEL_VERSION, - piskel : { - height : piskel.getHeight(), - width : piskel.getWidth(), - layers : serializedLayers - } - }); - }, - - serializeLayer : function (layer) { - var frames = layer.getFrames(); - var renderer = new pskl.rendering.FramesheetRenderer(frames); - var base64PNG = renderer.renderAsCanvas().toDataURL(); - - return JSON.stringify({ - name : layer.getName(), - base64PNG : base64PNG, - frameCount : frames.length - }); - }, - - backwardDeserializer_v1 : function (data) { - var piskelData = data.piskel; - var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); - - piskelData.layers.forEach(function (serializedLayer) { - var layer = pskl.utils.Serializer.deserializeLayer_v1(serializedLayer); - piskel.addLayer(layer); - }); - - return piskel; - }, - - deserializeLayer_v1 : function (layerString) { - var layerData = JSON.parse(layerString); - var layer = new pskl.model.Layer(layerData.name); - layerData.frames.forEach(function (serializedFrame) { - var frame = pskl.utils.Serializer.deserializeFrame_v1(serializedFrame); - layer.addFrame(frame); - }); - - return layer; - }, - - deserializeFrame_v1 : function (frameString) { - var framePixelGrid = JSON.parse(frameString); - return pskl.model.Frame.fromPixelGrid(framePixelGrid); - }, - - /** - * Deserialize old piskel framesheets. Initially piskels were stored as arrays of pixelGrids : "[[pixelGrid],[pixelGrid],[pixelGrid]]". - */ - backwardDeserializer_ : function (pixelGrids) { - var frames = pixelGrids.map(function (grid) { - return pskl.model.Frame.fromPixelGrid(grid); - }); - var layer = pskl.model.Layer.fromFrames('Layer 1', frames); - return pskl.model.Piskel.fromLayers([layer]); - } - }; -})(); diff --git a/js/utils/Deserializer.js b/js/utils/serialization/Deserializer.js similarity index 66% rename from js/utils/Deserializer.js rename to js/utils/serialization/Deserializer.js index 56f64e4f..118538c6 100644 --- a/js/utils/Deserializer.js +++ b/js/utils/serialization/Deserializer.js @@ -1,5 +1,5 @@ (function () { - var ns = $.namespace('pskl.utils'); + var ns = $.namespace('pskl.utils.serialization'); ns.Deserializer = function (data, callback) { this.layersToLoad_ = 0; @@ -8,23 +8,29 @@ this.piskel_ = null; }; + ns.Deserializer.deserialize = function (data, callback) { + var deserializer; + if (data.modelVersion == Constants.MODEL_VERSION) { + deserializer = new ns.Deserializer(data, callback); + } else if (data.modelVersion == 1) { + deserializer = new ns.backward.Deserializer_v1(data, callback); + } else { + deserializer = new ns.backward.Deserializer_v0(data, callback); + } + deserializer.deserialize(); + }; + ns.Deserializer.prototype.deserialize = function () { var data = this.data_; - if (data.modelVersion == Constants.MODEL_VERSION) { - var piskelData = data.piskel; - this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height); + var piskelData = data.piskel; + this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height); - this.layersToLoad_ = piskelData.layers.length; + this.layersToLoad_ = piskelData.layers.length; - piskelData.layers.forEach(function (serializedLayer) { - var layer = this.deserializeLayer(serializedLayer); - this.piskel_.addLayer(layer); - }.bind(this)); - } else if (data.modelVersion == 1) { - this.callback_(pskl.utils.Serializer.backwardDeserializer_v1(data)); - } else { - this.callback_(pskl.utils.Serializer.backwardDeserializer_(data)); - } + piskelData.layers.forEach(function (serializedLayer) { + var layer = this.deserializeLayer(serializedLayer); + this.piskel_.addLayer(layer); + }.bind(this)); }; ns.Deserializer.prototype.deserializeLayer = function (layerString) { diff --git a/js/utils/serialization/Serializer.js b/js/utils/serialization/Serializer.js new file mode 100644 index 00000000..934de4b4 --- /dev/null +++ b/js/utils/serialization/Serializer.js @@ -0,0 +1,31 @@ +(function () { + var ns = $.namespace('pskl.utils'); + + ns.Serializer = { + serializePiskel : function (piskel) { + var serializedLayers = piskel.getLayers().map(function (l) { + return pskl.utils.Serializer.serializeLayer(l); + }); + return JSON.stringify({ + modelVersion : Constants.MODEL_VERSION, + piskel : { + height : piskel.getHeight(), + width : piskel.getWidth(), + layers : serializedLayers + } + }); + }, + + serializeLayer : function (layer) { + var frames = layer.getFrames(); + var renderer = new pskl.rendering.FramesheetRenderer(frames); + var base64PNG = renderer.renderAsCanvas().toDataURL(); + + return JSON.stringify({ + name : layer.getName(), + base64PNG : base64PNG, + frameCount : frames.length + }); + } + }; +})(); diff --git a/js/utils/serialization/backward/Deserializer_v0.js b/js/utils/serialization/backward/Deserializer_v0.js new file mode 100644 index 00000000..c976acb8 --- /dev/null +++ b/js/utils/serialization/backward/Deserializer_v0.js @@ -0,0 +1,18 @@ +(function () { + var ns = $.namespace('pskl.utils.serialization.backward'); + + ns.Deserializer_v0 = function (data, callback) { + this.data_ = data; + this.callback_ = callback; + }; + + ns.Deserializer_v0.prototype.deserialize = function () { + var pixelGrids = this.data_; + var frames = pixelGrids.map(function (grid) { + return pskl.model.Frame.fromPixelGrid(grid); + }); + var layer = pskl.model.Layer.fromFrames('Layer 1', frames); + + this.callback_(pskl.model.Piskel.fromLayers([layer])); + }; +})(); \ No newline at end of file diff --git a/js/utils/serialization/backward/Deserializer_v1.js b/js/utils/serialization/backward/Deserializer_v1.js new file mode 100644 index 00000000..92b05741 --- /dev/null +++ b/js/utils/serialization/backward/Deserializer_v1.js @@ -0,0 +1,36 @@ +(function () { + var ns = $.namespace('pskl.utils.serialization.backward'); + + ns.Deserializer_v1 = function (data, callback) { + this.callback_ = callback; + this.data_ = data; + }; + + ns.Deserializer_v1.prototype.deserialize = function () { + var piskelData = this.data_.piskel; + var piskel = new pskl.model.Piskel(piskelData.width, piskelData.height); + + piskelData.layers.forEach(function (serializedLayer) { + var layer = this.deserializeLayer(serializedLayer); + piskel.addLayer(layer); + }.bind(this)); + + this.callback_(piskel); + }; + + ns.Deserializer_v1.prototype.deserializeLayer = function (layerString) { + var layerData = JSON.parse(layerString); + var layer = new pskl.model.Layer(layerData.name); + layerData.frames.forEach(function (serializedFrame) { + var frame = this.deserializeFrame(serializedFrame); + layer.addFrame(frame); + }.bind(this)); + + return layer; + }; + + ns.Deserializer_v1.prototype.deserializeFrame = function (frameString) { + var framePixelGrid = JSON.parse(frameString); + return pskl.model.Frame.fromPixelGrid(framePixelGrid); + }; +})(); \ No newline at end of file diff --git a/piskel-script-list.js b/piskel-script-list.js index f4a02c4d..562de48f 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -19,10 +19,12 @@ exports.scripts = [ "js/utils/LayerUtils.js", "js/utils/ImageResizer.js", "js/utils/PixelUtils.js", - "js/utils/Deserializer.js", - "js/utils/Serializer.js", "js/utils/Template.js", "js/utils/UserSettings.js", + "js/utils/serialization/Serializer.js", + "js/utils/serialization/Deserializer.js", + "js/utils/serialization/backward/Deserializer_v0.js", + "js/utils/serialization/backward/Deserializer_v1.js", "js/lib/jsColor_1_4_0/jscolor.js", // Application libraries--> From 5d83a39cf02fde16b3c9d1e34fd64f40105ddd6c Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sun, 17 Nov 2013 20:20:27 +0100 Subject: [PATCH 07/19] feature : add keyboard shortcuts : Events cleanup + Removed unused events --- js/Events.js | 19 ------------------- js/controller/DrawingController.js | 2 -- 2 files changed, 21 deletions(-) diff --git a/js/Events.js b/js/Events.js index e7e5e7ea..88458b7e 100644 --- a/js/Events.js +++ b/js/Events.js @@ -15,22 +15,6 @@ var Events = { */ 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. * The payload will be: @@ -39,7 +23,6 @@ var Events = { */ USER_SETTINGS_CHANGED: "USER_SETTINGS_CHANGED", - /* Listened to by SettingsController */ CLOSE_SETTINGS_DRAWER : "CLOSE_SETTINGS_DRAWER", /** @@ -50,8 +33,6 @@ var Events = { FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED", - CURRENT_FRAME_SET: "CURRENT_FRAME_SET", - SELECTION_CREATED: "SELECTION_CREATED", SELECTION_MOVE_REQUEST: "SELECTION_MOVE_REQUEST", SELECTION_DISMISSED: "SELECTION_DISMISSED", diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 598b6122..00e25d9c 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -43,7 +43,6 @@ this.initMouseBehavior(); $.subscribe(Events.TOOL_SELECTED, $.proxy(function(evt, toolBehavior) { - console.log("Tool selected: ", toolBehavior); this.currentToolBehavior = toolBehavior; this.overlayFrame.clear(); }, this)); @@ -107,7 +106,6 @@ if(event.button == 2) { // right click this.isRightClicked = true; - $.publish(Events.CANVAS_RIGHT_CLICKED); } var coords = this.getSpriteCoordinates(event); From 25e647049994620df013a0ec7fdc419fed5c6426 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sun, 17 Nov 2013 21:15:53 +0100 Subject: [PATCH 08/19] feature : add keyboard shortcuts : keycodes + moved keycode translation to KeycodeTranslator + made KeycodeTranslator more generic to handle 0-9 and a-z keys + small refactor in KeyboardEventService --- js/service/KeyboardEventService.js | 93 +++++++++++------------- js/service/keyboard/KeyboardEvent.js | 17 +++++ js/service/keyboard/KeycodeTranslator.js | 19 +++++ piskel-script-list.js | 1 + 4 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 js/service/keyboard/KeyboardEvent.js create mode 100644 js/service/keyboard/KeycodeTranslator.js diff --git a/js/service/KeyboardEventService.js b/js/service/KeyboardEventService.js index 2a79d6d1..b1f96160 100644 --- a/js/service/KeyboardEventService.js +++ b/js/service/KeyboardEventService.js @@ -1,64 +1,53 @@ (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; - } + ns.KeyboardEventService = function () { + this.keyboardActions_ = { + "ctrl" : { + "z" : Events.UNDO, + "y" : Events.REDO, + "x" : Events.CUT, + "c" : Events.COPY, + "v" : Events.PASTE } - } + }; }; + /** * @public */ ns.KeyboardEventService.prototype.init = function() { $(document.body).keydown($.proxy(this.onKeyUp_, this)); }; - -})(); \ No newline at end of file + + /** + * @private + */ + ns.KeyboardEventService.prototype.onKeyUp_ = function(evt) { + var eventToTrigger; + + // jquery names FTW ... + var keycode = evt.which; + var charkey = pskl.service.keyboard.KeycodeTranslator.toChar(keycode); + + if(charkey) { + if (this.isCtrlKeyPressed_(evt)) { + eventToTrigger = this.keyboardActions_.ctrl[charkey]; + } + } + + if(eventToTrigger) { + $.publish(eventToTrigger); + evt.preventDefault(); + } + }; + + ns.KeyboardEventService.prototype.isCtrlKeyPressed_ = function (evt) { + return this.isMac_() ? evt.metaKey : evt.ctrlKey; + }; + + ns.KeyboardEventService.prototype.isMac_ = function () { + return navigator.appVersion.indexOf("Mac") != -1; + }; +})(); diff --git a/js/service/keyboard/KeyboardEvent.js b/js/service/keyboard/KeyboardEvent.js new file mode 100644 index 00000000..6110f3c0 --- /dev/null +++ b/js/service/keyboard/KeyboardEvent.js @@ -0,0 +1,17 @@ +(function () { + var ns = $.namespace('service.keyboard'); + + ns.KeyboardEvent = function (eventName, args, description) { + this.eventName = eventName; + this.args = args; + this.description = description; + }; + + ns.KeyboardEvent.prototype.fire = function () { + $.publish(this.eventName, this.args); + }; + + ns.KeyboardEvent.prototype.getDescription = function () { + return this.description; + }; +})(); diff --git a/js/service/keyboard/KeycodeTranslator.js b/js/service/keyboard/KeycodeTranslator.js new file mode 100644 index 00000000..e43df52b --- /dev/null +++ b/js/service/keyboard/KeycodeTranslator.js @@ -0,0 +1,19 @@ +(function () { + var specialKeys = {}; + + var ns = $.namespace('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, we'll use base 36 to get the string representation + return (keycode - 65 + 10).toString(36); + } else { + return specialKeys[keycode]; + } + } + }; +})(); \ No newline at end of file diff --git a/piskel-script-list.js b/piskel-script-list.js index deeb069e..81170315 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -62,6 +62,7 @@ exports.scripts = [ // Services "js/service/LocalStorageService.js", "js/service/HistoryService.js", + "js/service/keyboard/KeycodeTranslator.js", "js/service/KeyboardEventService.js", "js/service/ImageUploadService.js", From f0ef0163096aaa87f10a545a2685783b50ac480c Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sun, 17 Nov 2013 22:07:26 +0100 Subject: [PATCH 09/19] feature : add keyboard shortcuts : initial impl + added shortcuts for all tools in ToolController + modified structure of tools in Controller to Array of descriptors to allow to declare shortcuts directly when setting up the tool controller --- js/Events.js | 4 +- js/controller/ToolController.js | 77 +++++++++++++++--------- js/service/KeyboardEventService.js | 17 ++++-- js/service/keyboard/KeycodeTranslator.js | 2 +- 4 files changed, 64 insertions(+), 36 deletions(-) diff --git a/js/Events.js b/js/Events.js index 88458b7e..db854704 100644 --- a/js/Events.js +++ b/js/Events.js @@ -40,9 +40,11 @@ var Events = { SHOW_NOTIFICATION: "SHOW_NOTIFICATION", HIDE_NOTIFICATION: "HIDE_NOTIFICATION", + // Events triggered by keyboard UNDO: "UNDO", REDO: "REDO", CUT: "CUT", COPY: "COPY", - PASTE: "PASTE" + PASTE: "PASTE", + SELECT_TOOL : "SELECT_TOOL" }; \ No newline at end of file diff --git a/js/controller/ToolController.js b/js/controller/ToolController.js index 4b38b2f9..0c9d8499 100644 --- a/js/controller/ToolController.js +++ b/js/controller/ToolController.js @@ -1,25 +1,28 @@ (function () { var ns = $.namespace("pskl.controller"); - + ns.ToolController = function () { - - this.toolInstances = { - "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() + var toDescriptor = function (id, shortcut, instance) { + return {id:id, shortcut:shortcut, instance:instance}; }; - this.currentSelectedTool = this.toolInstances.simplePen; - this.previousSelectedTool = this.toolInstances.simplePen; + this.tools = [ + 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]; }; /** @@ -30,9 +33,11 @@ // Initialize tool: // Set SimplePen as default selected tool: - this.selectTool_(this.toolInstances.simplePen); + this.selectTool_(this.tools[0]); // Activate listener on tool panel: $("#tool-section").click($.proxy(this.onToolIconClicked_, this)); + + $.subscribe(Events.SELECT_TOOL, $.proxy(this.onKeyboardShortcut_, this)); }; /** @@ -44,18 +49,18 @@ if(previousSelectedToolClass) { stage.removeClass(previousSelectedToolClass); } - stage.addClass(tool.toolId); - stage.data("selected-tool-class", tool.toolId); + stage.addClass(tool.instance.toolId); + stage.data("selected-tool-class", tool.instance.toolId); }; /** * @private */ ns.ToolController.prototype.selectTool_ = function(tool) { - console.log("Selecting Tool:" , this.currentSelectedTool); + console.log("Selecting Tool:" , this.currentSelectedTool.name); this.currentSelectedTool = tool; this.activateToolOnStage_(this.currentSelectedTool); - $.publish(Events.TOOL_SELECTED, [tool]); + $.publish(Events.TOOL_SELECTED, [tool.instance]); }; /** @@ -78,10 +83,20 @@ } }; + ns.ToolController.prototype.onKeyboardShortcut_ = function(evt, 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.getToolById_ = function (toolId) { - for(var key in this.toolInstances) { - if (this.toolInstances[key].toolId == toolId) { - return this.toolInstances[key]; + for(var i = 0 ; i < this.tools.length ; i++) { + var tool = this.tools[i]; + if (tool.instance.toolId == toolId) { + return tool; } } return null; @@ -93,14 +108,16 @@ ns.ToolController.prototype.createToolMarkup_ = function() { var currentTool, toolMarkup = '', extraClass; // TODO(vincz): Tools rendering order is not enforced by the data stucture (this.toolInstances), fix that. - for (var toolKey in this.toolInstances) { - currentTool = this.toolInstances[toolKey]; - extraClass = currentTool.toolId; - if (this.currentSelectedTool == currentTool) { + for(var i = 0 ; i < this.tools.length ; i++) { + var tool = this.tools[i]; + var instance = tool.instance; + + extraClass = instance.toolId; + if (this.currentSelectedTool == tool) { extraClass = extraClass + " selected"; } - toolMarkup += '
  • '; + toolMarkup += '
  • '; } $('#tools-container').html(toolMarkup); }; diff --git a/js/service/KeyboardEventService.js b/js/service/KeyboardEventService.js index b1f96160..8e5c7479 100644 --- a/js/service/KeyboardEventService.js +++ b/js/service/KeyboardEventService.js @@ -11,6 +11,13 @@ "v" : Events.PASTE } }; + + // See ToolController + // TODO : Allow for other classes to register new shortcuts + var toolKeys = 'pveblrcmzso'.split(''); + toolKeys.forEach(function (key) { + this.keyboardActions_[key] = Events.SELECT_TOOL; + }.bind(this)); }; @@ -34,12 +41,14 @@ if(charkey) { if (this.isCtrlKeyPressed_(evt)) { eventToTrigger = this.keyboardActions_.ctrl[charkey]; + } else { + eventToTrigger = this.keyboardActions_[charkey]; } - } - if(eventToTrigger) { - $.publish(eventToTrigger); - evt.preventDefault(); + if(eventToTrigger) { + $.publish(eventToTrigger, charkey); + evt.preventDefault(); + } } }; diff --git a/js/service/keyboard/KeycodeTranslator.js b/js/service/keyboard/KeycodeTranslator.js index e43df52b..2ff2fcf8 100644 --- a/js/service/keyboard/KeycodeTranslator.js +++ b/js/service/keyboard/KeycodeTranslator.js @@ -1,7 +1,7 @@ (function () { var specialKeys = {}; - var ns = $.namespace('service.keyboard'); + var ns = $.namespace('pskl.service.keyboard'); ns.KeycodeTranslator= { toChar : function (keycode) { From c033d65cde6153402a455b9bde740f889a3db3a8 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Sun, 17 Nov 2013 22:47:21 +0100 Subject: [PATCH 10/19] fix : tool icon not selected when using shortcut --- js/controller/ToolController.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/js/controller/ToolController.js b/js/controller/ToolController.js index 0c9d8499..2b62fd4a 100644 --- a/js/controller/ToolController.js +++ b/js/controller/ToolController.js @@ -57,9 +57,15 @@ * @private */ ns.ToolController.prototype.selectTool_ = function(tool) { - console.log("Selecting Tool:" , this.currentSelectedTool.name); this.currentSelectedTool = tool; this.activateToolOnStage_(this.currentSelectedTool); + + 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]); }; @@ -75,10 +81,6 @@ var tool = this.getToolById_(toolId); if (tool) { this.selectTool_(tool); - - // Show tool as selected: - $('#tool-section .tool-icon.selected').removeClass('selected'); - clickedTool.addClass('selected'); } } }; From e0b76f5329847778b6f5be7eaea7ddab7f2a2ae1 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Mon, 18 Nov 2013 23:53:12 +0100 Subject: [PATCH 11/19] feature : add keyboard shortcuts : added help panel displayed on shift+? --- css/cheatsheet.css | 77 +++++++++++++++++++++ index.html | 4 +- js/Events.js | 4 +- js/app.js | 3 + js/controller/PaletteController.js | 13 +++- js/controller/ToolController.js | 2 +- js/service/KeyboardEventService.js | 12 +++- js/service/keyboard/CheatsheetService.js | 88 ++++++++++++++++++++++++ js/service/keyboard/KeycodeTranslator.js | 4 +- piskel-script-list.js | 1 + templates/cheatsheet.html | 19 +++++ 11 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 css/cheatsheet.css create mode 100644 js/service/keyboard/CheatsheetService.js create mode 100644 templates/cheatsheet.html diff --git a/css/cheatsheet.css b/css/cheatsheet.css new file mode 100644 index 00000000..db130301 --- /dev/null +++ b/css/cheatsheet.css @@ -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; +} \ No newline at end of file diff --git a/index.html b/index.html index 309d86f3..8b290c15 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ + @@ -53,9 +54,8 @@ - - + diff --git a/js/Events.js b/js/Events.js index db854704..f6a642f7 100644 --- a/js/Events.js +++ b/js/Events.js @@ -46,5 +46,7 @@ var Events = { CUT: "CUT", COPY: "COPY", PASTE: "PASTE", - SELECT_TOOL : "SELECT_TOOL" + SELECT_TOOL : "SELECT_TOOL", + TOGGLE_HELP : "TOGGLE_HELP", + SWAP_COLORS : "SWAP_COLORS" }; \ No newline at end of file diff --git a/js/app.js b/js/app.js index 3262db09..53f59dbd 100644 --- a/js/app.js +++ b/js/app.js @@ -57,6 +57,9 @@ this.toolController = new pskl.controller.ToolController(); this.toolController.init(); + this.cheatsheetService = new pskl.service.keyboard.CheatsheetService(); + this.cheatsheetService.init(); + this.paletteController = new pskl.controller.PaletteController(); this.paletteController.init(); diff --git a/js/controller/PaletteController.js b/js/controller/PaletteController.js index 48b2a141..58fc8278 100644 --- a/js/controller/PaletteController.js +++ b/js/controller/PaletteController.js @@ -18,6 +18,8 @@ this.updateColorPicker_(color, $('#secondary-color-picker')); }, this)); + $.subscribe(Events.SWAP_COLORS, this.onSwapColorsEvent_.bind(this)); + // Initialize colorpickers: var colorPicker = $('#color-picker'); colorPicker.val(Constants.DEFAULT_PEN_COLOR); @@ -43,6 +45,15 @@ } }; + ns.PaletteController.prototype.onSwapColorsEvent_ = function () { + // TODO : juliandescottes : oooooh huge crap ... palette controller doesn't know which colors are selected !! + // JC Denton commented : 'what a shame' + var primaryColor = pskl.app.drawingController.primaryColor; + var secondaryColor = pskl.app.drawingController.secondaryColor; + $.publish(Events.PRIMARY_COLOR_SELECTED, [secondaryColor]); + $.publish(Events.SECONDARY_COLOR_SELECTED, [primaryColor]); + }; + /** * @private */ @@ -64,7 +75,7 @@ if (color == Constants.TRANSPARENT_COLOR) { // We can set the current palette color to transparent. // You can then combine this transparent color with an advanced - // tool for customized deletions. + // tool for customized deletions. // Eg: bucket + transparent: Delete a colored area // Stroke + transparent: hollow out the equivalent of a stroke diff --git a/js/controller/ToolController.js b/js/controller/ToolController.js index 2b62fd4a..4fa8b712 100644 --- a/js/controller/ToolController.js +++ b/js/controller/ToolController.js @@ -109,7 +109,7 @@ */ ns.ToolController.prototype.createToolMarkup_ = function() { var currentTool, toolMarkup = '', extraClass; - // 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++) { var tool = this.tools[i]; var instance = tool.instance; diff --git a/js/service/KeyboardEventService.js b/js/service/KeyboardEventService.js index 8e5c7479..1a1c5587 100644 --- a/js/service/KeyboardEventService.js +++ b/js/service/KeyboardEventService.js @@ -9,7 +9,11 @@ "x" : Events.CUT, "c" : Events.COPY, "v" : Events.PASTE - } + }, + "shift" : { + "?" : Events.TOGGLE_HELP + }, + "x" : Events.SWAP_COLORS }; // See ToolController @@ -41,6 +45,8 @@ if(charkey) { if (this.isCtrlKeyPressed_(evt)) { eventToTrigger = this.keyboardActions_.ctrl[charkey]; + } else if (this.isShiftKeyPressed_(evt)) { + eventToTrigger = this.keyboardActions_.shift[charkey]; } else { eventToTrigger = this.keyboardActions_[charkey]; } @@ -56,6 +62,10 @@ return this.isMac_() ? evt.metaKey : evt.ctrlKey; }; + ns.KeyboardEventService.prototype.isShiftKeyPressed_ = function (evt) { + return evt.shiftKey; + }; + ns.KeyboardEventService.prototype.isMac_ = function () { return navigator.appVersion.indexOf("Mac") != -1; }; diff --git a/js/service/keyboard/CheatsheetService.js b/js/service/keyboard/CheatsheetService.js new file mode 100644 index 00000000..6b3b10ff --- /dev/null +++ b/js/service/keyboard/CheatsheetService.js @@ -0,0 +1,88 @@ +(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_(); + $.subscribe(Events.TOGGLE_HELP, this.toggleCheatsheet_.bind(this)); + }; + + ns.CheatsheetService.prototype.toggleCheatsheet_ = function () { + if (this.isDisplayed_) { + this.hideCheatsheet_(); + } else { + this.showCheatsheet_(); + } + }; + + ns.CheatsheetService.prototype.showCheatsheet_ = function () { + this.cheatsheetEl_.style.display = 'block'; + this.isDisplayed_ = true; + }; + + + ns.CheatsheetService.prototype.hideCheatsheet_ = function () { + this.cheatsheetEl_.style.display = 'none'; + this.isDisplayed_ = false; + }; + + ns.CheatsheetService.prototype.initMarkup_ = function () { + this.initMarkupForTools_(); + + + }; + + 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); + } + }; + + this.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('ctrl + X', 'Cut selection'), + toDescriptor('ctrl + C', 'Copy selection'), + toDescriptor('ctrl + V', 'Paste selection'), + toDescriptor('ctrl + Z', 'Undo'), + toDescriptor('ctrl + Y', 'Redo') + ]; + 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); + } + }; + +})(); \ No newline at end of file diff --git a/js/service/keyboard/KeycodeTranslator.js b/js/service/keyboard/KeycodeTranslator.js index 2ff2fcf8..cf82c249 100644 --- a/js/service/keyboard/KeycodeTranslator.js +++ b/js/service/keyboard/KeycodeTranslator.js @@ -1,5 +1,7 @@ (function () { - var specialKeys = {}; + var specialKeys = { + 191 : "?" + }; var ns = $.namespace('pskl.service.keyboard'); diff --git a/piskel-script-list.js b/piskel-script-list.js index 81170315..23580119 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -63,6 +63,7 @@ exports.scripts = [ "js/service/LocalStorageService.js", "js/service/HistoryService.js", "js/service/keyboard/KeycodeTranslator.js", + "js/service/keyboard/CheatsheetService.js", "js/service/KeyboardEventService.js", "js/service/ImageUploadService.js", diff --git a/templates/cheatsheet.html b/templates/cheatsheet.html new file mode 100644 index 00000000..cfa7dffa --- /dev/null +++ b/templates/cheatsheet.html @@ -0,0 +1,19 @@ + + \ No newline at end of file From a0273edb3e0a367345ca739bc30be8a37982b5ad Mon Sep 17 00:00:00 2001 From: jdescottes Date: Tue, 19 Nov 2013 07:40:35 +0100 Subject: [PATCH 12/19] feature : add keyboard shortcuts + cleanup of color management + colors are now stored in palette controller + drawing controller has a dependency on palette controller + UPDATE COLOR events have been removed (they were used only for synchronizing palette and drawing controller) --- js/Events.js | 6 +-- js/app.js | 7 ++-- js/controller/DrawingController.js | 25 +++--------- js/controller/PaletteController.js | 62 +++++++++++++++++++++--------- js/drawingtools/ColorPicker.js | 6 +-- 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/js/Events.js b/js/Events.js index f6a642f7..329b5221 100644 --- a/js/Events.js +++ b/js/Events.js @@ -3,10 +3,8 @@ var Events = { TOOL_SELECTED : "TOOL_SELECTED", TOOL_RELEASED : "TOOL_RELEASED", - PRIMARY_COLOR_SELECTED: "PRIMARY_COLOR_SELECTED", - PRIMARY_COLOR_UPDATED: "PRIMARY_COLOR_UPDATED", - SECONDARY_COLOR_SELECTED: "SECONDARY_COLOR_SELECTED", - SECONDARY_COLOR_UPDATED: "SECONDARY_COLOR_UPDATED", + SELECT_PRIMARY_COLOR: "SELECT_PRIMARY_COLOR", + SELECT_SECONDARY_COLOR: "SELECT_SECONDARY_COLOR", /** * When this event is emitted, a request is sent to the localstorage diff --git a/js/app.js b/js/app.js index 53f59dbd..33f34f9c 100644 --- a/js/app.js +++ b/js/app.js @@ -21,7 +21,10 @@ this.piskelController = new pskl.controller.PiskelController(piskel); - 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.animationController = new pskl.controller.AnimatedPreviewController(this.piskelController, $('#preview-canvas-container')); @@ -60,8 +63,6 @@ this.cheatsheetService = new pskl.service.keyboard.CheatsheetService(); this.cheatsheetService.init(); - this.paletteController = new pskl.controller.PaletteController(); - this.paletteController.init(); var drawingLoop = new pskl.rendering.DrawingLoop(); drawingLoop.addCallback(this.render, this); diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 00e25d9c..a7c8e1a4 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -1,11 +1,13 @@ (function () { var ns = $.namespace("pskl.controller"); - ns.DrawingController = function (piskelController, container) { + ns.DrawingController = function (piskelController, paletteController,container) { /** * @public */ this.piskelController = piskelController; + this.paletteController = paletteController; + /** * @public */ @@ -35,8 +37,6 @@ this.isRightClicked = false; this.previousMousemoveTime = 0; this.currentToolBehavior = null; - this.primaryColor = Constants.DEFAULT_PEN_COLOR; - this.secondaryColor = Constants.TRANSPARENT_COLOR; }; ns.DrawingController.prototype.init = function () { @@ -47,21 +47,6 @@ this.overlayFrame.clear(); }, 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.startDPIUpdateTimer_, this)); $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); @@ -221,9 +206,9 @@ */ ns.DrawingController.prototype.getCurrentColor_ = function () { if(this.isRightClicked) { - return this.secondaryColor; + return this.paletteController.getSecondaryColor(); } else { - return this.primaryColor; + return this.paletteController.getPrimaryColor(); } }; diff --git a/js/controller/PaletteController.js b/js/controller/PaletteController.js index 58fc8278..6c0c58ee 100644 --- a/js/controller/PaletteController.js +++ b/js/controller/PaletteController.js @@ -1,7 +1,10 @@ (function () { var ns = $.namespace("pskl.controller"); - ns.PaletteController = function () {}; + ns.PaletteController = function () { + this.primaryColor = Constants.DEFAULT_PEN_COLOR; + this.secondaryColor = Constants.TRANSPARENT_COLOR; + }; /** * @public @@ -10,24 +13,18 @@ var transparentColorPalette = $(".palette-color[data-color=TRANSPARENT]"); transparentColorPalette.mouseup($.proxy(this.onPaletteColorClick_, this)); - $.subscribe(Events.PRIMARY_COLOR_UPDATED, $.proxy(function(evt, color) { - this.updateColorPicker_(color, $('#color-picker')); - }, this)); - - $.subscribe(Events.SECONDARY_COLOR_UPDATED, $.proxy(function(evt, color) { - this.updateColorPicker_(color, $('#secondary-color-picker')); - }, this)); - + $.subscribe(Events.SELECT_PRIMARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:true})); + $.subscribe(Events.SELECT_SECONDARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:false})); $.subscribe(Events.SWAP_COLORS, this.onSwapColorsEvent_.bind(this)); // Initialize colorpickers: var colorPicker = $('#color-picker'); - colorPicker.val(Constants.DEFAULT_PEN_COLOR); + colorPicker.val(this.primaryColor); colorPicker.change({isPrimary : true}, $.proxy(this.onPickerChange_, this)); var secondaryColorPicker = $('#secondary-color-picker'); - secondaryColorPicker.val(Constants.TRANSPARENT_COLOR); + secondaryColorPicker.val(this.secondaryColor); secondaryColorPicker.change({isPrimary : false}, $.proxy(this.onPickerChange_, this)); window.jscolor.install(); @@ -39,19 +36,46 @@ ns.PaletteController.prototype.onPickerChange_ = function(evt, isPrimary) { var inputPicker = $(evt.target); if(evt.data.isPrimary) { - $.publish(Events.PRIMARY_COLOR_SELECTED, [inputPicker.val()]); + this.setPrimaryColor(inputPicker.val()); } 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.onSwapColorsEvent_ = function () { - // TODO : juliandescottes : oooooh huge crap ... palette controller doesn't know which colors are selected !! - // JC Denton commented : 'what a shame' - var primaryColor = pskl.app.drawingController.primaryColor; - var secondaryColor = pskl.app.drawingController.secondaryColor; - $.publish(Events.PRIMARY_COLOR_SELECTED, [secondaryColor]); - $.publish(Events.SECONDARY_COLOR_SELECTED, [primaryColor]); + var primaryColor = this.getPrimaryColor(); + this.setPrimaryColor(this.getSecondaryColor()); + this.setSecondaryColor(primaryColor); }; /** diff --git a/js/drawingtools/ColorPicker.js b/js/drawingtools/ColorPicker.js index 8bbedf0d..fba7d5a9 100644 --- a/js/drawingtools/ColorPicker.js +++ b/js/drawingtools/ColorPicker.js @@ -12,7 +12,7 @@ }; pskl.utils.inherit(ns.ColorPicker, ns.BaseTool); - + /** * @override */ @@ -20,9 +20,9 @@ if (frame.containsPixel(col, row)) { var sampledColor = frame.getPixel(col, row); 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) { - $.publish(Events.SECONDARY_COLOR_SELECTED, [sampledColor]); + $.publish(Events.SELECT_SECONDARY_COLOR, [sampledColor]); } } }; From 7d296f3dc381b9c9223aff8247ef030c02ffa2db Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Tue, 19 Nov 2013 08:13:16 +0100 Subject: [PATCH 13/19] Fix : add keyboard shortcuts : forgot misc keys --- js/service/keyboard/CheatsheetService.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/service/keyboard/CheatsheetService.js b/js/service/keyboard/CheatsheetService.js index 6b3b10ff..31f1a876 100644 --- a/js/service/keyboard/CheatsheetService.js +++ b/js/service/keyboard/CheatsheetService.js @@ -35,8 +35,7 @@ ns.CheatsheetService.prototype.initMarkup_ = function () { this.initMarkupForTools_(); - - + this.initMarkupForMisc_(); }; ns.CheatsheetService.prototype.initMarkupForTools_ = function () { @@ -85,4 +84,4 @@ } }; -})(); \ No newline at end of file +})(); From 9d0f41362b4326275fd5cc91879225f8dbb880f2 Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Tue, 19 Nov 2013 08:15:37 +0100 Subject: [PATCH 14/19] Fix : typo --- js/service/keyboard/CheatsheetService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/service/keyboard/CheatsheetService.js b/js/service/keyboard/CheatsheetService.js index 31f1a876..9d4a4ba2 100644 --- a/js/service/keyboard/CheatsheetService.js +++ b/js/service/keyboard/CheatsheetService.js @@ -56,7 +56,7 @@ } }; - this.initMarkupForMisc_ = function () { + ns.CheatsheetService.prototype.initMarkupForMisc_ = function () { var shortcutTemplate = pskl.utils.Template.get('cheatsheet-shortcut-template'); var miscShortcutsContainer = $('.cheatsheet-misc-shortcuts', this.cheatsheetEl_).get(0); From 6eabf01ffc50c6970f11c96d83b17faef4c1acbe Mon Sep 17 00:00:00 2001 From: jdescottes Date: Tue, 19 Nov 2013 23:46:33 +0100 Subject: [PATCH 15/19] feature : add keyboard shortcuts + decentralized shortcut declaration + each service/controller is now responsible for declaring its shorcuts - documentation (cheatsheet) is still to be maintained manually - init order matters (shortcutService has to be instanciated before everyone else) => should have a standalone KeyboardService singleton which is ready as soon as it is loaded --- js/Events.js | 9 +-- js/app.js | 12 ++-- js/controller/PaletteController.js | 11 ++- js/controller/PiskelController.js | 19 +++++ js/controller/ToolController.js | 43 ++++++----- js/selection/SelectionManager.js | 31 +++----- js/service/HistoryService.js | 5 +- js/service/KeyboardEventService.js | 72 ------------------- js/service/keyboard/CheatsheetService.js | 18 ++++- js/service/keyboard/KeyboardEvent.js | 17 ----- js/service/keyboard/KeycodeTranslator.js | 7 +- js/service/keyboard/ShortcutService.js | 90 ++++++++++++++++++++++++ piskel-script-list.js | 2 +- 13 files changed, 190 insertions(+), 146 deletions(-) delete mode 100644 js/service/KeyboardEventService.js delete mode 100644 js/service/keyboard/KeyboardEvent.js create mode 100644 js/service/keyboard/ShortcutService.js diff --git a/js/Events.js b/js/Events.js index 329b5221..28e1107f 100644 --- a/js/Events.js +++ b/js/Events.js @@ -39,12 +39,5 @@ var Events = { HIDE_NOTIFICATION: "HIDE_NOTIFICATION", // Events triggered by keyboard - UNDO: "UNDO", - REDO: "REDO", - CUT: "CUT", - COPY: "COPY", - PASTE: "PASTE", - SELECT_TOOL : "SELECT_TOOL", - TOGGLE_HELP : "TOGGLE_HELP", - SWAP_COLORS : "SWAP_COLORS" + SELECT_TOOL : "SELECT_TOOL" }; \ No newline at end of file diff --git a/js/app.js b/js/app.js index 33f34f9c..18681b8a 100644 --- a/js/app.js +++ b/js/app.js @@ -10,6 +10,9 @@ ns.app = { init : function () { + this.shortcutService = new pskl.service.keyboard.ShortcutService(); + this.shortcutService.init(); + var size = this.readSizeFromURL_(); var piskel = new pskl.model.Piskel(size.width, size.height); @@ -20,6 +23,7 @@ piskel.addLayer(layer); this.piskelController = new pskl.controller.PiskelController(piskel); + this.piskelController.init(); this.paletteController = new pskl.controller.PaletteController(); this.paletteController.init(); @@ -39,15 +43,15 @@ this.settingsController = new pskl.controller.settings.SettingsController(this.piskelController); this.settingsController.init(); + this.toolController = new pskl.controller.ToolController(); + this.toolController.init(); + this.selectionManager = new pskl.selection.SelectionManager(this.piskelController); this.selectionManager.init(); this.historyService = new pskl.service.HistoryService(this.piskelController); this.historyService.init(); - this.keyboardEventService = new pskl.service.KeyboardEventService(); - this.keyboardEventService.init(); - this.notificationController = new pskl.controller.NotificationController(); this.notificationController.init(); @@ -57,8 +61,6 @@ this.imageUploadService = new pskl.service.ImageUploadService(); this.imageUploadService.init(); - this.toolController = new pskl.controller.ToolController(); - this.toolController.init(); this.cheatsheetService = new pskl.service.keyboard.CheatsheetService(); this.cheatsheetService.init(); diff --git a/js/controller/PaletteController.js b/js/controller/PaletteController.js index 6c0c58ee..98013cd1 100644 --- a/js/controller/PaletteController.js +++ b/js/controller/PaletteController.js @@ -15,7 +15,9 @@ $.subscribe(Events.SELECT_PRIMARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:true})); $.subscribe(Events.SELECT_SECONDARY_COLOR, this.onColorSelected_.bind(this, {isPrimary:false})); - $.subscribe(Events.SWAP_COLORS, this.onSwapColorsEvent_.bind(this)); + + pskl.app.shortcutService.addShortcut('X', this.swapColors.bind(this)); + pskl.app.shortcutService.addShortcut('D', this.resetColors.bind(this)); // Initialize colorpickers: var colorPicker = $('#color-picker'); @@ -72,12 +74,17 @@ return this.secondaryColor; }; - ns.PaletteController.prototype.onSwapColorsEvent_ = function () { + 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 */ diff --git a/js/controller/PiskelController.js b/js/controller/PiskelController.js index 8d102322..c3351d58 100644 --- a/js/controller/PiskelController.js +++ b/js/controller/PiskelController.js @@ -15,6 +15,11 @@ this.currentFrameIndex = 0; 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)); $.publish(Events.PISKEL_RESET); $.publish(Events.FRAME_SIZE_CHANGED); @@ -116,6 +121,20 @@ $.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) { this.currentLayerIndex = index; $.publish(Events.PISKEL_RESET); diff --git a/js/controller/ToolController.js b/js/controller/ToolController.js index 4fa8b712..0e2d42dc 100644 --- a/js/controller/ToolController.js +++ b/js/controller/ToolController.js @@ -29,15 +29,14 @@ * @public */ ns.ToolController.prototype.init = function() { - this.createToolMarkup_(); + this.createToolsDom_(); + this.addKeyboardShortcuts_(); // Initialize tool: // Set SimplePen as default selected tool: this.selectTool_(this.tools[0]); // Activate listener on tool panel: $("#tool-section").click($.proxy(this.onToolIconClicked_, this)); - - $.subscribe(Events.SELECT_TOOL, $.proxy(this.onKeyboardShortcut_, this)); }; /** @@ -85,7 +84,7 @@ } }; - ns.ToolController.prototype.onKeyboardShortcut_ = function(evt, charkey) { + 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()) { @@ -107,20 +106,32 @@ /** * @private */ - ns.ToolController.prototype.createToolMarkup_ = function() { - var currentTool, toolMarkup = '', extraClass; - + ns.ToolController.prototype.createToolsDom_ = function() { + var toolMarkup = ''; for(var i = 0 ; i < this.tools.length ; i++) { - var tool = this.tools[i]; - var instance = tool.instance; - - extraClass = instance.toolId; - if (this.currentSelectedTool == tool) { - extraClass = extraClass + " selected"; - } - toolMarkup += '
  • '; + toolMarkup += this.getToolMarkup_(this.tools[i]); } $('#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 '
  • '; + }; + + 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)); + } + }; })(); \ No newline at end of file diff --git a/js/selection/SelectionManager.js b/js/selection/SelectionManager.js index 5f6f3790..578e64b3 100644 --- a/js/selection/SelectionManager.js +++ b/js/selection/SelectionManager.js @@ -13,9 +13,9 @@ $.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this)); $.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this)); - $.subscribe(Events.PASTE, $.proxy(this.onPaste_, this)); - $.subscribe(Events.COPY, $.proxy(this.onCopy_, this)); - $.subscribe(Events.CUT, $.proxy(this.onCut_, 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)); $.subscribe(Events.TOOL_SELECTED, $.proxy(this.onToolSelected_, this)); }; @@ -46,10 +46,7 @@ this.cleanSelection_(); }; - /** - * @private - */ - ns.SelectionManager.prototype.onCut_ = function(evt) { + ns.SelectionManager.prototype.cut = function() { if(this.currentSelection) { // Put cut target into the selection: this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame()); @@ -59,9 +56,8 @@ for(var i=0, l=pixels.length; i= 65 && keycode <= 90) { - // key is a-z, we'll use base 36 to get the string representation + // key is a-z, use base 36 to get the string representation return (keycode - 65 + 10).toString(36); } else { return specialKeys[keycode]; diff --git a/js/service/keyboard/ShortcutService.js b/js/service/keyboard/ShortcutService.js new file mode 100644 index 00000000..07419af7 --- /dev/null +++ b/js/service/keyboard/ShortcutService.js @@ -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; + }; +})(); \ No newline at end of file diff --git a/piskel-script-list.js b/piskel-script-list.js index 23580119..f7661cb9 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -62,9 +62,9 @@ exports.scripts = [ // Services "js/service/LocalStorageService.js", "js/service/HistoryService.js", + "js/service/keyboard/ShortcutService.js", "js/service/keyboard/KeycodeTranslator.js", "js/service/keyboard/CheatsheetService.js", - "js/service/KeyboardEventService.js", "js/service/ImageUploadService.js", // Tools From 0a05374af5404434cb9af6a02d497123690f0a19 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Wed, 20 Nov 2013 22:58:20 +0100 Subject: [PATCH 16/19] feature : add keyboard shortcuts + added shortcut to create new frames (n) + added shortcut to duplicate frame (shift+n) --- js/controller/PiskelController.js | 24 +++++++++++++++++++----- js/controller/PreviewFilmController.js | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/js/controller/PiskelController.js b/js/controller/PiskelController.js index c3351d58..acb20765 100644 --- a/js/controller/PiskelController.js +++ b/js/controller/PiskelController.js @@ -20,6 +20,8 @@ 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.FRAME_SIZE_CHANGED); @@ -68,15 +70,21 @@ return !!this.getCurrentLayer().getFrameAt(index); }; - // backward from framesheet - ns.PiskelController.prototype.getFrameByIndex = - ns.PiskelController.prototype.getMergedFrameAt; + ns.PiskelController.prototype.addFrame = function () { + this.addFrameAt(this.getFrameCount()); + }; - 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(); layers.forEach(function (l) { - l.addFrame(this.createEmptyFrame_()); + l.addFrameAt(this.createEmptyFrame_(), index); }.bind(this)); + + $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.createEmptyFrame_ = function () { @@ -97,11 +105,17 @@ $.publish(Events.PISKEL_RESET); }; + ns.PiskelController.prototype.duplicateCurrentFrame = function () { + this.duplicateFrameAt(this.currentFrameIndex); + }; + ns.PiskelController.prototype.duplicateFrameAt = function (index) { var layers = this.getLayers(); layers.forEach(function (l) { l.duplicateFrameAt(index); }); + + $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { diff --git a/js/controller/PreviewFilmController.js b/js/controller/PreviewFilmController.js index 7d1d1310..4006196f 100644 --- a/js/controller/PreviewFilmController.js +++ b/js/controller/PreviewFilmController.js @@ -19,7 +19,7 @@ }; ns.PreviewFilmController.prototype.addFrame = function () { - this.piskelController.addEmptyFrame(); + this.piskelController.addFrame(); this.piskelController.setCurrentFrameIndex(this.piskelController.getFrameCount() - 1); this.updateScrollerOverflows(); }; From 2d3bbc73f13dc77d143670e0e784bbe86191e590 Mon Sep 17 00:00:00 2001 From: jdescottes Date: Wed, 20 Nov 2013 23:00:47 +0100 Subject: [PATCH 17/19] Forgot to add duplicate frame shortcut to cheatsheet --- js/service/keyboard/CheatsheetService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/service/keyboard/CheatsheetService.js b/js/service/keyboard/CheatsheetService.js index 93d66731..2a00bc8f 100644 --- a/js/service/keyboard/CheatsheetService.js +++ b/js/service/keyboard/CheatsheetService.js @@ -85,6 +85,7 @@ toDescriptor('↑', 'Select previous frame'), /* ASCII for up-arrow */ toDescriptor('↓', '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++) { From 3de3c3542e9c9478f176c0fadc1271260bf13fd1 Mon Sep 17 00:00:00 2001 From: juliandescottes Date: Sat, 23 Nov 2013 19:33:00 +0100 Subject: [PATCH 18/19] Post merge fixes --- js/service/LocalStorageService.js | 5 ----- piskel-script-list.js | 1 - 2 files changed, 6 deletions(-) diff --git a/js/service/LocalStorageService.js b/js/service/LocalStorageService.js index 4fb92114..783211c3 100644 --- a/js/service/LocalStorageService.js +++ b/js/service/LocalStorageService.js @@ -45,14 +45,9 @@ */ ns.LocalStorageService.prototype.restoreFromLocalStorage_ = function() { var framesheet = JSON.parse(window.localStorage.snapShot); -<<<<<<< HEAD pskl.utils.serialization.Deserializer.deserialize(framesheet, function (piskel) { pskl.app.piskelController.setPiskel(piskel); }); -======= - var piskel = pskl.utils.Serializer.createPiskel(framesheet); - pskl.app.piskelController.setPiskel(piskel); ->>>>>>> master }; /** diff --git a/piskel-script-list.js b/piskel-script-list.js index 63c45f9e..9234465c 100644 --- a/piskel-script-list.js +++ b/piskel-script-list.js @@ -50,7 +50,6 @@ exports.scripts = [ "js/rendering/frame/FrameRenderer.js", "js/rendering/frame/CachedFrameRenderer.js", "js/rendering/CanvasRenderer.js", - "js/rendering/FrameRenderer.js", "js/rendering/FramesheetRenderer.js", "js/rendering/PiskelRenderer.js", From 569f42b7e2caaf36f7de2c7162712e57756b8edb Mon Sep 17 00:00:00 2001 From: jdescottes Date: Mon, 25 Nov 2013 22:24:55 +0100 Subject: [PATCH 19/19] Fix : fire resize event after loading piskel --- js/controller/PiskelController.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/controller/PiskelController.js b/js/controller/PiskelController.js index acb20765..12c570ac 100644 --- a/js/controller/PiskelController.js +++ b/js/controller/PiskelController.js @@ -15,6 +15,9 @@ this.currentFrameIndex = 0; this.layerIdCounter = 1; + + $.publish(Events.PISKEL_RESET); + $.publish(Events.FRAME_SIZE_CHANGED); }; ns.PiskelController.prototype.init = function () { @@ -22,9 +25,6 @@ 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.FRAME_SIZE_CHANGED); }; ns.PiskelController.prototype.getHeight = function () {