diff --git a/index.html b/index.html index b0cb5181..d4cc28f7 100644 --- a/index.html +++ b/index.html @@ -96,6 +96,7 @@ + diff --git a/js/controller/AnimatedPreviewController.js b/js/controller/AnimatedPreviewController.js index c8b7f51a..ee1e7f1c 100644 --- a/js/controller/AnimatedPreviewController.js +++ b/js/controller/AnimatedPreviewController.js @@ -3,10 +3,13 @@ ns.AnimatedPreviewController = function (framesheet, container, dpi) { this.framesheet = framesheet; this.container = container; - this.animIndex = 0; + + this.elapsedTime = 0; + this.currentIndex = 0; this.fps = parseInt($("#preview-fps")[0].value, 10); - + this.deltaTime = 0; + this.previousTime = 0; var renderingOptions = { "dpi": dpi }; @@ -14,40 +17,24 @@ }; ns.AnimatedPreviewController.prototype.init = function () { - this.initDom(); - - this.renderer.init(this.framesheet.getFrameByIndex(this.animIndex)); - - this.startAnimationTimer(); - }; - - ns.AnimatedPreviewController.prototype.initDom = function () { $("#preview-fps")[0].addEventListener('change', this.onFPSSliderChange.bind(this)); }; - ns.AnimatedPreviewController.prototype.startAnimationTimer = function () { - this.stopAnimationTimer(); - this.animationTimer = window.setTimeout(this.refreshAnimatedPreview.bind(this), 1000/this.fps); - }; - - ns.AnimatedPreviewController.prototype.stopAnimationTimer = function () { - if (this.animationTimer) { - window.clearInterval(this.animationTimer); - this.animationTimer = null; - } - }; - ns.AnimatedPreviewController.prototype.onFPSSliderChange = function(evt) { this.fps = parseInt($("#preview-fps")[0].value, 10); }; - ns.AnimatedPreviewController.prototype.refreshAnimatedPreview = function () { - if (!this.framesheet.hasFrameAtIndex(this.animIndex)) { - this.animIndex = 0; + ns.AnimatedPreviewController.prototype.render = function (delta) { + this.elapsedTime += delta; + var index = Math.floor(this.elapsedTime / (1000/this.fps)); + if (index != this.currentIndex) { + this.currentIndex = index; + if (!this.framesheet.hasFrameAtIndex(this.currentIndex)) { + this.currentIndex = 0; + this.elapsedTime = 0; + } + this.renderer.render(this.framesheet.getFrameByIndex(this.currentIndex)); } - this.renderer.render(this.framesheet.getFrameByIndex(this.animIndex)); - this.animIndex++; - this.startAnimationTimer(); }; })(); \ No newline at end of file diff --git a/js/controller/DrawingController.js b/js/controller/DrawingController.js index 97997a9c..a9a32e3e 100644 --- a/js/controller/DrawingController.js +++ b/js/controller/DrawingController.js @@ -210,8 +210,22 @@ this.overlayRenderer.render(this.overlayFrame); }; + ns.DrawingController.prototype.render = function () { + try { + + this.renderFrame(); + this.renderOverlay(); + } catch (e) { + // TODO : temporary t/c for integration + } + }; + ns.DrawingController.prototype.renderFrame = function () { - this.renderer.render(this.frame); + var serializedFrame = this.frame.serialize(); + if (this.serializedFrame != serializedFrame) { + this.serializedFrame = serializedFrame + this.renderer.render(this.frame); + } }; ns.DrawingController.prototype.renderFramePixel = function (col, row) { @@ -219,7 +233,11 @@ }; ns.DrawingController.prototype.renderOverlay = function () { - this.overlayRenderer.render(this.overlayFrame); + var serializedOverlay = this.overlayFrame.serialize(); + if (this.serializedOverlay != serializedOverlay) { + this.serializedOverlay = serializedOverlay + this.renderer.render(this.overlayFrame); + } }; ns.DrawingController.prototype.clearOverlay = function () { diff --git a/js/controller/PreviewFilmController.js b/js/controller/PreviewFilmController.js index 7db43364..5ae04e03 100644 --- a/js/controller/PreviewFilmController.js +++ b/js/controller/PreviewFilmController.js @@ -6,40 +6,41 @@ this.framesheet = framesheet; this.container = container; + this.dirty = false; + $.subscribe(Events.REDRAW_PREVIEWFILM, $.proxy(function(evt) { - this.createPreviews() + this.dirty = true; }, this)); }; ns.PreviewFilmController.prototype.init = function() { - var addFrameButton = $('#add-frame-button')[0]; - addFrameButton.addEventListener('mousedown', this.addFrame.bind(this)); - this.createPreviews(); - - + var addFrameButton = $('#add-frame-button')[0]; + addFrameButton.addEventListener('mousedown', this.addFrame.bind(this)); }; ns.PreviewFilmController.prototype.addFrame = function () { this.framesheet.addEmptyFrame(); - piskel.setActiveFrameAndRedraw(this.framesheet.getFrameCount() - 1); + piskel.setActiveFrame(this.framesheet.getFrameCount() - 1); }; - ns.PreviewFilmController.prototype.createPreviews = function () { - // TODO(vincz): Full redraw on any drawing modification, optimize. - this.container.html(""); + ns.PreviewFilmController.prototype.render = function () { + if (!this.dirty) return + // TODO(vincz): Full redraw on any drawing modification, optimize. + this.container.html(""); - var frameCount = this.framesheet.getFrameCount(); - - for (var i = 0, l = frameCount; i < l ; i++) { - this.container.append(this.createInterstitialTile_(i)); - this.container.append(this.createPreviewTile_(i, this.framesheet)); - } - this.container.append(this.createInterstitialTile_(frameCount)); + var frameCount = this.framesheet.getFrameCount(); - var needDragndropBehavior = !!(frameCount > 1); - if(needDragndropBehavior) { - this.initDragndropBehavior_(); - } + for (var i = 0, l = frameCount; i < l ; i++) { + this.container.append(this.createInterstitialTile_(i)); + this.container.append(this.createPreviewTile_(i, this.framesheet)); + } + this.container.append(this.createInterstitialTile_(frameCount)); + + var needDragndropBehavior = !!(frameCount > 1); + if(needDragndropBehavior) { + this.initDragndropBehavior_(); + } + this.dirty = false; }; /** @@ -135,7 +136,7 @@ $('#preview-list').removeClass("show-interstitial-tiles"); // TODO(vincz): deprecate. - piskel.setActiveFrameAndRedraw(activeFrame); + piskel.setActiveFrame(activeFrame); // TODO(vincz): move localstorage request to the model layer? $.publish(Events.LOCALSTORAGE_REQUEST); @@ -168,7 +169,7 @@ previewTileRoot.addEventListener('click', function(evt) { // has not class tile-action: if(!evt.target.classList.contains('tile-action')) { - piskel.setActiveFrameAndRedraw(tileNumber); + piskel.setActiveFrame(tileNumber); } }); diff --git a/js/drawingtools/Move.js b/js/drawingtools/Move.js index 2ff9ca73..23b0f58f 100644 --- a/js/drawingtools/Move.js +++ b/js/drawingtools/Move.js @@ -29,7 +29,6 @@ var colDiff = col - this.startCol, rowDiff = row - this.startRow; if (colDiff != 0 || rowDiff != 0) { this.shiftFrame(colDiff, rowDiff, drawer.frame, this.frameClone); - drawer.renderFrame(); } }; diff --git a/js/drawingtools/PaintBucket.js b/js/drawingtools/PaintBucket.js index a4523bd7..f3c3ccfe 100644 --- a/js/drawingtools/PaintBucket.js +++ b/js/drawingtools/PaintBucket.js @@ -21,10 +21,6 @@ var targetColor = drawer.frame.getPixel(col, row); //this.recursiveFloodFill_(frame, col, row, targetColor, color); this.queueLinearFloodFill_(drawer.frame, col, row, targetColor, color); - - // Draw in canvas: - // TODO: Remove that when we have the centralized redraw loop - drawer.renderFrame(); }; /** diff --git a/js/drawingtools/Rectangle.js b/js/drawingtools/Rectangle.js index 36967878..5f2fa7c7 100644 --- a/js/drawingtools/Rectangle.js +++ b/js/drawingtools/Rectangle.js @@ -25,7 +25,6 @@ // Drawing the first point of the rectangle in the fake overlay canvas: drawer.overlayFrame.setPixel(col, row, color); - drawer.renderOverlay(); }; ns.Rectangle.prototype.moveToolAt = function(col, row, color, drawer) { @@ -44,13 +43,13 @@ } drawer.overlayFrame.setPixel(strokePoints[i].col, strokePoints[i].row, color); } - drawer.renderOverlay(); }; /** * @override */ ns.Rectangle.prototype.releaseToolAt = function(col, row, color, drawer) { + drawer.clearOverlay(); // If the stroke tool is released outside of the canvas, we cancel the stroke: if(drawer.frame.containsPixel(col, row)) { var strokePoints = this.getRectanglePixels_(this.startCol, col, this.startRow, row); @@ -59,12 +58,8 @@ drawer.frame.setPixel(strokePoints[i].col, strokePoints[i].row, color); } // The user released the tool to draw a line. We will compute the pixel coordinate, impact - // the model and draw them in the drawing canvas (not the fake overlay anymore) - // Draw in canvas: - // TODO: Remove that when we have the centralized redraw loop - drawer.renderFrame(); + // the model and draw them in the drawing canvas (not the fake overlay anymore) } - drawer.clearOverlay(); }; /** diff --git a/js/drawingtools/SimplePen.js b/js/drawingtools/SimplePen.js index 39a27363..86065d63 100644 --- a/js/drawingtools/SimplePen.js +++ b/js/drawingtools/SimplePen.js @@ -23,10 +23,6 @@ this.previousCol = col; this.previousRow = row; drawer.frame.setPixel(col, row, color); - - // Draw on canvas: - // TODO: Remove that when we have the centralized redraw loop - drawer.renderFramePixel(col, row); } }; diff --git a/js/piskel.js b/js/piskel.js index 9e8d11c6..2aee3ebb 100644 --- a/js/piskel.js +++ b/js/piskel.js @@ -36,6 +36,7 @@ $.namespace("pskl"); piskel.initDPIs_(); + frameSheet = new pskl.model.FrameSheet(framePixelWidth, framePixelHeight); frameSheet.addEmptyFrame(); @@ -78,12 +79,21 @@ $.namespace("pskl"); } $.subscribe('SET_ACTIVE_FRAME', function(evt, frameId) { - piskel.setActiveFrameAndRedraw(frameId); + piskel.setActiveFrame(frameId); }); $.subscribe('FRAMESHEET_RESET', function(evt, frameId) { piskel.redraw(); }); + var drawingLoop = new pskl.rendering.DrawingLoop(); + drawingLoop.addCallback(this.render, this); + drawingLoop.start(); + }, + + render : function (delta) { + this.drawingController.render(delta); + this.animationController.render(delta); + this.previewsController.render(delta); }, /** @@ -138,7 +148,7 @@ $.namespace("pskl"); $.subscribe(Events.REFRESH, function() { - piskel.setActiveFrameAndRedraw(0); + piskel.setActiveFrame(0); }); pskl.ToolSelector.init(); @@ -164,13 +174,12 @@ $.namespace("pskl"); piskel.setActiveFrame(0); $.publish(Events.HIDE_NOTIFICATION); piskel.finishInit(); - piskel.setActiveFrameAndRedraw(0); }; xhr.onerror = function () { $.publish(Events.HIDE_NOTIFICATION); piskel.finishInit(); - piskel.setActiveFrameAndRedraw(0); + piskel.setActiveFrame(0); }; xhr.send(); @@ -181,18 +190,6 @@ $.namespace("pskl"); this.drawingController.frame = this.getCurrentFrame(); }, - setActiveFrameAndRedraw: function(index) { - this.setActiveFrame(index); - this.redraw(); - }, - - redraw : function () { - // Update drawing canvas: - this.drawingController.renderFrame(); - // Update slideshow: - this.previewsController.createPreviews(); - }, - getActiveFrameIndex: function() { if(-1 == activeFrameIndex) { throw "Bad active frame initialization." @@ -203,7 +200,6 @@ $.namespace("pskl"); getCurrentFrame : function () { return frameSheet.getFrameByIndex(activeFrameIndex); }, - // TODO(julz): Create package ? storeSheet : function (event) { // TODO Refactor using jquery ? diff --git a/js/rendering/DrawingLoop.js b/js/rendering/DrawingLoop.js new file mode 100644 index 00000000..9d13a239 --- /dev/null +++ b/js/rendering/DrawingLoop.js @@ -0,0 +1,58 @@ +(function () { + var ns = $.namespace("pskl.rendering"); + + ns.DrawingLoop = function () { + this.requestAnimationFrame = this.getRequestAnimationFrameShim_(); + this.isRunning = false; + this.previousTime = 0; + this.callbacks = []; + }; + + ns.DrawingLoop.prototype.addCallback = function (callback, scope, args) { + var callbackObj = { + fn : callback, + scope : scope, + args : args + }; + this.callbacks.push(callbackObj); + return callbackObj; + }; + + ns.DrawingLoop.prototype.removeCallback = function (callbackObj) { + var index = this.callbacks.indexOf(callbackObj); + if (index != -1) { + this.callbacks.splice(index, 1); + } + }; + + ns.DrawingLoop.prototype.start = function () { + this.isRunning = true; + this.loop_(); + }; + + ns.DrawingLoop.prototype.loop_ = function () { + var currentTime = Date.now(); + var delta = currentTime - this.previousTime; + this.executeCallbacks_(delta); + this.previousTime = currentTime; + this.requestAnimationFrame.call(window, this.loop_.bind(this)); + }; + + ns.DrawingLoop.prototype.executeCallbacks_ = function (deltaTime) { + for (var i = 0 ; i < this.callbacks.length ; i++) { + var cb = this.callbacks[i]; + cb.fn.call(cb.scope, deltaTime, cb.args); + } + }; + + ns.DrawingLoop.prototype.stop = function () { + this.isRunning = false; + }; + + ns.DrawingLoop.prototype.getRequestAnimationFrameShim_ = function () { + var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {window.setTimeout(callback, 1000/60)}; + + return requestAnimationFrame; + } +})() \ No newline at end of file diff --git a/js/rendering/FrameRenderer.js b/js/rendering/FrameRenderer.js index 14f6ae42..a2f97e44 100644 --- a/js/rendering/FrameRenderer.js +++ b/js/rendering/FrameRenderer.js @@ -1,7 +1,6 @@ (function () { var ns = $.namespace("pskl.rendering"); - ns.FrameRenderer = function (container, renderingOptions, className) { this.defaultRenderingOptions = { @@ -169,4 +168,18 @@ } return this.canvas; }; + + /** + * @private + */ + ns.FrameRenderer.prototype.createCanvasForFrame_ = function (frame) { + var canvas = document.createElement("canvas"); + canvas.setAttribute("width", frame.getWidth() * this.dpi); + canvas.setAttribute("height", frame.getHeight() * this.dpi); + + canvas.classList.add("canvas"); + if(this.className) canvas.classList.add(this.className); + + return canvas; + }; })(); \ No newline at end of file