Improve handling of item based onFrame handlers, by moving functionality to View and removing handlers properly when the view is destroyed.

This commit is contained in:
Jürg Lehni 2012-12-03 09:53:47 -08:00
parent 5b56bd7fbf
commit cf5853c8cc
3 changed files with 109 additions and 88 deletions

View file

@ -75,19 +75,6 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
}
};
var onFrameItems = [];
function onFrame(event) {
// Note: Do not optimaize onFrameItems.length since it may change!
for (var i = 0; i < onFrameItems.length; i++) {
var item = onFrameItems[i];
if (item)
item.fire('frame', event);
else
// item was marked for delition. Remove it, and reduce index
onFrameItems.splice(i--, 1);
}
}
return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
function(name) {
@ -95,21 +82,10 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
}, {
onFrame: {
install: function() {
if (!onFrameItems.length)
this._project.view.attach('frame', onFrame);
onFrameItems.push(this);
this._project.view._animateItem(this, true);
},
uninstall: function() {
// Mark for deletion, but do not remove it yet, since
// removing handlers from inside handlers would mess up
// onFrame loop above otherwise.
onFrameItems[onFrameItems.indexOf(this)] = null;
if (onFrameItems.length == 1) {
// If this is the last one, just stop animating
// straight away.
this._project.view.detach('frame', onFrame);
onFrameItems = [];
}
this._project.view._animateItem(this, false);
}
},

View file

@ -52,14 +52,14 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{
// Activate straight away by passing true to base(), so paper.project is
// set, as required by Layer and DoumentView constructors.
this.base(true);
this._currentStyle = new PathStyle();
this._selectedItems = {};
this._selectedItemCount = 0;
this.layers = [];
this.symbols = [];
this.activeLayer = new Layer();
if (view)
this.view = view instanceof View ? view : View.create(view);
this._currentStyle = new PathStyle();
this._selectedItems = {};
this._selectedItemCount = 0;
// Change tracking, not in use for now. Activate once required:
// this._changes = [];
// this._changesById = {};

View file

@ -24,60 +24,6 @@
* screen.
*/
var View = this.View = Base.extend(Callback, /** @lends View# */{
_events: {
onFrame: {
install: function() {
/*#*/ if (options.browser) {
var that = this,
requested = false,
before,
time = 0,
count = 0;
this._onFrameCallback = function(param, dontRequest) {
requested = false;
// See if we need to stop due to a call to uninstall()
if (!that._onFrameCallback)
return;
// Set the global paper object to the current scope
paper = that._scope;
if (!dontRequest) {
// Request next frame already
requested = true;
DomEvent.requestAnimationFrame(that._onFrameCallback,
that._element);
}
var now = Date.now() / 1000,
delta = before ? now - before : 0;
// delta: Time elapsed since last redraw in seconds
// time: Time since first call of frame() in seconds
// Use Base.merge to convert into a Base object,
// for #toString()
that.fire('frame', Base.merge({
delta: delta,
time: time += delta,
count: count++
}));
before = now;
// Update framerate stats
if (that._stats)
that._stats.update();
// Automatically draw view on each frame.
that.draw(true);
};
// Call the onFrame handler straight away, initializing the
// sequence of onFrame calls.
if (!requested)
this._onFrameCallback();
/*#*/ } // options.browser
},
uninstall: function() {
delete this._onFrameCallback;
}
},
onResize: {}
},
initialize: function(element) {
// Store reference to the currently active global paper scope, and the
@ -157,6 +103,8 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{
// Make sure the first view is focused for keyboard input straight away
if (!View._focused)
View._focused = this;
// Items that need the onFrame handler called on them
this._frameItems = [];
},
/**
@ -178,18 +126,115 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{
DomEvent.remove(this._element, this._viewHandlers);
DomEvent.remove(window, this._windowHandlers);
this._element = this._project = null;
// Removing all onFrame handlers makes the _onFrameCallback handler stop
// Removing all onFrame handlers makes the onFrame handler stop
// automatically through its uninstall method.
this.detach('frame');
this._frameItems = [];
return true;
},
_events: {
onFrame: {
install: function() {
/*#*/ if (options.browser) {
// Call the onFrame handler straight away and initialize the
// sequence of onFrame calls.
if (!this._requested) {
this._animate = true;
this._handleFrame(true);
}
/*#*/ } // options.browser
},
uninstall: function() {
this._animate = false;
}
},
onResize: {}
},
// These are default values for event related properties on the prototype.
// Writing item._count++ does not change the defaults, it creates / updates
// the property on the instance. Useful!
_animate: false,
_time: 0,
_count: 0,
_handleFrame: function(request) {
this._requested = false;
// See if we need to stop due to a call to uninstall()
if (!this._animate)
return;
// Set the global paper object to the current scope
paper = this._scope;
if (request) {
// Request next frame already
this._requested = true;
var that = this;
DomEvent.requestAnimationFrame(function() {
that._handleFrame(true);
}, this._element);
}
var now = Date.now() / 1000,
delta = this._before ? now - this._before : 0;
this._before = now;
// Use Base.merge to convert into a Base object, for #toString()
this.fire('frame', Base.merge({
// Time elapsed since last redraw in seconds:
delta: delta,
// Time since first call of frame() in seconds:
time: this._time += delta,
count: this._count++
}));
// Update framerate stats
if (this._stats)
this._stats.update();
// Automatically draw view on each frame.
this.draw(true);
},
_animateItem: function(item, animate) {
var items = this._frameItems;
if (animate) {
if (!items.length)
this.attach('frame', this._handleFrameItems);
items.push(item);
} else {
// Mark for deletion, but do not remove it yet, since
// removing handlers from inside handlers would mess up
// onFrame loop in the view otherwise.
items[items.indexOf(this)] = null;
if (items.length == 1) {
// If this is the last one, just stop animating straight away.
this.detach('frame', this._handleFrameItems);
this._frameItems = [];
}
}
},
// An empty callback that's only there so _frameItems can be handled
// through the onFrame callback framework that automatically starts and
// stops the animation for us whenever there's one or more frame handlers
_handleFrameItems: function(event) {
var items = this._frameItems;
// Note: Do not optimaize onFrameItems.length since it may change!
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item)
item.fire('frame', event);
else
// item was marked for delition. Remove it, and reduce index
items.splice(i--, 1);
}
},
_redraw: function() {
this._redrawNeeded = true;
if (this._onFrameCallback) {
// If there's a _onFrameCallback, call it staight away,
// but without requesting another animation frame.
this._onFrameCallback(0, true);
if (this._animate) {
// If we're animating, call _handleFrame staight away, but without
// requesting another animation frame.
this._handleFrame();
} else {
// Otherwise simply redraw the view now
this.draw();