diff --git a/src/css/frames-list.css b/src/css/frames-list.css index 6658029b..08b7a689 100644 --- a/src/css/frames-list.css +++ b/src/css/frames-list.css @@ -136,7 +136,8 @@ } .preview-tile .tile-overlay.tile-count.toggled { - background-color: rgba(111, 215, 0, 0.6); + background-color: gold; + color: black; } .preview-tile .tile-overlay.delete-frame-action { diff --git a/src/js/controller/FramesListController.js b/src/js/controller/FramesListController.js index 34e05ccb..88eaad4f 100644 --- a/src/js/controller/FramesListController.js +++ b/src/js/controller/FramesListController.js @@ -116,8 +116,7 @@ this.previewList.insertBefore(newtile, this.addFrameTile); this.updateScrollerOverflows(); } else if (action == ACTION.TOGGLE) { - var frame = this.piskelController.getCurrentLayer().getFrameAt(index); - frame.toggled = !frame.toggled; + this.piskelController.toggleFrameAt(index); } this.flagForRedraw_(); @@ -138,9 +137,8 @@ this.tiles[i].setAttribute('data-tile-number', i); this.tiles[i].querySelector('.tile-count').innerHTML = (i + 1); - // Update toggle - var frame = this.piskelController.getCurrentLayer().getFrameAt(i); - if (frame.toggled) { + // Update visibility + if (this.piskelController.hasVisibleFrameAt(i)) { this.tiles[i].querySelector('.tile-count').classList.add('toggled'); } diff --git a/src/js/controller/piskel/PiskelController.js b/src/js/controller/piskel/PiskelController.js index 851f835c..a49b9ce3 100644 --- a/src/js/controller/piskel/PiskelController.js +++ b/src/js/controller/piskel/PiskelController.js @@ -152,31 +152,35 @@ this.setCurrentFrameIndex(index + 1); }; + ns.PiskelController.prototype.toggleFrameAt = function (index) { + this.getLayers().forEach(function (l) { + l.toggleFrameAt(index); + }); + }; + ns.PiskelController.prototype.moveFrame = function (fromIndex, toIndex) { this.getLayers().forEach(function (l) { l.moveFrame(fromIndex, toIndex); }); }; - ns.PiskelController.prototype.getFrames = function () { - var layer = this.getCurrentLayer(); - return layer.getFrames(); + ns.PiskelController.prototype.hasVisibleFrameAt = function (index) { + var frame = this.getCurrentLayer().getFrameAt(index); + return frame ? frame.isVisible() : false; }; - ns.PiskelController.prototype.getToggledFrameIndexes = function () { - var frameIndexes = this.getFrames() + ns.PiskelController.prototype.getVisibleFrameIndexes = function () { + var frameIndexes = this.getCurrentLayer().getFrames() /* Replace each frame with its index - or -1 if it's not toggled */ + or -1 if it's not visible */ .map( function(frame, idx) { - var idx; - idx += 1; - return (frame.toggled) ? idx - 1 : -1; + return (frame.visible) ? idx : -1; }) - /* Filter out untoggled frames */ + /* Filter out invisible frames */ .filter( - function(frame) { - return frame >= 0; + function(index) { + return index >= 0; }); return frameIndexes; }; diff --git a/src/js/controller/piskel/PublicPiskelController.js b/src/js/controller/piskel/PublicPiskelController.js index f18f54c1..2c371ad8 100644 --- a/src/js/controller/piskel/PublicPiskelController.js +++ b/src/js/controller/piskel/PublicPiskelController.js @@ -37,6 +37,7 @@ this.saveWrap_('moveLayerDown', true); this.saveWrap_('removeCurrentLayer', true); this.saveWrap_('setLayerOpacityAt', true); + this.saveWrap_('toggleFrameAt', true); var shortcuts = pskl.service.keyboard.Shortcuts; pskl.app.shortcutService.registerShortcut(shortcuts.MISC.PREVIOUS_FRAME, this.selectPreviousFrame.bind(this)); diff --git a/src/js/controller/preview/PreviewController.js b/src/js/controller/preview/PreviewController.js index 3c8cc7e3..feee03a5 100644 --- a/src/js/controller/preview/PreviewController.js +++ b/src/js/controller/preview/PreviewController.js @@ -281,7 +281,7 @@ return this.piskelController.getCurrentFrameIndex(); } else { var index = Math.floor(this.elapsedTime / (1000 / this.fps)); - var frameIndexes = this.piskelController.getToggledFrameIndexes(); + var frameIndexes = this.piskelController.getVisibleFrameIndexes(); if (frameIndexes.length <= index) { this.elapsedTime = 0; index = (frameIndexes.length) ? frameIndexes[0] : this.piskelController.getCurrentFrameIndex(); diff --git a/src/js/model/Frame.js b/src/js/model/Frame.js index 6187b385..da57197b 100644 --- a/src/js/model/Frame.js +++ b/src/js/model/Frame.js @@ -9,7 +9,7 @@ this.version = 0; this.pixels = ns.Frame.createEmptyPixelGrid_(width, height); this.stateIndex = 0; - this.toggled = true; + this.visible = true; } else { throw 'Bad arguments in pskl.model.Frame constructor : ' + width + ', ' + height; } @@ -152,4 +152,12 @@ ns.Frame.prototype.isSameSize = function (otherFrame) { return this.getHeight() == otherFrame.getHeight() && this.getWidth() == otherFrame.getWidth(); }; + + ns.Frame.prototype.toggleVisibility = function () { + this.visible = !this.visible; + }; + + ns.Frame.prototype.isVisible = function () { + return this.visible; + }; })(); diff --git a/src/js/model/Layer.js b/src/js/model/Layer.js index 0dba5340..89888276 100644 --- a/src/js/model/Layer.js +++ b/src/js/model/Layer.js @@ -110,6 +110,15 @@ } }; + ns.Layer.prototype.toggleFrameAt = function (index) { + var frame = this.frames[index]; + if (frame) { + frame.toggleVisibility(); + } else { + console.error('Frame not found in toggleFrameAt (at %s)', index); + } + }; + ns.Layer.prototype.size = function () { return this.frames.length; }; diff --git a/src/js/utils/serialization/Deserializer.js b/src/js/utils/serialization/Deserializer.js index f8f532c6..bcb3c268 100644 --- a/src/js/utils/serialization/Deserializer.js +++ b/src/js/utils/serialization/Deserializer.js @@ -36,6 +36,7 @@ var descriptor = new pskl.model.piskel.Descriptor(name, description); this.piskel_ = new pskl.model.Piskel(piskelData.width, piskelData.height, piskelData.fps, descriptor); + this.hiddenFrames = piskelData.hiddenFrames || []; this.layersToLoad_ = piskelData.layers.length; piskelData.layers.forEach(this.deserializeLayer.bind(this)); @@ -73,7 +74,14 @@ image.src = chunk.base64PNG; return deferred.promise; })).then(function () { - frames.forEach(layer.addFrame.bind(layer)); + var hiddenFrames = this.hiddenFrames; + frames.forEach(function (frame) { + layer.addFrame(frame); + var currentIndex = layer.getFrames().length - 1; + if (hiddenFrames.indexOf(currentIndex) != -1) { + frame.visible = false; + } + }); this.layers_[index] = layer; this.onLayerLoaded_(); }.bind(this)).catch(function (error) { diff --git a/src/js/utils/serialization/Serializer.js b/src/js/utils/serialization/Serializer.js index 553e2ae2..5bf6ad5a 100644 --- a/src/js/utils/serialization/Serializer.js +++ b/src/js/utils/serialization/Serializer.js @@ -21,6 +21,16 @@ var serializedLayers = piskel.getLayers().map(function (l) { return pskl.utils.serialization.Serializer.serializeLayer(l); }); + var frames = pskl.app.piskelController.getLayerAt(0).getFrames(); + var hiddenFrames = frames.map(function (frame, index) { + if (frame.visible) { + return -1; + } + return index; + }).filter(function (frameIndex) { + return frameIndex !== -1; + }); + return JSON.stringify({ modelVersion : Constants.MODEL_VERSION, piskel : { @@ -29,7 +39,8 @@ fps : pskl.app.piskelController.getFPS(), height : piskel.getHeight(), width : piskel.getWidth(), - layers : serializedLayers + layers : serializedLayers, + hiddenFrames : hiddenFrames, } }); }, diff --git a/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js b/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js index 9a8f8d7a..9506e0c6 100644 --- a/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js +++ b/src/js/utils/serialization/arraybuffer/ArrayBufferDeserializer.js @@ -36,23 +36,36 @@ // Layers meta var layerCount = arr16[6]; + // Layers meta + var serializedHiddenFramesLength = arr16[7]; + + var currentIndex = 8; /********/ /* DATA */ /********/ // Descriptor name var descriptorName = ''; for (i = 0; i < descriptorNameLength; i++) { - descriptorName += String.fromCharCode(arr16[7 + i]); + descriptorName += String.fromCharCode(arr16[currentIndex + i]); } + currentIndex += descriptorNameLength; // Descriptor description var descriptorDescription = ''; for (i = 0; i < descriptorDescriptionLength; i++) { - descriptorDescription = String.fromCharCode(arr16[7 + descriptorNameLength + i]); + descriptorDescription = String.fromCharCode(arr16[8 + descriptorNameLength + i]); } + currentIndex += descriptorDescriptionLength; + + // Hidden frames + var serializedHiddenFrames = ''; + for (i = 0; i < serializedHiddenFramesLength; i++) { + serializedHiddenFrames = String.fromCharCode(arr16[8 + descriptorNameLength + i]); + } + var hiddenFrames = serializedHiddenFrames.split('-'); + currentIndex += serializedHiddenFramesLength; // Layers - var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; var layers = []; var layer; for (i = 0; i < layerCount; i++) { @@ -60,27 +73,27 @@ var frames = []; // Meta - var layerNameLength = arr16[layerStartIndex]; - var opacity = arr16[layerStartIndex + 1] / 65535; - var frameCount = arr16[layerStartIndex + 2]; - var dataUriLengthFirstHalf = arr16[layerStartIndex + 3]; - var dataUriLengthSecondHalf = arr16[layerStartIndex + 4]; + var layerNameLength = arr16[currentIndex]; + var opacity = arr16[currentIndex + 1] / 65535; + var frameCount = arr16[currentIndex + 2]; + var dataUriLengthFirstHalf = arr16[currentIndex + 3]; + var dataUriLengthSecondHalf = arr16[currentIndex + 4]; var dataUriLength = (dataUriLengthSecondHalf >>> 0) | (dataUriLengthFirstHalf << 16 >>> 0); // Name var layerName = ''; for (j = 0; j < layerNameLength; j++) { - layerName += String.fromCharCode(arr16[layerStartIndex + 5 + j]); + layerName += String.fromCharCode(arr16[currentIndex + 5 + j]); } // Data URI var dataUri = ''; for (j = 0; j < dataUriLength; j++) { - dataUri += String.fromCharCode(arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j]); + dataUri += String.fromCharCode(arr8[(currentIndex + 5 + layerNameLength) * 2 + j]); } dataUri = 'data:image/png;base64,' + dataUri; - layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); + currentIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); layer.name = layerName; layer.opacity = opacity; @@ -99,6 +112,10 @@ var frames = pskl.utils.FrameUtils.createFramesFromSpritesheet(this, layer.frameCount); frames.forEach(function (frame) { layer.model.addFrame(frame); + var currentIndex = layer.model.getFrames().length - 1; + if (hiddenFrames.indexOf(currentIndex) != -1) { + frame.visible = false; + } }); loadedLayers++; diff --git a/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js b/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js index d52c945f..0b3d249a 100644 --- a/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js +++ b/src/js/utils/serialization/arraybuffer/ArrayBufferSerializer.js @@ -101,7 +101,22 @@ framesData.push({uri: dataUri, length: dataUriLength}); } - var bytes = ns.ArrayBufferSerializer.calculateRequiredBytes(piskel, framesData); + var frames = pskl.app.piskelController.getLayerAt(0).getFrames(); + var hiddenFrames = frames.map(function (frame, index) { + if (frame.visible) { + return -1; + } + return index; + }).filter(function (frameIndex) { + return frameIndex !== -1; + }); + var serializedHiddenFrames = hiddenFrames.join('-'); + + var bytes = ns.ArrayBufferSerializer.calculateRequiredBytes( + piskel, + framesData, + serializedHiddenFrames + ); var buffer = new ArrayBuffer(bytes); var arr8 = new Uint8Array(buffer); @@ -130,21 +145,33 @@ // Layers meta arr16[6] = piskel.getLayers().length; + // Frames meta + arr16[7] = serializedHiddenFrames.length; + + var currentIndex = 8; + /********/ /* DATA */ /********/ // Descriptor name for (i = 0; i < descriptorNameLength; i++) { - arr16[7 + i] = descriptorName.charCodeAt(i); + arr16[currentIndex + i] = descriptorName.charCodeAt(i); } + currentIndex = currentIndex + descriptorNameLength; // Descriptor description for (i = 0; i < descriptorDescriptionLength; i++) { - arr16[7 + descriptorNameLength + i] = descriptorDescription.charCodeAt(i); + arr16[currentIndex + i] = descriptorDescription.charCodeAt(i); } + currentIndex = currentIndex + descriptorDescriptionLength; + + // Hidden frames + for (i = 0; i < serializedHiddenFrames.length; i++) { + arr16[currentIndex + i] = serializedHiddenFrames.charCodeAt(i); + } + currentIndex = currentIndex + serializedHiddenFrames.length; // Layers - var layerStartIndex = 7 + descriptorNameLength + descriptorDescriptionLength; for (i = 0, layers = piskel.getLayers(); i < layers.length; i++) { var layer = layers[i]; var frames = layer.getFrames(); @@ -158,23 +185,23 @@ dataUriLength = framesData[i].length; // Meta - arr16[layerStartIndex] = layerNameLength; - arr16[layerStartIndex + 1] = Math.floor(opacity * 65535); - arr16[layerStartIndex + 2] = frameCount; - arr16[layerStartIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16 - arr16[layerStartIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16 + arr16[currentIndex] = layerNameLength; + arr16[currentIndex + 1] = Math.floor(opacity * 65535); + arr16[currentIndex + 2] = frameCount; + arr16[currentIndex + 3] = ((dataUriLength & 0xffff0000) >> 16) >>> 0; // Upper 16 + arr16[currentIndex + 4] = ((dataUriLength & 0x0000ffff)) >>> 0; // Lower 16 // Name for (j = 0; j < layerNameLength; j++) { - arr16[layerStartIndex + 5 + j] = layerName.charCodeAt(j); + arr16[currentIndex + 5 + j] = layerName.charCodeAt(j); } // Data URI for (j = 0; j < dataUriLength; j++) { - arr8[(layerStartIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j); + arr8[(currentIndex + 5 + layerNameLength) * 2 + j] = dataUri.charCodeAt(j); } - layerStartIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); + currentIndex += Math.ceil(5 + layerNameLength + (dataUriLength / 2)); } return buffer;