diff --git a/.gitignore b/.gitignore index 9d68abca..780fe156 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ npm-debug.log build/*.js build/*.css +# diffs +diff.txt + # Closure compiler generated JS binary. build/closure/closure_compiled_binary.js diff --git a/src/css/tools.css b/src/css/tools.css index 3b0dbcf2..6f38e313 100644 --- a/src/css/tools.css +++ b/src/css/tools.css @@ -130,7 +130,7 @@ } .tool-rectangle-select .drawing-canvas-container:hover { - cursor: url(../img/icons/select.png) 15 15, pointer; + cursor: crosshair; } .tool-shape-select .drawing-canvas-container:hover { diff --git a/src/js/Events.js b/src/js/Events.js index 07d75b09..97d84028 100644 --- a/src/js/Events.js +++ b/src/js/Events.js @@ -32,6 +32,7 @@ var Events = { * Number of frames, content of frames, color used for the palette may have changed. */ PISKEL_RESET: "PISKEL_RESET", + PISKEL_SAVE_STATE: "PISKEL_SAVE_STATE", PISKEL_SAVED: "PISKEL_SAVED", diff --git a/src/js/app.js b/src/js/app.js index 07885727..28225379 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -30,7 +30,10 @@ piskel.addLayer(layer); - this.piskelController = new pskl.controller.PiskelController(piskel); + this.corePiskelController = new pskl.controller.piskel.PiskelController(piskel); + this.corePiskelController.init(); + + this.piskelController = new pskl.controller.piskel.PublicPiskelController(this.corePiskelController); this.piskelController.init(); this.paletteController = new pskl.controller.PaletteController(); @@ -69,7 +72,7 @@ this.selectionManager = new pskl.selection.SelectionManager(this.piskelController); this.selectionManager.init(); - this.historyService = new pskl.service.HistoryService(this.piskelController); + this.historyService = new pskl.service.HistoryService(this.corePiskelController); this.historyService.init(); this.notificationController = new pskl.controller.NotificationController(); diff --git a/src/js/controller/DrawingController.js b/src/js/controller/DrawingController.js index 1cb9dd7f..8bfe9f08 100644 --- a/src/js/controller/DrawingController.js +++ b/src/js/controller/DrawingController.js @@ -157,8 +157,8 @@ // Warning : do not call setCurrentButton here // mousemove do not have the correct mouse button information on all browsers this.currentToolBehavior.moveToolAt( - coords.x, - coords.y, + coords.x | 0, + coords.y | 0, this.getCurrentColor_(event), currentFrame, this.overlayFrame, @@ -175,6 +175,7 @@ event ); } + $.publish(Events.CURSOR_MOVED, [coords.x, coords.y]); this.previousMousemoveTime = currentTime; } }; diff --git a/src/js/controller/LayersListController.js b/src/js/controller/LayersListController.js index b3e2a76f..27246926 100644 --- a/src/js/controller/LayersListController.js +++ b/src/js/controller/LayersListController.js @@ -44,15 +44,15 @@ this.piskelController.setCurrentLayerIndex(parseInt(index, 10)); } else if (el.classList.contains('edit-icon')) { index = el.parentNode.dataset.layerIndex; - var layer = this.piskelController.getLayerByIndex(parseInt(index, 10)); - this.renameLayer_(layer); + this.renameLayerAt_(index); } }; - ns.LayersListController.prototype.renameLayer_ = function (layer) { - var newName = window.prompt("Please enter the layer name", layer.getName()); - if (newName) { - layer.setName(newName); + ns.LayersListController.prototype.renameLayerAt_ = function (index) { + var layer = this.piskelController.getLayerAt(index); + var name = window.prompt("Please enter the layer name", layer.getName()); + if (name) { + this.piskelController.renameLayerAt(index, name); this.renderLayerList_(); } }; diff --git a/src/js/controller/PiskelController.js b/src/js/controller/piskel/PiskelController.js similarity index 82% rename from src/js/controller/PiskelController.js rename to src/js/controller/piskel/PiskelController.js index 776f220a..630ceb6d 100644 --- a/src/js/controller/PiskelController.js +++ b/src/js/controller/piskel/PiskelController.js @@ -1,5 +1,5 @@ (function () { - var ns = $.namespace('pskl.controller'); + var ns = $.namespace('pskl.controller.piskel'); ns.PiskelController = function (piskel) { if (piskel) { @@ -15,16 +15,9 @@ this.currentFrameIndex = 0; this.layerIdCounter = 1; - - $.publish(Events.FRAME_SIZE_CHANGED); - $.publish(Events.PISKEL_RESET); }; 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)); }; ns.PiskelController.prototype.getHeight = function () { @@ -51,7 +44,11 @@ }; ns.PiskelController.prototype.getCurrentLayer = function () { - return this.piskel.getLayerAt(this.currentLayerIndex); + return this.getLayerAt(this.currentLayerIndex); + }; + + ns.PiskelController.prototype.getLayerAt = function (index) { + return this.piskel.getLayerAt(index); }; ns.PiskelController.prototype.getCurrentFrame = function () { @@ -59,6 +56,19 @@ return layer.getFrameAt(this.currentFrameIndex); }; + + ns.PiskelController.prototype.getCurrentLayerIndex = function () { + return this.currentLayerIndex; + }; + + ns.PiskelController.prototype.getCurrentFrameIndex = function () { + return this.currentFrameIndex; + }; + + ns.PiskelController.prototype.getPiskel = function () { + return this.piskel; + }; + ns.PiskelController.prototype.getFrameAt = function (index) { var frames = this.getLayers().map(function (l) { return l.getFrameAt(index); @@ -79,12 +89,9 @@ }; ns.PiskelController.prototype.addFrameAt = function (index) { - var layers = this.getLayers(); - layers.forEach(function (l) { + this.getLayers().forEach(function (l) { l.addFrameAt(this.createEmptyFrame_(), index); }.bind(this)); - - $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.createEmptyFrame_ = function () { @@ -93,16 +100,13 @@ }; ns.PiskelController.prototype.removeFrameAt = function (index) { - var layers = this.getLayers(); - layers.forEach(function (l) { + this.getLayers().forEach(function (l) { l.removeFrameAt(index); }); // Current frame index is impacted if the removed frame was before the current frame if (this.currentFrameIndex >= index && this.currentFrameIndex > 0) { this.setCurrentFrameIndex(this.currentFrameIndex - 1); } - - $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.duplicateCurrentFrame = function () { @@ -110,17 +114,13 @@ }; ns.PiskelController.prototype.duplicateFrameAt = function (index) { - var layers = this.getLayers(); - layers.forEach(function (l) { + this.getLayers().forEach(function (l) { l.duplicateFrameAt(index); }); - - $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { - var layers = this.getLayers(); - layers.forEach(function (l) { + this.getLayers().forEach(function (l) { l.moveFrame(fromIndex, toIndex); }); }; @@ -132,7 +132,6 @@ ns.PiskelController.prototype.setCurrentFrameIndex = function (index) { this.currentFrameIndex = index; - $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.selectNextFrame = function () { @@ -151,7 +150,6 @@ ns.PiskelController.prototype.setCurrentLayerIndex = function (index) { this.currentLayerIndex = index; - $.publish(Events.PISKEL_RESET); }; ns.PiskelController.prototype.selectLayer = function (layer) { @@ -161,6 +159,13 @@ } }; + ns.PiskelController.prototype.renameLayerAt = function (index, name) { + var layer = this.getLayerByIndex(index); + if (layer) { + layer.setName(name); + } + }; + ns.PiskelController.prototype.getLayerByIndex = function (index) { var layers = this.getLayers(); if (layers[index]) { @@ -190,6 +195,7 @@ } this.piskel.addLayer(layer); this.setCurrentLayerIndex(this.piskel.getLayers().length - 1); + } else { throw 'Layer name should be unique'; } @@ -219,11 +225,7 @@ } }; - ns.PiskelController.prototype.serialize = function () { - return pskl.utils.Serializer.serializePiskel(this.piskel); - }; - - ns.PiskelController.prototype.load = function (data) { - this.deserialize(JSON.stringify(data)); + ns.PiskelController.prototype.serialize = function (expanded) { + return pskl.utils.Serializer.serializePiskel(this.piskel, expanded); }; })(); \ No newline at end of file diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js new file mode 100644 index 00000000..d9e5002b --- /dev/null +++ b/src/js/controller/piskel/PublicPiskelController.js @@ -0,0 +1,143 @@ +(function () { + var ns = $.namespace('pskl.controller.piskel'); + + ns.PublicPiskelController = function (piskelController) { + this.piskelController = piskelController; + pskl.utils.wrap(this, this.piskelController); + }; + + ns.PublicPiskelController.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)); + }; + + ns.PublicPiskelController.prototype.setPiskel = function (piskel) { + this.piskelController.setPiskel(piskel); + + $.publish(Events.FRAME_SIZE_CHANGED); + $.publish(Events.PISKEL_RESET); + $.publish(Events.PISKEL_SAVE_STATE, { + type : pskl.service.HistoryService.SNAPSHOT + }); + }; + + ns.PublicPiskelController.prototype.addFrame = function () { + this.addFrameAt(this.getFrameCount()); + }; + + ns.PublicPiskelController.prototype.addFrameAtCurrentIndex = function () { + this.addFrameAt(this.getCurrentFrameIndex()); + }; + + ns.PublicPiskelController.prototype.addFrameAt = function (index) { + this.raiseSaveStateEvent_(this.piskelController.addFrameAt, [index]); + this.piskelController.addFrameAt(index); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.removeFrameAt = function (index) { + this.raiseSaveStateEvent_(this.piskelController.removeFrameAt, [index]); + this.piskelController.removeFrameAt(index); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.duplicateCurrentFrame = function () { + this.piskelController.duplicateFrameAt(this.getCurrentFrameIndex()); + }; + + ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args) { + $.publish(Events.PISKEL_SAVE_STATE, { + type : pskl.service.HistoryService.REPLAY, + scope : this, + replay : { + fn : fn, + args : args + } + }); + }; + + ns.PublicPiskelController.prototype.replay = function (frame, replayData) { + replayData.fn.apply(this.piskelController, replayData.args); + }; + + ns.PublicPiskelController.prototype.duplicateFrameAt = function (index) { + this.raiseSaveStateEvent_(this.piskelController.duplicateFrameAt, [index]); + this.piskelController.duplicateFrameAt(index); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.moveFrame = function (fromIndex, toIndex) { + this.raiseSaveStateEvent_(this.piskelController.moveFrame, [fromIndex, toIndex]); + this.piskelController.moveFrame(fromIndex, toIndex); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.setCurrentFrameIndex = function (index) { + this.piskelController.setCurrentFrameIndex(index); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.selectNextFrame = function () { + this.piskelController.selectNextFrame(); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.selectPreviousFrame = function () { + this.piskelController.selectPreviousFrame(); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.setCurrentLayerIndex = function (index) { + this.piskelController.setCurrentLayerIndex(index); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.selectLayer = function (layer) { + this.piskelController.selectLayer(layer); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.renameLayerAt = function (index, name) { + this.raiseSaveStateEvent_(this.piskelController.renameLayerAt, [index, name]); + this.piskelController.renameLayerAt(index, name); + }; + + ns.PublicPiskelController.prototype.createLayer = function (name) { + this.raiseSaveStateEvent_(this.piskelController.createLayer, [name]); + this.piskelController.createLayer(name); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.moveLayerUp = function () { + this.raiseSaveStateEvent_(this.piskelController.moveLayerUp, []); + this.piskelController.moveLayerUp(); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.moveLayerDown = function () { + this.raiseSaveStateEvent_(this.piskelController.moveLayerDown, []); + this.piskelController.moveLayerDown(); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.removeCurrentLayer = function () { + this.raiseSaveStateEvent_(this.piskelController.removeCurrentLayer, []); + this.piskelController.removeCurrentLayer(); + $.publish(Events.PISKEL_RESET); + }; + + ns.PublicPiskelController.prototype.getCurrentLayerIndex = function () { + return this.piskelController.currentLayerIndex; + }; + + ns.PublicPiskelController.prototype.getCurrentFrameIndex = function () { + return this.piskelController.currentFrameIndex; + }; + + ns.PublicPiskelController.prototype.getPiskel = function () { + return this.piskelController.piskel; + }; + +})(); \ No newline at end of file diff --git a/src/js/controller/settings/PngExportController.js b/src/js/controller/settings/PngExportController.js index 33e9e546..5afba0b9 100644 --- a/src/js/controller/settings/PngExportController.js +++ b/src/js/controller/settings/PngExportController.js @@ -56,7 +56,7 @@ }; ns.PngExportController.prototype.getPiskelName_ = function () { - return this.piskelController.piskel.getDescriptor().name; + return this.piskelController.getPiskel().getDescriptor().name; }; ns.PngExportController.prototype.getFramesheetAsBase64Png = function () { diff --git a/src/js/controller/settings/ResizeController.js b/src/js/controller/settings/ResizeController.js index 7ed4cec9..550946d7 100644 --- a/src/js/controller/settings/ResizeController.js +++ b/src/js/controller/settings/ResizeController.js @@ -39,7 +39,7 @@ layers.push(layer); } - var piskel = pskl.model.Piskel.fromLayers(layers, this.piskelController.piskel.getDescriptor()); + var piskel = pskl.model.Piskel.fromLayers(layers, this.piskelController.getPiskel().getDescriptor()); pskl.app.piskelController.setPiskel(piskel); $.publish(Events.CLOSE_SETTINGS_DRAWER); }; diff --git a/src/js/controller/settings/SaveController.js b/src/js/controller/settings/SaveController.js index 01c7a6f9..fc083339 100644 --- a/src/js/controller/settings/SaveController.js +++ b/src/js/controller/settings/SaveController.js @@ -21,7 +21,7 @@ this.status = $('#save-status'); - var descriptor = this.piskelController.piskel.getDescriptor(); + var descriptor = this.piskelController.getPiskel().getDescriptor(); this.nameInput.val(descriptor.name); this.descriptionInput.val(descriptor.description); @@ -46,7 +46,7 @@ var isPublic = !!this.isPublicCheckbox.prop('checked'); var descriptor = new pskl.model.piskel.Descriptor(name, description, isPublic); - this.piskelController.piskel.setDescriptor(descriptor); + this.piskelController.getPiskel().setDescriptor(descriptor); this.beforeSaving_(); pskl.app.store({ diff --git a/src/js/drawingtools/BaseTool.js b/src/js/drawingtools/BaseTool.js index c9cca264..a408e453 100644 --- a/src/js/drawingtools/BaseTool.js +++ b/src/js/drawingtools/BaseTool.js @@ -6,16 +6,17 @@ (function() { var ns = $.namespace("pskl.drawingtools"); - ns.BaseTool = function() {}; + ns.BaseTool = function() { + this.toolId = "tool-base"; + }; ns.BaseTool.prototype.applyToolAt = function(col, row, color, frame, overlay, event) {}; - ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay, event) { - $.publish(Events.CURSOR_MOVED, [col, row]); - }; + ns.BaseTool.prototype.moveToolAt = function(col, row, color, frame, overlay, event) {}; + + ns.BaseTool.prototype.replay = Constants.ABSTRACT_FUNCTION; ns.BaseTool.prototype.moveUnactiveToolAt = function(col, row, color, frame, overlay, event) { - $.publish(Events.CURSOR_MOVED, [col, row]); if (overlay.containsPixel(col, row)) { if (!isNaN(this.highlightedPixelCol) && @@ -49,6 +50,14 @@ } }; + ns.BaseTool.prototype.raiseSaveStateEvent = function (replayData) { + $.publish(Events.PISKEL_SAVE_STATE, { + type : pskl.service.HistoryService.REPLAY, + scope : this, + replay : replayData + }); + }; + ns.BaseTool.prototype.releaseToolAt = function(col, row, color, frame, overlay, event) {}; diff --git a/src/js/drawingtools/Eraser.js b/src/js/drawingtools/Eraser.js index 57570fe9..27e169c7 100644 --- a/src/js/drawingtools/Eraser.js +++ b/src/js/drawingtools/Eraser.js @@ -8,6 +8,7 @@ var ns = $.namespace("pskl.drawingtools"); ns.Eraser = function() { + this.superclass.constructor.call(this); this.toolId = "tool-eraser"; this.helpText = "Eraser tool"; }; diff --git a/src/js/drawingtools/Move.js b/src/js/drawingtools/Move.js index 30baac06..630a5729 100644 --- a/src/js/drawingtools/Move.js +++ b/src/js/drawingtools/Move.js @@ -29,7 +29,6 @@ }; ns.Move.prototype.moveToolAt = function(col, row, color, frame, overlay, event) { - $.publish(Events.CURSOR_MOVED, [col, row]); var colDiff = col - this.startCol, rowDiff = row - this.startRow; this.shiftFrame(colDiff, rowDiff, frame, this.frameClone); }; @@ -53,5 +52,14 @@ */ ns.Move.prototype.releaseToolAt = function(col, row, color, frame, overlay, event) { this.moveToolAt(col, row, color, frame, overlay); + + this.raiseSaveStateEvent({ + colDiff : col - this.startCol, + rowDiff : row - this.startRow + }); + }; + + ns.Move.prototype.replay = function(frame, replayData) { + this.shiftFrame(replayData.colDiff, replayData.rowDiff, frame, frame.clone()); }; })(); diff --git a/src/js/drawingtools/PaintBucket.js b/src/js/drawingtools/PaintBucket.js index 34fc4714..c2ece067 100644 --- a/src/js/drawingtools/PaintBucket.js +++ b/src/js/drawingtools/PaintBucket.js @@ -17,8 +17,17 @@ * @override */ ns.PaintBucket.prototype.applyToolAt = function(col, row, color, frame, overlay, event) { - pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, col, row, color); + + this.raiseSaveStateEvent({ + col : col, + row : row, + color : color + }); + }; + + ns.PaintBucket.prototype.replay = function (frame, replayData) { + pskl.PixelUtils.paintSimilarConnectedPixelsFromFrame(frame, replayData.col, replayData.row, replayData.color); }; })(); diff --git a/src/js/drawingtools/ShapeTool.js b/src/js/drawingtools/ShapeTool.js index d0de3a0e..a99965d2 100644 --- a/src/js/drawingtools/ShapeTool.js +++ b/src/js/drawingtools/ShapeTool.js @@ -49,7 +49,24 @@ } var coords = this.getCoordinates_(col, row, event); this.draw_(coords.col, coords.row, color, frame); + $.publish(Events.DRAG_END, [col, row]); + this.raiseSaveStateEvent({ + col : col, + row : row, + startCol : this.startCol, + startRow : this.startRow, + color : color + }); + }; + + /** + * @override + */ + ns.ShapeTool.prototype.replay = function(frame, replayData) { + this.startCol = replayData.startCol; + this.startRow = replayData.startRow; + this.draw_(replayData.col, replayData.row, replayData.color, frame); }; /** diff --git a/src/js/drawingtools/SimplePen.js b/src/js/drawingtools/SimplePen.js index 64349e7a..ffbfa845 100644 --- a/src/js/drawingtools/SimplePen.js +++ b/src/js/drawingtools/SimplePen.js @@ -13,6 +13,7 @@ this.previousCol = null; this.previousRow = null; + this.pixels = []; }; pskl.utils.inherit(ns.SimplePen, ns.BaseTool); @@ -21,18 +22,19 @@ * @override */ ns.SimplePen.prototype.applyToolAt = function(col, row, color, frame, overlay, event) { - if (frame.containsPixel(col, row)) { - frame.setPixel(col, row, color); - } + frame.setPixel(col, row, color); this.previousCol = col; this.previousRow = row; + this.pixels.push({ + col : col, + row : row + }); }; /** * @override */ ns.SimplePen.prototype.moveToolAt = function(col, row, color, frame, overlay, event) { - $.publish(Events.CURSOR_MOVED, [col, row]); if((Math.abs(col - this.previousCol) > 1) || (Math.abs(row - this.previousRow) > 1)) { // The pen movement is too fast for the mousemove frequency, there is a gap between the // current point and the previously drawn one. @@ -40,7 +42,7 @@ var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow); for(var i=0, l=interpolatedPixels.length; i LOAD_STATE_INTERVAL; + var indexInRange = index >= 0 && index < this.stateQueue.length; + return timeOk && indexInRange; + }; + + ns.HistoryService.prototype.getPreviousSnapshotIndex_ = function (index) { + while (this.stateQueue[index] && !this.stateQueue[index].piskel) { + index = index - 1; + } + return index; + }; + + ns.HistoryService.prototype.loadState = function (index) { + if (this.isLoadStateAllowed_(index)) { + this.lastLoadState = Date.now(); + + var snapshotIndex = this.getPreviousSnapshotIndex_(index); + if (snapshotIndex < 0) { + throw 'Could not find previous SNAPSHOT saved in history stateQueue'; + } + + var piskelSnapshot = this.getSnapshotFromState_(snapshotIndex); + this.loadPiskel(piskelSnapshot, this.onPiskelLoadedCallback.bind(this, index, snapshotIndex)); + } + }; + + ns.HistoryService.prototype.getSnapshotFromState_ = function (stateIndex) { + var state = this.stateQueue[stateIndex]; + var piskelSnapshot = state.piskel; + + // If the snapshot is stringified, parse it and backup the result for faster access next time + // FIXME : Memory consumption might go crazy if we keep unpacking big piskels indefinitely + // ==> should ensure I remove some of them :) + if (typeof piskelSnapshot === "string") { + piskelSnapshot = JSON.parse(piskelSnapshot); + state.piskel = piskelSnapshot; + } + + return piskelSnapshot; + }; + + ns.HistoryService.prototype.loadPiskel = function (piskel, callback) { + var descriptor = this.piskelController.piskel.getDescriptor(); + pskl.utils.serialization.Deserializer.deserialize(piskel, function (deserializedPiskel) { + deserializedPiskel.setDescriptor(descriptor); + this.piskelController.setPiskel(deserializedPiskel); + callback(deserializedPiskel); + }.bind(this)); + }; + + ns.HistoryService.prototype.onPiskelLoadedCallback = function (index, snapshotIndex, piskel) { + for (var i = snapshotIndex + 1 ; i <= index ; i++) { + var state = this.stateQueue[i]; + this.setupState(state); + this.replayState(state); + } + + var lastState = this.stateQueue[index]; + this.setupState(lastState); + this.currentIndex = index; $.publish(Events.PISKEL_RESET); - $.subscribe(Events.PISKEL_RESET, this.saveState__b); + }; + + ns.HistoryService.prototype.setupState = function (state) { + this.piskelController.setCurrentFrameIndex(state.frameIndex); + this.piskelController.setCurrentLayerIndex(state.layerIndex); + }; + + ns.HistoryService.prototype.replayState = function (state) { + var action = state.action; + var type = action.type; + var layer = this.piskelController.getLayerAt(state.layerIndex); + var frame = layer.getFrameAt(state.frameIndex); + action.scope.replay(frame, action.replay); }; })(); \ No newline at end of file diff --git a/src/js/service/SavedStatusService.js b/src/js/service/SavedStatusService.js index ac3d6a23..04ab950b 100644 --- a/src/js/service/SavedStatusService.js +++ b/src/js/service/SavedStatusService.js @@ -2,7 +2,7 @@ var ns = $.namespace('pskl.service'); ns.SavedStatusService = function (piskelController) { - this.piskelController_ = piskelController; + this.piskelController = piskelController; }; ns.SavedStatusService.prototype.init = function () { @@ -15,7 +15,7 @@ }; ns.SavedStatusService.prototype.onPiskelReset = function () { - var piskel = this.piskelController_.piskel; + var piskel = this.piskelController.getPiskel(); // A first PISKEL_RESET is triggered during the load of a new Piskel, it should be ignored // putting a firstResetDone flag as a nasty workaround for this if (piskel.firstResetDone_) { @@ -34,7 +34,7 @@ }; ns.SavedStatusService.prototype.updateDirtyStatus = function (status) { - var piskel = this.piskelController_.piskel; + var piskel = this.piskelController.getPiskel(); if (piskel.isDirty_ !== status) { // Redraw piskel name only if dirty status actually changed piskel.isDirty_ = status; @@ -43,7 +43,7 @@ }; ns.SavedStatusService.prototype.updatePiskelName = function () { - var piskel = this.piskelController_.piskel; + var piskel = this.piskelController.getPiskel(); var name = piskel.getDescriptor().name; if (piskel.isDirty_) { $('.piskel-name').html(name + ' *'); @@ -53,7 +53,7 @@ }; ns.SavedStatusService.prototype.onBeforeUnload = function (evt) { - var piskel = this.piskelController_.piskel; + var piskel = this.piskelController.getPiskel(); if (piskel.isDirty_) { var confirmationMessage = "Your Piskel seems to have unsaved changes"; diff --git a/src/js/service/keyboard/KeycodeTranslator.js b/src/js/service/keyboard/KeycodeTranslator.js index d478dd66..00005dc7 100644 --- a/src/js/service/keyboard/KeycodeTranslator.js +++ b/src/js/service/keyboard/KeycodeTranslator.js @@ -1,6 +1,7 @@ (function () { var specialKeys = { 191 : "?", + 8 : "back", 27 : "esc", 38 : "up", 40 : "down", diff --git a/src/js/service/keyboard/ShortcutService.js b/src/js/service/keyboard/ShortcutService.js index bd2aea39..ca921e81 100644 --- a/src/js/service/keyboard/ShortcutService.js +++ b/src/js/service/keyboard/ShortcutService.js @@ -12,6 +12,13 @@ $(document.body).keydown($.proxy(this.onKeyUp_, this)); }; + /** + * Add a keyboard shortcut + * @param {String} rawKey (case insensitive) key can be a meta (optional) + [a-z0-9] or + * a special key (check list of supported keys in KeycodeTranslator) + * eg. 'ctrl+A', 'del' + * @param {Function} callback should return true to let the original event perform its default action + */ ns.ShortcutService.prototype.addShortcut = function (rawKey, callback) { var parsedKey = this.parseKey_(rawKey.toLowerCase()); @@ -78,8 +85,10 @@ } if(cb) { - cb(charkey); - evt.preventDefault(); + var bubble = cb(charkey); + if (bubble !== true) { + evt.preventDefault(); + } } } } diff --git a/src/js/utils/core.js b/src/js/utils/core.js index c37a7033..13633d7a 100644 --- a/src/js/utils/core.js +++ b/src/js/utils/core.js @@ -47,5 +47,13 @@ if (typeof Function.prototype.bind !== "function") { extendedObject.prototype.superclass = inheritFrom.prototype; }; + ns.wrap = function (wrapper, wrappedObject) { + for (var prop in wrappedObject) { + if (typeof wrappedObject[prop] === 'function' && typeof wrapper[prop] === 'undefined') { + wrapper[prop] = wrappedObject[prop].bind(wrappedObject); + } + } + }; + })(); diff --git a/src/js/utils/serialization/Deserializer.js b/src/js/utils/serialization/Deserializer.js index 5c35322a..8c0b43e4 100644 --- a/src/js/utils/serialization/Deserializer.js +++ b/src/js/utils/serialization/Deserializer.js @@ -29,11 +29,11 @@ this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor); this.layersToLoad_ = piskelData.layers.length; - - piskelData.layers.forEach(function (serializedLayer) { - var layer = this.deserializeLayer(serializedLayer); - this.piskel_.addLayer(layer); - }.bind(this)); + if (piskelData.expanded) { + piskelData.layers.forEach(this.loadExpandedLayer.bind(this)); + } else { + piskelData.layers.forEach(this.deserializeLayer.bind(this)); + } }; ns.Deserializer.prototype.deserializeLayer = function (layerString) { @@ -48,11 +48,8 @@ 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(layer.addFrame.bind(layer)); - - this.onLayerLoaded_(); + this.addFramesToLayer(frames, layer); }.bind(this); // 3 - set the source of the image @@ -62,6 +59,24 @@ return layer; }; + ns.Deserializer.prototype.loadExpandedLayer = function (layerData) { + var layer = new pskl.model.Layer(layerData.name); + var frames = layerData.grids.map(function (grid) { + return pskl.model.Frame.fromPixelGrid(grid); + }); + this.addFramesToLayer(frames, layer); + + // 4 - return a pointer to the new layer instance + return layer; + }; + + ns.Deserializer.prototype.addFramesToLayer = function (frames, layer) { + frames.forEach(layer.addFrame.bind(layer)); + + this.piskel_.addLayer(layer); + this.onLayerLoaded_(); + }; + ns.Deserializer.prototype.onLayerLoaded_ = function () { this.layersToLoad_ = this.layersToLoad_ - 1; if (this.layersToLoad_ === 0) { diff --git a/src/js/utils/serialization/Serializer.js b/src/js/utils/serialization/Serializer.js index 934de4b4..271d41ae 100644 --- a/src/js/utils/serialization/Serializer.js +++ b/src/js/utils/serialization/Serializer.js @@ -2,30 +2,35 @@ var ns = $.namespace('pskl.utils'); ns.Serializer = { - serializePiskel : function (piskel) { + serializePiskel : function (piskel, expanded) { var serializedLayers = piskel.getLayers().map(function (l) { - return pskl.utils.Serializer.serializeLayer(l); + return pskl.utils.Serializer.serializeLayer(l, expanded); }); return JSON.stringify({ modelVersion : Constants.MODEL_VERSION, piskel : { height : piskel.getHeight(), width : piskel.getWidth(), - layers : serializedLayers + layers : serializedLayers, + expanded : expanded } }); }, - serializeLayer : function (layer) { + serializeLayer : function (layer, expanded) { var frames = layer.getFrames(); var renderer = new pskl.rendering.FramesheetRenderer(frames); - var base64PNG = renderer.renderAsCanvas().toDataURL(); - - return JSON.stringify({ + var layerToSerialize = { name : layer.getName(), - base64PNG : base64PNG, frameCount : frames.length - }); + }; + if (expanded) { + layerToSerialize.grids = frames.map(function (f) {return f.pixels;}); + return layerToSerialize; + } else { + layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL(); + return JSON.stringify(layerToSerialize); + } } }; })(); diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 3a7b8809..26eb3d22 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -65,7 +65,8 @@ "js/rendering/PiskelRenderer.js", // Controllers - "js/controller/PiskelController.js", + "js/controller/piskel/PiskelController.js", + "js/controller/piskel/PublicPiskelController.js", "js/controller/CursorCoordinatesController.js", "js/controller/DrawingController.js", "js/controller/PreviewFilmController.js",