From b71ffdbe71838681d8e5e21a6a1e1a107be42883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Tue, 26 Jan 2016 21:37:27 +0100 Subject: [PATCH] Remove all direct calls to view.update() and favor of the new view.requestUpdate() Pure window.requestAnimationFrame() smoothness, automatic updates even when working directly from JavaScript, and no more slow-downs from onLoad events! Closes #830, #925 --- examples/Paperjs.org/Qbertify.html | 1 - examples/Scripts/BooleanOperations.html | 7 -- src/core/PaperScript.js | 2 - src/event/Key.js | 2 - src/item/Raster.js | 3 - src/project/Project.js | 6 + src/svg/SVGImport.js | 1 - src/view/CanvasView.js | 6 +- src/view/View.js | 144 +++++++++++------------- 9 files changed, 76 insertions(+), 96 deletions(-) diff --git a/examples/Paperjs.org/Qbertify.html b/examples/Paperjs.org/Qbertify.html index 7ca885fa..45db33ae 100644 --- a/examples/Paperjs.org/Qbertify.html +++ b/examples/Paperjs.org/Qbertify.html @@ -122,7 +122,6 @@ var image = document.createElement('img'); image.onload = function () { handleImage(image); - view.update(); }; image.src = event.target.result; }; diff --git a/examples/Scripts/BooleanOperations.html b/examples/Scripts/BooleanOperations.html index b3318c98..0455c220 100644 --- a/examples/Scripts/BooleanOperations.html +++ b/examples/Scripts/BooleanOperations.html @@ -273,8 +273,6 @@ // var nup = unite(pathA, pathB); // console.timeEnd('unite'); // // nup.style = booleanStyle; - - // view.update(); } var booleanStyle = { @@ -307,7 +305,6 @@ var boolPathU = _p1U.unite(_p2U); console.timeEnd('Union'); boolPathU.style = booleanStyle; - view.update(); } if (operations.intersection) { @@ -318,7 +315,6 @@ var boolPathI = _p1I.intersect(_p2I); console.timeEnd('Intersection'); boolPathI.style = booleanStyle; - view.update(); } if (operations.subtraction) { @@ -329,7 +325,6 @@ var boolPathS = _p1S.subtract(_p2S); console.timeEnd('Subtraction'); boolPathS.style = booleanStyle; - view.update(); } if (operations.exclusion) { @@ -340,7 +335,6 @@ var boolPathE = _p1E.exclude(_p2E); console.timeEnd('Exclusion'); boolPathE.style = booleanStyle; - view.update(); } if (operations.division) { @@ -352,7 +346,6 @@ console.timeEnd('Division'); disperse(boolPathD); boolPathD.style = booleanStyle; - view.update(); } } diff --git a/src/core/PaperScript.js b/src/core/PaperScript.js index ead636f8..e687e21d 100644 --- a/src/core/PaperScript.js +++ b/src/core/PaperScript.js @@ -482,8 +482,6 @@ Base.exports.PaperScript = (function() { }); if (res.onFrame) view.setOnFrame(res.onFrame); - // Automatically update view at the end. - view.update(); } return compiled; } diff --git a/src/event/Key.js b/src/event/Key.js index ae8cbe0e..794cea84 100644 --- a/src/event/Key.js +++ b/src/event/Key.js @@ -134,8 +134,6 @@ var Key = new function() { paper = scope; // Call the onKeyDown or onKeyUp handler if present tool.emit(type, new KeyEvent(down, key, character, event)); - if (view) - view.update(); } } diff --git a/src/item/Raster.js b/src/item/Raster.js index 2456fb38..41efed76 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -230,9 +230,6 @@ var Raster = Item.extend(/** @lends Raster# */{ if (view && that.responds(type)) { paper = view._scope; that.emit(type, new Event(event)); - // TODO: Request a redraw in the next animation frame from - // update() instead! - view.update(); } } diff --git a/src/project/Project.js b/src/project/Project.js index 324ceb1f..bcf5d0ba 100644 --- a/src/project/Project.js +++ b/src/project/Project.js @@ -88,7 +88,13 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ */ _changed: function(flags, item) { if (flags & /*#=*/ChangeFlag.APPEARANCE) { + // Never draw changes right away. Simply mark the project as "dirty" + // and request a view update through window.requestAnimationFrame() + // (via view.requestUpdate()), which handles the smooth updates. this._needsUpdate = true; + var view = this._view; + if (view && !view._requested) + view.requestUpdate(); } // Have project keep track of changed items so they can be iterated. // This can be used for example to update the SVG tree. Needs to be diff --git a/src/svg/SVGImport.js b/src/svg/SVGImport.js index 63fc5b88..7faacd5d 100644 --- a/src/svg/SVGImport.js +++ b/src/svg/SVGImport.js @@ -558,7 +558,6 @@ new function() { view = scope.project && scope.getView(); if (onLoad) onLoad.call(this, item); - view.update(); } if (isRoot) { diff --git a/src/view/CanvasView.js b/src/view/CanvasView.js index ca029a67..d4a29936 100644 --- a/src/view/CanvasView.js +++ b/src/view/CanvasView.js @@ -119,13 +119,11 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ * event hanlders for interaction, animation and load events, this method is * invoked for you automatically at the end. * - * @param {Boolean} [force=false] {@true if the view should be updated even - * if no change has happened} * @return {Boolean} {@true if the view was updated} */ - update: function(force) { + update: function() { var project = this._project; - if (!project || !force && !project._needsUpdate) + if (!project || !project._needsUpdate) return false; var ctx = this._context, size = this._viewSize; diff --git a/src/view/View.js b/src/view/View.js index acc29a1e..fd95de76 100644 --- a/src/view/View.js +++ b/src/view/View.js @@ -160,18 +160,73 @@ var View = Base.extend(Emitter, /** @lends View# */{ _time: 0, _count: 0, - _requestFrame: function() { - var that = this; - DomEvent.requestAnimationFrame(function() { - that._requested = false; - // Do we need to stop due to a call to the frame event's uninstall() - if (!that._animate) - return; - // Request next frame already before handling the current frame - that._requestFrame(); - that._handleFrame(); - }, this._element); - this._requested = true; + /** + * Updates the view if there are changes. Note that when using built-in + * event hanlders for interaction, animation and load events, this method is + * invoked for you automatically at the end. + * + * @name View#update + * @function + * @param {Boolean} [force=false] {@true if the view should be updated even + * if no change has happened} + * @return {Boolean} {@true if the view was updated} + */ + // update: function(force) { + // }, + + /** + * Updates the view if there are changes. + * + * @deprecated use {@link #update()} instead. + */ + draw: '#update', + + /** + * Requests an update of the view if there are changes through the browser's + * requestAnimationFrame() mechanism for smooth animation. Note that when + * using built-in event handlers for interaction, animation and load events, + * updates are automatically invoked for you automatically at the end. + * + * @function + */ + requestUpdate: function() { + if (!this._requested) { + var that = this; + DomEvent.requestAnimationFrame(function() { + that._requested = false; + // Only handle frame and request next one if we don't need to + // stop, e.g. due to a call to pause(), or a request for a + // single redraw. + if (that._animate) { + // Request next frame before handling the current frame + that.requestUpdate(); + that._handleFrame(); + } + // Even if we're not animating, update the view now since this + // might have been a request for a single redraw after a change + that.update(); + }, this._element); + this._requested = true; + } + }, + + /** + * Makes all animation play by adding the view to the request animation + * loop. + */ + play: function() { + this._animate = true; + // Request a frame handler straight away to initialize the + // sequence of onFrame calls. + this.requestUpdate(); + }, + + /** + * Makes all animation pause by removing the view to the request animation + * loop. + */ + pause: function() { + this._animate = false; }, _handleFrame: function() { @@ -193,8 +248,6 @@ var View = Base.extend(Emitter, /** @lends View# */{ if (this._stats) this._stats.update(); this._handlingFrame = false; - // Automatically update view on each frame. - this.update(); }, _animateItem: function(item, animate) { @@ -229,20 +282,6 @@ var View = Base.extend(Emitter, /** @lends View# */{ } }, - _update: function() { - this._project._needsUpdate = true; - if (this._handlingFrame) - return; - if (this._animate) { - // If we're animating, call _handleFrame staight away, but without - // requesting another animation frame. - this._handleFrame(); - } else { - // Otherwise simply update the view now - this.update(); - } - }, - /** * Private notifier that is called whenever a change occurs in this view. * Used only by Matrix for now. @@ -317,7 +356,7 @@ var View = Base.extend(Emitter, /** @lends View# */{ delta: delta }); this._changed(); - this.update(); + this.requestUpdate(); }, /** @@ -539,49 +578,6 @@ var View = Base.extend(Emitter, /** @lends View# */{ this.translate(Point.read(arguments).negate()); } }), /** @lends View# */{ - /** - * Makes all animation play by adding the view to the request animation - * loop. - */ - play: function() { - this._animate = true; - // Request a frame handler straight away to initialize the - // sequence of onFrame calls. - if (!this._requested) - this._requestFrame(); - }, - - /** - * Makes all animation pause by removing the view to the request animation - * loop. - */ - pause: function() { - this._animate = false; - }, - - /** - * Updates the view if there are changes. Note that when using built-in - * event hanlders for interaction, animation and load events, this method is - * invoked for you automatically at the end. - * - * @name View#update - * @function - * @param {Boolean} [force=false] {@true if the view should be updated even - * if no change has happened} - * @return {Boolean} {@true if the view was updated} - */ - // update: function(force) { - // }, - - /** - * Updates the view if there are changes. - * - * @deprecated use {@link #update()} instead. - */ - draw: function() { - this.update(); - }, - // TODO: getInvalidBounds // TODO: invalidate(rect) // TODO: style: artwork / preview / raster / opaque / ink @@ -1186,10 +1182,6 @@ new function() { // Injection scope for mouse events on the browser if (called && (!nativeMove || responds('mousedrag')) || mouse.down && responds('mouseup')) event.preventDefault(); - - // In the end we always call update(), which only updates the view - // if anything has changed in the above calls. - this.update(); }, _countItemEvent: function(type, sign) {