diff --git a/README.md b/README.md index 7a87fff7..989e8723 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,24 @@ The goal is to create an easy-to-use/in-the-cloud/web-based 2d animation editor. Try it at : http://juliandescottes.github.com/piskel/ -v0.0something (with 2 pull requests from grosbouddha) +v0.0almostthere +------------------------------------ +**30 Aug 2012** : Many new features in 2 days : +* __save animations__, they are persisted in the cloud, and can be retrieved via a __unique URL__ +* __color picker__, no longer limited to black and white +* __local storage__, your work is automatically backed up locally +* __color palette__, listing all the colors already used in the animation +* __slider__ for choosing the speed of the preview + +UI was slightly updated : + +![Screenshot 2](https://dl.dropbox.com/u/17803671/screen_piskel_2.png "Screenshot 2") + +v0.0something ------------------------------------ **28 Aug 2012** : Thanks to grosbouddha, new features added to Piskel : * modify preview speed ! -* remove (shitty) frames +* remove frames * transparent background v0.0whatever (aka the thing I did last night) diff --git a/css/style.css b/css/style.css index d9011854..8ef86e8d 100644 --- a/css/style.css +++ b/css/style.css @@ -152,7 +152,8 @@ ul, li { border: #F0C36D 1px solid; border-bottom: 0; font-weight: bold; - font-size: 14px; + font-size: 14px; + z-index: 10000; } /* Force apparition of scrollbars on leopard */ diff --git a/js/piskel.js b/js/piskel.js index 350456fa..1064ecf8 100644 --- a/js/piskel.js +++ b/js/piskel.js @@ -55,6 +55,7 @@ var frameId = this.getFrameIdFromUrl(); if (frameId) { + this.displayMessage("Loading animation with id : [" + frameId + "]"); this.loadFramesheetFromService(frameId); } else { this.finishInit(); @@ -86,10 +87,12 @@ xhr.onload = function(e) { frameSheet.deserialize(this.responseText); + piskel.removeMessage(); piskel.finishInit(); }; xhr.onerror = function () { + piskel.removeMessage(); piskel.finishInit(); }; @@ -98,16 +101,25 @@ initLocalStorageBackup: function() { if(window.localStorage && window.localStorage['snapShot']) { - var message = document.createElement('div'); - message.id = "user-message"; - message.className = "user-message"; var reloadLink = "reload"; var discardLink = "discard"; - message.innerHTML = "Non saved version found. " + reloadLink + " or " + discardLink; - message.onclick = function() { - message.parentNode.removeChild(message); - }; - document.body.appendChild(message); + this.displayMessage("Non saved version found. " + reloadLink + " or " + discardLink); + } + }, + + displayMessage : function (content) { + var message = document.createElement('div'); + message.id = "user-message"; + message.className = "user-message"; + message.innerHTML = content; + message.onclick = this.removeMessage; + document.body.appendChild(message); + }, + + removeMessage : function () { + var message = $("user-message"); + if (message) { + message.parentNode.removeChild(message); } }, diff --git a/preview/css/piskel.css b/preview/css/piskel.css deleted file mode 100644 index c384327b..00000000 --- a/preview/css/piskel.css +++ /dev/null @@ -1,36 +0,0 @@ -html, body { - height : 100%; -} - -.debug { - border : 1px Solid black; -} - -.left-nav { - position:absolute; - top : 0; - bottom : 0; - width : 200px; - background : #000; -} - -.main-panel { - position:absolute; - top : 0; - bottom : 0; - left : 200px; - right : 0; - background : #ccc; -} - -.preview-container { - position : absolute; - top : 30px; - right : 0; - height : 200px; - width : 200px; - background : white; - border : 0px Solid black; - border-radius:5px 0px 0px 5px; - box-shadow : 0px 0px 2px rgba(0,0,0,0.2); -} \ No newline at end of file diff --git a/preview/css/style.css b/preview/css/style.css deleted file mode 100644 index 9136c5f0..00000000 --- a/preview/css/style.css +++ /dev/null @@ -1,140 +0,0 @@ -html, body { - height : 100%; - margin : 0; - cursor : default; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -ul, li { - margin : 0; - padding : 0; -} - -.debug { - border : 1px Solid black; -} - -.left-nav { - position:absolute; - top : 0; - bottom : 0; - width : 200px; - overflow-y: scroll; - background : #000; - padding : 10px; -} - -.main-panel { - position:absolute; - top : 0; - bottom : 0; - left : 220px; - right : 0; - background : #ccc; -} - -.preview-container { - position : absolute; - top : 30px; - right : 0; - height : 256px; - width : 256px; - background : white; - border : 0px Solid black; - border-radius:5px 0px 0px 5px; - box-shadow : 0px 0px 2px rgba(0,0,0,0.2); -} - -.preview-container canvas { - border : 0px Solid transparent; - border-radius:5px 0px 0px 5px; -} - -#cursorInfo { - position : fixed; - cursor : default; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.action-button { - background-color : white; - width : 150px; - display : inline-block; -} - -#preview-list { - list-style-type: none; -} - -.preview-tile { - padding : 10px; - overflow: hidden; - background-color: gray; -} - -.preview-tile .canvas-container { - float: left; -} - -.preview-tile .tile-view { - float: left; - border: blue 1px solid; -} - -.preview-tile .tile-action { - display: none; - float: right; -} - -.preview-tile:hover .tile-action { - display: block; -} - -.preview-tile:hover { - background-color: lightgray; -} - -#preview-list .preview-tile.selected { - background-color: lightyellow; -} - -.canvas-container { - position: relative; - display: block; -} - -.canvas-container .canvas-background { - background: url(../img/transparent_background.png) repeat; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; -} - -.canvas { - position: relative; - z-index: 1; -} - -/* Force apparition of scrollbars on leopard */ -::-webkit-scrollbar { - -webkit-appearance: none; - width: 7px; -} - -::-webkit-scrollbar-thumb { - border-radius: 4px; - background-color: rgba(180,180,180,.7); - -webkit-box-shadow: 0 0 1px rgba(255,255,255,.5); -} \ No newline at end of file diff --git a/preview/img/transparent_background.png b/preview/img/transparent_background.png deleted file mode 100644 index 4a1f7a8e..00000000 Binary files a/preview/img/transparent_background.png and /dev/null differ diff --git a/preview/index.html b/preview/index.html deleted file mode 100644 index c08fa6e8..00000000 --- a/preview/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - Piskel - - - - - - - - - -
- Get URL - - -
-
-
-
-
-
-
-
-
-
- -
-
-
-
- - - - diff --git a/preview/js/app.js b/preview/js/app.js deleted file mode 100644 index d66a847f..00000000 --- a/preview/js/app.js +++ /dev/null @@ -1,35 +0,0 @@ -// Generated by CoffeeScript 1.3.1 -(function() { - - $(document).ready(function() { - var Piskel; - Piskel = Em.Application.create(); - Piskel.mainView = Em.View.create({ - templateName: 'main', - onCanvasClick: function() {} - }); - Piskel.mainView.append; - Piskel.Art = Ember.Object.extend({ - setContext: function(context) { - this.context = context; - }, - clear: function() { - this.context.save(); - return this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height); - } - }); - $('#main').ready(function() { - var context, mainCanvas; - mainCanvas = document.getElementById('main'); - context = mainCanvas.getContext('2d'); - context.save(); - context.clearRect(0, 0, context.canvas.width, context.canvas.height); - context.fillStyle = 'white'; - context.fillRect(0, 0, context.canvas.width, context.canvas.height); - context.restore(); - return Piskel.mainCanvas = document.getElementById('main'); - }); - return window.Piskel = Piskel; - }); - -}).call(this); diff --git a/preview/js/frameSheetModel.js b/preview/js/frameSheetModel.js deleted file mode 100644 index d4d2712b..00000000 --- a/preview/js/frameSheetModel.js +++ /dev/null @@ -1,101 +0,0 @@ -var FrameSheetModel = (function() { - - var inst; - var frames = []; - var width; - var height; - - var createEmptyFrame_ = function() { - var emptyFrame = new Array(width); - for (var columnIndex=0; columnIndex < width; columnIndex++) { - emptyFrame[columnIndex] = new Array(height); - } - return emptyFrame; - }; - - var serializeFrame_ = function (frame) { - var buffer = []; - for (var i = 0 ; i < frame.length ; i++) { - var serializedLine = ""; - for(var j = 0 ; j < frame[i].length ; j++) { - if (typeof frame[i][j] == 'undefined' || frame[i][j] == 'tc') { - serializedLine += "0" - } else { - serializedLine += "1" - } - } - buffer.push(parseInt(serializedLine, 2).toString(36)); - - } - return buffer.join(","); - }; - - return { - validate: function() { - return true; // I'm always right dude - }, - - // Could be use to pass around model using long GET param (good enough for simple models) and - // do some temporary locastorage - serialize: function() { - var buffer = []; - for (var i = 0 ; i < frames.length ; i++) { - buffer.push(serializeFrame_(frames[i])); - } - return buffer.join("+"); - //throw "FrameSheet.serialize Not implemented" - }, - - addEmptyFrame: function() { - this.addFrame(createEmptyFrame_()); - }, - - addFrame : function (frame) { - frames.push(frame); - }, - - getFrameCount: function() { - return frames.length; - }, - - getFrameByIndex: function(index) { - if (isNaN(index)) { - throw "Bad argument value for getFrameByIndex method: <" + index + ">" - } else if (index < 0 || index > frames.length) { - throw "Out of bound index for frameSheet object." - } - - return frames[index]; - }, - - removeFrameByIndex: function(index) { - if(index < 0 || index > inst.getFrameCount()) { - throw "Bad index value for removeFrameByIndex."; - } - frames.splice(index, 1); - }, - - duplicateFrameByIndex: function(frameToDuplicateIndex) { - var frame = inst.getFrameByIndex(frameToDuplicateIndex); - var clonedFrame = []; - for(var i=0, l=frame.length; i 100) { - animPreviewFPS = 100; - } - animFPSTuner.value = animPreviewFPS; - refreshUpdater = startPreviewRefresh(); - }); - }, - - createPreviews : function () { - var container = $('preview-list'), previewTile; - container.innerHTML = ""; - for (var i = 0, l = frameSheet.getFrameCount(); i < l ; i++) { - previewTile = this.createPreviewTile(i); - container.appendChild(previewTile); - } - }, - - createPreviewTile: function(tileNumber) { - var previewTileRoot = document.createElement("li"); - var classname = "preview-tile"; - - if (this.getActiveFrameIndex() == tileNumber) { - classname += " selected"; - } - previewTileRoot.className = classname; - - var canvasContainer = document.createElement("div"); - canvasContainer.className = "canvas-container"; - canvasContainer.setAttribute('style', - 'width:' + framePixelWidth * previewTileCanvasDpi + 'px; height:' + framePixelHeight * previewTileCanvasDpi + 'px;'); - - var canvasBackground = document.createElement("div"); - canvasBackground.className = "canvas-background"; - canvasContainer.appendChild(canvasBackground); - - var canvasPreview = document.createElement("canvas"); - canvasPreview.className = "canvas tile-view" - - canvasPreview.setAttribute('width', framePixelWidth * previewTileCanvasDpi); - canvasPreview.setAttribute('height', framePixelHeight * previewTileCanvasDpi); - - previewTileRoot.addEventListener('click', function(evt) { - // has not class tile-action: - // TODO: let me know when you want to start using a framework :) - if(!evt.target.className.match(new RegExp('(\\s|^)'+ 'tile-action' +'(\\s|$)'))) { - piskel.setActiveFrameAndRedraw(tileNumber); - } - }); - - var canvasPreviewDuplicateAction = document.createElement("button"); - canvasPreviewDuplicateAction.className = "tile-action" - canvasPreviewDuplicateAction.innerHTML = "dup" - - canvasPreviewDuplicateAction.addEventListener('click', function(evt) { - piskel.duplicateFrame(tileNumber); - }); - - this.drawFrameToCanvas(frameSheet.getFrameByIndex(tileNumber), canvasPreview, previewTileCanvasDpi); - canvasContainer.appendChild(canvasPreview); - previewTileRoot.appendChild(canvasContainer); - previewTileRoot.appendChild(canvasPreviewDuplicateAction); - - if(tileNumber > 0 || frameSheet.getFrameCount() > 1) { - var canvasPreviewDeleteAction = document.createElement("button"); - canvasPreviewDeleteAction.className = "tile-action" - canvasPreviewDeleteAction.innerHTML = "del" - canvasPreviewDeleteAction.addEventListener('click', function(evt) { - frameSheet.removeFrameByIndex(tileNumber); - animIndex = 0; - piskel.createPreviews(); - }); - previewTileRoot.appendChild(canvasPreviewDeleteAction); - } - - return previewTileRoot; - }, - - refreshAnimatedPreview : function () { - piskel.drawFrameToCanvas(frameSheet.getFrameByIndex(animIndex), previewCanvas, previewAnimationCanvasDpi); - animIndex++; - if (animIndex == frameSheet.getFrameCount()) { - animIndex = 0; - } - }, - - removeFrame: function(frameIndex) { - frameSheet.removeFrameByIndex(frameIndex); - - this.setActiveFrameAndRedraw(frameIndex - 1); - }, - - duplicateFrame: function(frameIndex) { - frameSheet.duplicateFrameByIndex(frameIndex); - - this.setActiveFrameAndRedraw(frameIndex + 1); - }, - - updateCursorInfo : function (event) { - var cursor = $('cursorInfo'); - cursor.style.top = event.clientY + 10 + "px"; - cursor.style.left = event.clientX + 10 + "px"; - - var coordinates = this.getRelativeCoordinates(event.clientX, event.clientY) - cursor.innerHTML = [ - "X : " + coordinates.x, - "Y : " + coordinates.y - ].join(", "); - }, - - onCanvasMousedown : function (event) { - isClicked = true; - var coords = this.getRelativeCoordinates(event.clientX, event.clientY); - if(event.button == 0) { - this.drawAt(coords.x, coords.y, penColor); - } else { - // Right click used to delete. - isRightClicked = true; - this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR); - } - }, - - onCanvasMousemove : function (event) { - //this.updateCursorInfo(event); - if (isClicked) { - var coords = this.getRelativeCoordinates(event.clientX, event.clientY); - if(isRightClicked) { - this.drawAt(coords.x, coords.y, TRANSPARENT_COLOR); - } else { - this.drawAt(coords.x, coords.y, penColor); - } - } - }, - - onCanvasMouseup : function (event) { - if(isClicked || isRightClicked) { - // A mouse button was clicked on the drawing canvas before this mouseup event, - // the user was probably drawing on the canvas. - // Note: The mousemove movement (and the mouseup) may end up outside - // of the drawing canvas. - this.createPreviews(); - } - isClicked = false; - isRightClicked = false; - }, - - drawAt : function (x, y, color) { - var pixelWidthIndex = (x - x%drawingCanvasDpi) / 10; - var pixelHeightIndex = (y - y%drawingCanvasDpi) / 10; - - // Update model: - var currentFrame = frameSheet.getFrameByIndex(this.getActiveFrameIndex()); - - // TODO: make a better accessor for pixel state update: - // TODO: Make pen color dynamic: - currentFrame[pixelWidthIndex][pixelHeightIndex] = color; - - // Update view: - // TODO: Create a per pixel update function for perf ? - this.drawFrameToCanvas(currentFrame, drawingAreaCanvas, drawingCanvasDpi); - }, - - // TODO: move that to a FrameRenderer (/w cache) ? - drawFrameToCanvas: function(frame, canvasElement, dpi) { - var pixelColor, context = canvasElement.getContext('2d'); - for(var col = 0, num_col = frame.length; col < num_col; col++) { - for(var row = 0, num_row = frame[col].length; row < num_row; row++) { - pixelColor = frame[col][row]; - - if(pixelColor == undefined || pixelColor == TRANSPARENT_COLOR) { - context.clearRect(col * dpi, row * dpi, dpi, dpi); - } else { - context.fillStyle = pixelColor; - context.fillRect(col * dpi, row * dpi, dpi, dpi); - } - - - } - } - }, - - onCanvasContextMenu : function (event) { - event.preventDefault(); - event.stopPropagation(); - event.cancelBubble = true; - return false; - }, - - getRelativeCoordinates : function (x, y) { - var canvasRect = drawingAreaCanvas.getBoundingClientRect(); - return { - x : x - canvasRect.left, - y : y - canvasRect.top - } - } - }; - - window.piskel = piskel; - piskel.init(); - -})(function(id){return document.getElementById(id)}); \ No newline at end of file