mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Feature : undo redo including frame/layer actions
- Frame and Layer CRUD actions are now registered and can be cancelled - Limited performance impact while drawing - Improved frame cache invalidation
This commit is contained in:
parent
c2a3ccc8d0
commit
5541d030a5
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -225,7 +225,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
ns.PiskelController.prototype.serialize = function (compressed) {
|
||||
return pskl.utils.Serializer.serializePiskel(this.piskel, compressed);
|
||||
ns.PiskelController.prototype.serialize = function (expanded) {
|
||||
return pskl.utils.Serializer.serializePiskel(this.piskel, expanded);
|
||||
};
|
||||
})();
|
@ -19,12 +19,12 @@
|
||||
$.publish(Events.FRAME_SIZE_CHANGED);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
$.publish(Events.PISKEL_SAVE_STATE, {
|
||||
type : 'FULL'
|
||||
type : pskl.service.HistoryService.SNAPSHOT
|
||||
});
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.addFrame = function () {
|
||||
this.addFrameAt(this.piskelController.getFrameCount());
|
||||
this.addFrameAt(this.getFrameCount());
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.addFrameAtCurrentIndex = function () {
|
||||
@ -32,14 +32,14 @@
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.addFrameAt = function (index) {
|
||||
this.piskelController.addFrameAt(index);
|
||||
this.raiseSaveStateEvent_(this.piskelController.addFrameAt, [index]);
|
||||
this.piskelController.addFrameAt(index);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.removeFrameAt = function (index) {
|
||||
this.piskelController.removeFrameAt(index);
|
||||
this.raiseSaveStateEvent_(this.piskelController.removeFrameAt, [index]);
|
||||
this.piskelController.removeFrameAt(index);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
|
||||
ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args) {
|
||||
$.publish(Events.PISKEL_SAVE_STATE, {
|
||||
type : 'REPLAY',
|
||||
type : pskl.service.HistoryService.REPLAY,
|
||||
scope : this,
|
||||
replay : {
|
||||
fn : fn,
|
||||
@ -63,14 +63,14 @@
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.duplicateFrameAt = function (index) {
|
||||
this.piskelController.duplicateFrameAt(index);
|
||||
this.raiseSaveStateEvent_(this.piskelController.duplicateFrameAt, [index]);
|
||||
this.piskelController.duplicateFrameAt(index);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.moveFrame = function (fromIndex, toIndex) {
|
||||
this.piskelController.moveFrame(fromIndex, toIndex);
|
||||
this.raiseSaveStateEvent_(this.piskelController.moveFrame, [fromIndex, toIndex]);
|
||||
this.piskelController.moveFrame(fromIndex, toIndex);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
@ -100,31 +100,31 @@
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.renameLayerAt = function (index, name) {
|
||||
this.piskelController.renameLayerAt(index, name);
|
||||
this.raiseSaveStateEvent_(this.piskelController.renameLayerAt, [index, name]);
|
||||
this.piskelController.renameLayerAt(index, name);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.createLayer = function (name) {
|
||||
this.piskelController.createLayer(name);
|
||||
this.raiseSaveStateEvent_(this.piskelController.createLayer, [name]);
|
||||
this.piskelController.createLayer(name);
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.moveLayerUp = function () {
|
||||
this.piskelController.moveLayerUp();
|
||||
this.raiseSaveStateEvent_(this.piskelController.moveLayerUp, []);
|
||||
this.piskelController.moveLayerUp();
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.moveLayerDown = function () {
|
||||
this.piskelController.moveLayerDown();
|
||||
this.raiseSaveStateEvent_(this.piskelController.moveLayerDown, []);
|
||||
this.piskelController.moveLayerDown();
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
ns.PublicPiskelController.prototype.removeCurrentLayer = function () {
|
||||
this.piskelController.removeCurrentLayer();
|
||||
this.raiseSaveStateEvent_(this.piskelController.removeCurrentLayer, []);
|
||||
this.piskelController.removeCurrentLayer();
|
||||
$.publish(Events.PISKEL_RESET);
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
ns.BaseTool.prototype.raiseSaveStateEvent = function (replayData) {
|
||||
$.publish(Events.PISKEL_SAVE_STATE, {
|
||||
type : 'REPLAY',
|
||||
type : pskl.service.HistoryService.REPLAY,
|
||||
scope : this,
|
||||
replay : replayData
|
||||
});
|
||||
|
@ -42,7 +42,7 @@
|
||||
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
|
||||
for(var i=0, l=interpolatedPixels.length; i<l; i++) {
|
||||
var coords = interpolatedPixels[i];
|
||||
this.applyToolAt(coords.col, coords.row, color, frame, overlay);
|
||||
this.applyToolAt(coords.col, coords.row, color, frame, overlay, event);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -60,7 +60,7 @@
|
||||
}
|
||||
|
||||
$.publish(Events.PISKEL_SAVE_STATE, {
|
||||
type : 'REPLAY',
|
||||
type : pskl.service.HistoryService.REPLAY,
|
||||
scope : this,
|
||||
replay : {
|
||||
type : SELECTION_REPLAY.ERASE,
|
||||
@ -86,7 +86,7 @@
|
||||
var currentFrame = this.piskelController.getCurrentFrame();
|
||||
|
||||
$.publish(Events.PISKEL_SAVE_STATE, {
|
||||
type : 'REPLAY',
|
||||
type : pskl.service.HistoryService.REPLAY,
|
||||
scope : this,
|
||||
replay : {
|
||||
type : SELECTION_REPLAY.PASTE,
|
||||
|
@ -8,31 +8,41 @@
|
||||
this.piskelController = piskelController;
|
||||
this.stateQueue = [];
|
||||
this.currentIndex = -1;
|
||||
this.saveState__b = this.saveState.bind(this);
|
||||
this.saveState__b = this.onSaveStateEvent.bind(this);
|
||||
|
||||
this.lastLoadState = -1;
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.init = function () {
|
||||
ns.HistoryService.SNAPSHOT = 'SNAPSHOT';
|
||||
ns.HistoryService.REPLAY = 'REPLAY';
|
||||
|
||||
ns.HistoryService.prototype.init = function () {
|
||||
$.subscribe(Events.PISKEL_SAVE_STATE, this.saveState__b);
|
||||
|
||||
pskl.app.shortcutService.addShortcut('ctrl+Z', this.undo.bind(this));
|
||||
pskl.app.shortcutService.addShortcut('ctrl+Y', this.redo.bind(this));
|
||||
|
||||
this.saveState({
|
||||
type : ns.HistoryService.SNAPSHOT
|
||||
});
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.saveState = function (evt, actionInfo) {
|
||||
ns.HistoryService.prototype.onSaveStateEvent = function (evt, stateInfo) {
|
||||
this.saveState(stateInfo);
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.saveState = function (stateInfo) {
|
||||
this.stateQueue = this.stateQueue.slice(0, this.currentIndex + 1);
|
||||
this.currentIndex = this.currentIndex + 1;
|
||||
|
||||
var state = {
|
||||
action : actionInfo,
|
||||
action : stateInfo,
|
||||
frameIndex : this.piskelController.currentFrameIndex,
|
||||
layerIndex : this.piskelController.currentLayerIndex
|
||||
};
|
||||
|
||||
if (actionInfo.type === 'FULL' || this.currentIndex % SNAPSHOT_PERIOD === 0) {
|
||||
state.piskel = this.piskelController.serialize(false);
|
||||
if (stateInfo.type === ns.HistoryService.SNAPSHOT || this.currentIndex % SNAPSHOT_PERIOD === 0) {
|
||||
state.piskel = this.piskelController.serialize(true);
|
||||
}
|
||||
|
||||
this.stateQueue.push(state);
|
||||
@ -46,26 +56,6 @@
|
||||
this.loadState(this.currentIndex + 1);
|
||||
};
|
||||
|
||||
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 serializedPiskel = this.stateQueue[snapshotIndex].piskel;
|
||||
|
||||
if (typeof serializedPiskel === "string") {
|
||||
this.stateQueue[snapshotIndex].piskel = JSON.parse(serializedPiskel);
|
||||
serializedPiskel = this.stateQueue[snapshotIndex].piskel;
|
||||
}
|
||||
|
||||
this.loadPiskel(serializedPiskel, this.onPiskelLoadedCallback.bind(this, index, snapshotIndex));
|
||||
}
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.isLoadStateAllowed_ = function (index) {
|
||||
var timeOk = (Date.now() - this.lastLoadState) > LOAD_STATE_INTERVAL;
|
||||
var indexInRange = index >= 0 && index < this.stateQueue.length;
|
||||
@ -79,6 +69,44 @@
|
||||
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];
|
||||
@ -97,15 +125,6 @@
|
||||
this.piskelController.setCurrentLayerIndex(state.layerIndex);
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.loadPiskel = function (piskel, callback) {
|
||||
var descriptor = this.piskelController.piskel.getDescriptor();
|
||||
pskl.utils.serialization.Deserializer.deserialize(piskel, function (piskel) {
|
||||
piskel.setDescriptor(descriptor);
|
||||
this.piskelController.setPiskel(piskel);
|
||||
callback(piskel);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.HistoryService.prototype.replayState = function (state) {
|
||||
var action = state.action;
|
||||
var type = action.type;
|
||||
|
@ -49,7 +49,7 @@ if (typeof Function.prototype.bind !== "function") {
|
||||
|
||||
ns.wrap = function (wrapper, wrappedObject) {
|
||||
for (var prop in wrappedObject) {
|
||||
if (typeof wrappedObject[prop] === 'function') {
|
||||
if (typeof wrappedObject[prop] === 'function' && typeof wrapper[prop] === 'undefined') {
|
||||
wrapper[prop] = wrappedObject[prop].bind(wrappedObject);
|
||||
}
|
||||
}
|
||||
|
@ -29,19 +29,17 @@
|
||||
this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, descriptor);
|
||||
|
||||
this.layersToLoad_ = piskelData.layers.length;
|
||||
|
||||
piskelData.layers.forEach(function (serializedLayer) {
|
||||
this.deserializeLayer(serializedLayer);
|
||||
}.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) {
|
||||
var layerData = typeof layerString === "string" ? JSON.parse(layerString) : layerString;
|
||||
var layerData = JSON.parse(layerString);
|
||||
var layer = new pskl.model.Layer(layerData.name);
|
||||
|
||||
var isCompressedLayer = !!layerData.base64PNG;
|
||||
|
||||
if (isCompressedLayer) {
|
||||
// 1 - create an image to load the base64PNG representing the layer
|
||||
var base64PNG = layerData.base64PNG;
|
||||
var image = new Image();
|
||||
@ -56,12 +54,17 @@
|
||||
|
||||
// 3 - set the source of the image
|
||||
image.src = base64PNG;
|
||||
} else {
|
||||
|
||||
// 4 - return a pointer to the new layer instance
|
||||
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;
|
||||
|
@ -2,36 +2,34 @@
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.Serializer = {
|
||||
serializePiskel : function (piskel, compressed) {
|
||||
serializePiskel : function (piskel, expanded) {
|
||||
var serializedLayers = piskel.getLayers().map(function (l) {
|
||||
return pskl.utils.Serializer.serializeLayer(l, compressed);
|
||||
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, compressed) {
|
||||
if (compressed !== false) {
|
||||
compressed = true;
|
||||
}
|
||||
serializeLayer : function (layer, expanded) {
|
||||
var frames = layer.getFrames();
|
||||
var renderer = new pskl.rendering.FramesheetRenderer(frames);
|
||||
var layerToSerialize = {
|
||||
name : layer.getName(),
|
||||
frameCount : frames.length
|
||||
};
|
||||
if (compressed) {
|
||||
layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL();
|
||||
return JSON.stringify(layerToSerialize);
|
||||
} else {
|
||||
if (expanded) {
|
||||
layerToSerialize.grids = frames.map(function (f) {return f.pixels;});
|
||||
return layerToSerialize;
|
||||
} else {
|
||||
layerToSerialize.base64PNG = renderer.renderAsCanvas().toDataURL();
|
||||
return JSON.stringify(layerToSerialize);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user