diff --git a/src/js/Events.js b/src/js/Events.js index 21eb133b..8f9ee885 100644 --- a/src/js/Events.js +++ b/src/js/Events.js @@ -37,6 +37,9 @@ var Events = { PISKEL_RESET: "PISKEL_RESET", PISKEL_SAVE_STATE: "PISKEL_SAVE_STATE", + HISTORY_STATE_SAVED: "HISTORY_STATE_SAVED", + HISTORY_STATE_LOADED: "HISTORY_STATE_LOADED", + PISKEL_SAVED: "PISKEL_SAVED", FRAME_SIZE_CHANGED : "FRAME_SIZE_CHANGED", diff --git a/src/js/controller/AnimatedPreviewController.js b/src/js/controller/AnimatedPreviewController.js index 25a5019f..488ef88c 100644 --- a/src/js/controller/AnimatedPreviewController.js +++ b/src/js/controller/AnimatedPreviewController.js @@ -21,13 +21,13 @@ var frame = this.piskelController.getCurrentFrame(); this.renderer = new pskl.rendering.frame.TiledFrameRenderer(this.container); + this.popupPreviewController = new pskl.controller.PopupPreviewController(); }; ns.AnimatedPreviewController.prototype.init = function () { // the oninput event won't work on IE10 unfortunately, but at least will provide a // consistent behavior across all other browsers that support the input type range // see https://bugzilla.mozilla.org/show_bug.cgi?id=853670 - this.fpsRangeInput.on('input change', this.onFPSSliderChange.bind(this)); document.querySelector('.right-column').style.width = Constants.ANIMATED_PREVIEW_WIDTH + 'px'; @@ -39,8 +39,10 @@ $.subscribe(Events.FRAME_SIZE_CHANGED, this.onFrameSizeChange_.bind(this)); $.subscribe(Events.USER_SETTINGS_CHANGED, $.proxy(this.onUserSettingsChange_, this)); - $.subscribe(Events.TOOL_RELEASED, this.setRenderFlag_.bind(this, true)); - $.subscribe(Events.TOOL_PRESSED, this.setRenderFlag_.bind(this, false)); + $.subscribe(Events.PISKEL_SAVE_STATE, this.setRenderFlag_.bind(this, true)); + $.subscribe(Events.PISKEL_RESET, this.setRenderFlag_.bind(this, true)); + + this.popupPreviewController.init(); this.updateZoom_(); this.updateOnionSkinPreview_(); @@ -117,33 +119,27 @@ }; ns.AnimatedPreviewController.prototype.render = function (delta) { - if (this.renderFlag) { - this.elapsedTime += delta; - if (this.fps === 0) { - this._renderSelectedFrame(); - } else { - this._renderCurrentAnimationFrame(); - } + this.elapsedTime += delta; + var index = this.getNextIndex_(delta); + if (this.renderFlag || this.currentIndex != index) { + this.currentIndex = index; + var frame = this.piskelController.getFrameAt(this.currentIndex); + this.renderer.render(frame); + this.popupPreviewController.render(frame); + this.renderFlag = false; } }; - ns.AnimatedPreviewController.prototype._renderSelectedFrame = function (delta) { - // the selected frame is the currentFrame from the PiskelController perspective - var selectedFrameIndex = this.piskelController.getCurrentFrameIndex(); - var selectedFrame = this.piskelController.getFrameAt(selectedFrameIndex); - this.renderer.render(selectedFrame); - }; - - ns.AnimatedPreviewController.prototype._renderCurrentAnimationFrame = function (delta) { - var index = Math.floor(this.elapsedTime / (1000/this.fps)); - if (index != this.currentIndex) { - this.currentIndex = index; - if (!this.piskelController.hasFrameAt(this.currentIndex)) { - this.currentIndex = 0; + ns.AnimatedPreviewController.prototype.getNextIndex_ = function (delta) { + if (this.fps === 0) { + return this.piskelController.getCurrentFrameIndex(); + } else { + var index = Math.floor(this.elapsedTime / (1000/this.fps)); + if (!this.piskelController.hasFrameAt(index)) { this.elapsedTime = 0; + index = 0; } - var frame = this.piskelController.getFrameAt(this.currentIndex); - this.renderer.render(frame); + return index; } }; diff --git a/src/js/controller/piskel/PiskelController.js b/src/js/controller/piskel/PiskelController.js index d4eb301e..cd9f344d 100644 --- a/src/js/controller/piskel/PiskelController.js +++ b/src/js/controller/piskel/PiskelController.js @@ -259,6 +259,11 @@ this.selectLayer(layer); }; + ns.PiskelController.prototype.removeCurrentLayer = function () { + var currentLayerIndex = this.getCurrentLayerIndex(); + this.removeLayerAt(currentLayerIndex); + }; + ns.PiskelController.prototype.removeLayerAt = function (index) { if (this.getLayers().length > 1) { var layer = this.getLayerAt(index); diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js index e5da46bf..2778683a 100644 --- a/src/js/controller/piskel/PublicPiskelController.js +++ b/src/js/controller/piskel/PublicPiskelController.js @@ -7,6 +7,29 @@ }; ns.PublicPiskelController.prototype.init = function () { + // DECORATED WITH RESET + this.resetWrap_('setCurrentFrameIndex'); + this.resetWrap_('selectNextFrame'); + this.resetWrap_('selectPreviousFrame'); + this.resetWrap_('setCurrentLayerIndex'); + this.resetWrap_('selectLayer'); + // DECORATED WITH SAVE, NO RESET + this.saveWrap_('renameLayerAt', false); + // DECORATED WITH SAVE, WITH RESET + this.saveWrap_('removeCurrentLayer', true); + this.saveWrap_('addFrame', true); + this.saveWrap_('addFrameAtCurrentIndex', true); + this.saveWrap_('addFrameAt', true); + this.saveWrap_('removeFrameAt', true); + this.saveWrap_('duplicateCurrentFrame', true); + this.saveWrap_('duplicateFrameAt', true); + this.saveWrap_('moveFrame', true); + this.saveWrap_('createLayer', true); + this.saveWrap_('mergeDownLayerAt', true); + this.saveWrap_('moveLayerUp', true); + this.saveWrap_('moveLayerDown', true); + this.saveWrap_('removeCurrentLayer', true); + pskl.app.shortcutService.addShortcut('up', this.selectPreviousFrame.bind(this)); pskl.app.shortcutService.addShortcut('down', this.selectNextFrame.bind(this)); pskl.app.shortcutService.addShortcut('n', this.addFrameAtCurrentIndex.bind(this)); @@ -23,128 +46,47 @@ }); }; - ns.PublicPiskelController.prototype.addFrame = function () { - this.addFrameAt(this.getFrameCount()); + ns.PublicPiskelController.prototype.resetWrap_ = function (methodName) { + this[methodName] = function () { + this.piskelController[methodName].apply(this.piskelController, arguments); + $.publish(Events.PISKEL_RESET); + }; }; - ns.PublicPiskelController.prototype.addFrameAtCurrentIndex = function () { - this.addFrameAt(this.getCurrentFrameIndex()); + ns.PublicPiskelController.prototype.saveWrap_ = function (methodName, reset) { + this[methodName] = reset ? function () { + var stateInfo = this.getStateInfo_(); + this.piskelController[methodName].apply(this.piskelController, arguments); + this.raiseSaveStateEvent_(this.piskelController[methodName], arguments, stateInfo); + $.publish(Events.PISKEL_RESET); + } : function () { + var stateInfo = this.getStateInfo_(); + this.piskelController[methodName].apply(this.piskelController, arguments); + this.raiseSaveStateEvent_(this.piskelController[methodName], arguments, stateInfo); + }; }; - ns.PublicPiskelController.prototype.addFrameAt = function (index) { - this.raiseSaveStateEvent_(this.piskelController.addFrameAt, [index]); - this.piskelController.addFrameAt(index); - $.publish(Events.PISKEL_RESET); + ns.PublicPiskelController.prototype.getStateInfo_ = function () { + var stateInfo = { + frameIndex : this.piskelController.currentFrameIndex, + layerIndex : this.piskelController.currentLayerIndex + }; + return stateInfo; }; - 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.duplicateFrameAt(this.getCurrentFrameIndex()); + ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args, stateInfo) { + $.publish(Events.PISKEL_SAVE_STATE, { + type : pskl.service.HistoryService.REPLAY, + scope : this, + replay : { + fn : fn, + args : args + }, + state : stateInfo + }); }; 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.mergeDownLayerAt = function (index) { - this.raiseSaveStateEvent_(this.piskelController.mergeDownLayerAt, [index]); - this.piskelController.mergeDownLayerAt(index); - $.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 () { - var currentLayerIndex = this.getCurrentLayerIndex(); - this.raiseSaveStateEvent_(this.piskelController.removeLayerAt, [currentLayerIndex]); - this.piskelController.removeLayerAt(currentLayerIndex); - $.publish(Events.PISKEL_RESET); - }; - - ns.PublicPiskelController.prototype.getCurrentLayerIndex = function () { - return this.piskelController.getCurrentLayerIndex(); - }; - - ns.PublicPiskelController.prototype.getCurrentFrameIndex = function () { - return this.piskelController.currentFrameIndex; - }; - - ns.PublicPiskelController.prototype.getPiskel = function () { - return this.piskelController.piskel; - }; - - ns.PublicPiskelController.prototype.raiseSaveStateEvent_ = function (fn, args) { - $.publish(Events.PISKEL_SAVE_STATE, { - type : pskl.service.HistoryService.REPLAY_NO_SNAPSHOT, - scope : this, - replay : { - fn : fn, - args : args - } - }); - }; - })(); \ No newline at end of file diff --git a/src/js/controller/settings/AbstractSettingController.js b/src/js/controller/settings/AbstractSettingController.js index 980761e2..cfe5e1e7 100644 --- a/src/js/controller/settings/AbstractSettingController.js +++ b/src/js/controller/settings/AbstractSettingController.js @@ -3,9 +3,6 @@ ns.AbstractSettingController = function () {}; ns.AbstractSettingController.prototype.addEventListener = function (el, type, callback) { - if (typeof el === 'string') { - el = document.querySelector(el); - } pskl.utils.Event.addEventListener(el, type, callback, this); }; diff --git a/src/js/service/CurrentColorsService.js b/src/js/service/CurrentColorsService.js index 11a02536..fa953224 100644 --- a/src/js/service/CurrentColorsService.js +++ b/src/js/service/CurrentColorsService.js @@ -3,7 +3,10 @@ ns.CurrentColorsService = function (piskelController) { this.piskelController = piskelController; + // cache of current colors by history state + this.cache = {}; this.currentColors = []; + this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor(); this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this)); @@ -12,9 +15,8 @@ }; ns.CurrentColorsService.prototype.init = function () { - $.subscribe(Events.PISKEL_RESET, this.onPiskelUpdated_.bind(this)); - $.subscribe(Events.TOOL_RELEASED, this.onPiskelUpdated_.bind(this)); - $.subscribe(Events.USER_SETTINGS_CHANGED, this.onUserSettingsChange_.bind(this)); + $.subscribe(Events.HISTORY_STATE_SAVED, this.updateCurrentColors_.bind(this)); + $.subscribe(Events.HISTORY_STATE_LOADED, this.loadColorsFromCache_.bind(this)); }; ns.CurrentColorsService.prototype.getCurrentColors = function () { @@ -22,26 +24,14 @@ }; ns.CurrentColorsService.prototype.setCurrentColors = function (colors) { + var historyIndex = pskl.app.historyService.currentIndex; + this.cache[historyIndex] = colors; if (colors.join('') !== this.currentColors.join('')) { this.currentColors = colors; $.publish(Events.CURRENT_COLORS_UPDATED); } }; - ns.CurrentColorsService.prototype.onUserSettingsChange_ = function (evt, name, value) { - if (name == pskl.UserSettings.SELECTED_PALETTE) { - if (this.isCurrentColorsPaletteSelected_()) { - this.updateCurrentColors_(); - } - } - }; - - ns.CurrentColorsService.prototype.onPiskelUpdated_ = function (evt) { - if (this.isCurrentColorsPaletteSelected_()) { - this.updateCurrentColors_(); - } - }; - ns.CurrentColorsService.prototype.isCurrentColorsPaletteSelected_ = function () { var paletteId = pskl.UserSettings.get(pskl.UserSettings.SELECTED_PALETTE); var palette = this.paletteService.getPaletteById(paletteId); @@ -49,6 +39,14 @@ return palette.id === Constants.CURRENT_COLORS_PALETTE_ID; }; + ns.CurrentColorsService.prototype.loadColorsFromCache_ = function () { + var historyIndex = pskl.app.historyService.currentIndex; + var colors = this.cache[historyIndex]; + if (colors) { + this.setCurrentColors(colors); + } + }; + ns.CurrentColorsService.prototype.updateCurrentColors_ = function () { var layers = this.piskelController.getLayers(); var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);}); diff --git a/src/js/service/HistoryService.js b/src/js/service/HistoryService.js index 052056ff..611e8709 100644 --- a/src/js/service/HistoryService.js +++ b/src/js/service/HistoryService.js @@ -10,19 +10,19 @@ this.currentIndex = -1; this.lastLoadState = -1; - - this.saveNextAsSnapshot = false; }; + // Force to save a state as a SNAPSHOT ns.HistoryService.SNAPSHOT = 'SNAPSHOT'; + + // Default save state ns.HistoryService.REPLAY = 'REPLAY'; + + // Period (in number of state saved) between two snapshots ns.HistoryService.SNAPSHOT_PERIOD = 50; + + // Interval/buffer (in milliseconds) between two state load (ctrl+z/y spamming) ns.HistoryService.LOAD_STATE_INTERVAL = 50; - /** - * This event alters the state (frames, layers) of the piskel. The event is triggered before the execution of associated command. - * Don't store snapshots for such events. - */ - ns.HistoryService.REPLAY_NO_SNAPSHOT = 'REPLAY_NO_SNAPSHOT'; ns.HistoryService.prototype.init = function () { $.subscribe(Events.PISKEL_SAVE_STATE, this.onSaveStateEvent.bind(this)); @@ -35,31 +35,28 @@ }); }; - ns.HistoryService.prototype.onSaveStateEvent = function (evt, stateInfo) { - this.saveState(stateInfo); + ns.HistoryService.prototype.onSaveStateEvent = function (evt, action) { + this.saveState(action); }; - ns.HistoryService.prototype.saveState = function (stateInfo) { + ns.HistoryService.prototype.saveState = function (action) { this.stateQueue = this.stateQueue.slice(0, this.currentIndex + 1); this.currentIndex = this.currentIndex + 1; var state = { - action : stateInfo, - frameIndex : this.piskelController.currentFrameIndex, - layerIndex : this.piskelController.currentLayerIndex + action : action, + frameIndex : action.state ? action.state.frameIndex : this.piskelController.currentFrameIndex, + layerIndex : action.state ? action.state.layerIndex : this.piskelController.currentLayerIndex }; - var isSnapshot = stateInfo.type === ns.HistoryService.SNAPSHOT; - var isNoSnapshot = stateInfo.type === ns.HistoryService.REPLAY_NO_SNAPSHOT; - var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0 || this.saveNextAsSnapshot; - if (isNoSnapshot && isAtAutoSnapshotInterval) { - this.saveNextAsSnapshot = true; - } else if (isSnapshot || isAtAutoSnapshotInterval) { + var isSnapshot = action.type === ns.HistoryService.SNAPSHOT; + var isAtAutoSnapshotInterval = this.currentIndex % ns.HistoryService.SNAPSHOT_PERIOD === 0; + if (isSnapshot || isAtAutoSnapshotInterval) { state.piskel = this.piskelController.serialize(true); - this.saveNextAsSnapshot = false; } this.stateQueue.push(state); + $.publish(Events.HISTORY_STATE_SAVED); }; ns.HistoryService.prototype.undo = function () { @@ -143,6 +140,7 @@ this.currentIndex = index; $.publish(Events.PISKEL_RESET); + $.publish(Events.HISTORY_STATE_LOADED); if (originalSize !== this.getPiskelSize_()) { $.publish(Events.FRAME_SIZE_CHANGED); } diff --git a/src/js/utils/Event.js b/src/js/utils/Event.js index 71765701..2b58d04b 100644 --- a/src/js/utils/Event.js +++ b/src/js/utils/Event.js @@ -4,6 +4,10 @@ ns.Event = {}; ns.Event.addEventListener = function (el, type, callback, scope, args) { + if (typeof el === 'string') { + el = document.querySelector(el); + } + var listener = { el : el, type : type, diff --git a/src/piskel-script-list.js b/src/piskel-script-list.js index 8bdaa642..1cdc1bcc 100644 --- a/src/piskel-script-list.js +++ b/src/piskel-script-list.js @@ -85,6 +85,7 @@ "js/controller/drawing/DragHandler.js", "js/controller/PreviewFilmController.js", "js/controller/LayersListController.js", + "js/controller/PopupPreviewController.js", "js/controller/AnimatedPreviewController.js", "js/controller/MinimapController.js", "js/controller/ToolController.js", diff --git a/src/templates/misc-templates.html b/src/templates/misc-templates.html index 56d5a0b7..ebeb8916 100644 --- a/src/templates/misc-templates.html +++ b/src/templates/misc-templates.html @@ -6,4 +6,28 @@
{{status}}%
+ + + \ No newline at end of file diff --git a/src/templates/preview.html b/src/templates/preview.html index 7c01cca4..ec2c8ae2 100644 --- a/src/templates/preview.html +++ b/src/templates/preview.html @@ -12,4 +12,7 @@ +
+ +
\ No newline at end of file diff --git a/test/js/service/HistoryServiceTest.js b/test/js/service/HistoryServiceTest.js index 15827bb4..66d293db 100644 --- a/test/js/service/HistoryServiceTest.js +++ b/test/js/service/HistoryServiceTest.js @@ -69,11 +69,6 @@ describe("History Service suite", function() { expect(getLastState().piskel).toBe(SERIALIZED_PISKEL); sendSaveEvents(pskl.service.HistoryService.REPLAY).times(4); - - sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); - expect(getLastState().piskel).toBeUndefined(); - - sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once(); expect(getLastState().piskel).toBeUndefined(); sendSaveEvents(pskl.service.HistoryService.REPLAY).once(); @@ -82,5 +77,5 @@ describe("History Service suite", function() { // AFTER pskl.service.HistoryService.SNAPSHOT_PERIOD = SNAPSHOT_PERIOD_BACKUP; - }) + }); }); \ No newline at end of file