diff --git a/cocos2d/core/CCDirector.js b/cocos2d/core/CCDirector.js index 63b8b6abea..c139710f72 100644 --- a/cocos2d/core/CCDirector.js +++ b/cocos2d/core/CCDirector.js @@ -421,6 +421,10 @@ cc.Director = cc.Class.extend(/** @lends cc.director# */{ //cleanup scheduler this.getScheduler().unscheduleAllCallbacks(); + // Disable event dispatching + if(cc.eventManager) + cc.eventManager.setEnabled(false); + // don't release the event handlers // They are needed in case the director is run again @@ -878,7 +882,6 @@ if (cc._renderType === cc._RENDER_TYPE_CANVAS) { }; _p.setDepthTest = function () { - return; }; _p.setOpenGLView = function (openGLView) { @@ -886,8 +889,8 @@ if (cc._renderType === cc._RENDER_TYPE_CANVAS) { this._winSizeInPoints.width = cc._canvas.width; //this._openGLView.getDesignResolutionSize(); this._winSizeInPoints.height = cc._canvas.height; this._openGLView = openGLView || cc.view; - - return; + if(cc.eventManager) + cc.eventManager.setEnabled(true); }; _p._clear = function() { diff --git a/cocos2d/core/CCDirectorWebGL.js b/cocos2d/core/CCDirectorWebGL.js index 617cbfde42..f45a0194ca 100644 --- a/cocos2d/core/CCDirectorWebGL.js +++ b/cocos2d/core/CCDirectorWebGL.js @@ -133,6 +133,8 @@ if (cc._renderType === cc._RENDER_TYPE_WEBGL) { }*/ //} + if(cc.eventManager) + cc.eventManager.setEnabled(true); }; _p._clear = function() { diff --git a/cocos2d/core/event-manager/CCEventListener.js b/cocos2d/core/event-manager/CCEventListener.js index 28a9b554f3..8d5baaf3ac 100644 --- a/cocos2d/core/event-manager/CCEventListener.js +++ b/cocos2d/core/event-manager/CCEventListener.js @@ -1,5 +1,5 @@ /**************************************************************************** - Copyright (c) 2010-2014 cocos2d-x.org + Copyright (c) 2010-2014 Chukong Technologies Inc. http://www.cocos2d-x.org @@ -41,6 +41,7 @@ cc.EventListener = cc.Class.extend(/** @lends cc.EventListener# */{ _fixedPriority: 0, // The higher the number, the higher the priority, 0 is for scene graph base priority. _node: null, // scene graph based priority _paused: false, // Whether the listener is paused + _isEnabled: true, // Whether the listener is enabled /** * Initializes event with type and callback function @@ -54,42 +55,103 @@ cc.EventListener = cc.Class.extend(/** @lends cc.EventListener# */{ this._listenerID = listenerID || ""; }, + /** + *
+ * Sets paused state for the listener + * The paused state is only used for scene graph priority listeners. + * `EventDispatcher::resumeAllEventListenersForTarget(node)` will set the paused state to `true`, + * while `EventDispatcher::pauseAllEventListenersForTarget(node)` will set it to `false`. + * @note 1) Fixed priority listeners will never get paused. If a fixed priority doesn't want to receive events, + * call `setEnabled(false)` instead. + * 2) In `Node`'s onEnter and onExit, the `paused state` of the listeners which associated with that node will be automatically updated. + *
+ * @param {boolean} paused + * @private + */ _setPaused: function (paused) { this._paused = paused; }, + /** + * Checks whether the listener is paused + * @returns {boolean} + * @private + */ _isPaused: function () { return this._paused; }, + /** + * Marks the listener was registered by EventDispatcher + * @param {boolean} registered + * @private + */ _setRegistered: function (registered) { this._registered = registered; }, + /** + * Checks whether the listener was registered by EventDispatcher + * @returns {boolean} + * @private + */ _isRegistered: function () { return this._registered; }, + /** + * Gets the type of this listener + * @note It's different from `EventType`, e.g. TouchEvent has two kinds of event listeners - EventListenerOneByOne, EventListenerAllAtOnce + * @returns {number} + * @private + */ _getType: function () { return this._type; }, + /** + * Gets the listener ID of this listener + * When event is being dispatched, listener ID is used as key for searching listeners according to event type. + * @returns {string} + * @private + */ _getListenerID: function () { return this._listenerID; }, + /** + * Sets the fixed priority for this listener + * @note This method is only used for `fixed priority listeners`, it needs to access a non-zero value. 0 is reserved for scene graph priority listeners + * @param {number} fixedPriority + * @private + */ _setFixedPriority: function (fixedPriority) { this._fixedPriority = fixedPriority; }, + /** + * Gets the fixed priority of this listener + * @returns {number} 0 if it's a scene graph priority listener, non-zero for fixed priority listener + * @private + */ _getFixedPriority: function () { return this._fixedPriority; }, + /** + * Sets scene graph priority for this listener + * @param {cc.Node} node + * @private + */ _setSceneGraphPriority: function (node) { this._node = node; }, + /** + * Gets scene graph priority of this listener + * @returns {cc.Node} if it's a fixed priority listener, non-null for scene graph priority listener + * @private + */ _getSceneGraphPriority: function () { return this._node; }, @@ -110,6 +172,26 @@ cc.EventListener = cc.Class.extend(/** @lends cc.EventListener# */{ return null; }, + /** + * Enables or disables the listener + * @note Only listeners with `enabled` state will be able to receive events. + * When an listener was initialized, it's enabled by default. + * An event listener can receive events when it is enabled and is not paused. + * paused state is always false when it is a fixed priority listener. + * @param {boolean} enabled + */ + setEnabled: function(enabled){ + this._isEnabled = enabled; + }, + + /** + * Checks whether the listener is enabled + * @returns {boolean} + */ + isEnabled: function(){ + return this._isEnabled; + }, + /** * Currently JavaScript Bindings (JSB), in some cases, needs to use retain and release. This is a bug in JSB, * and the ugly workaround is to use retain/release. So, these 2 methods were added to be compatible with JSB. diff --git a/cocos2d/core/event-manager/CCEventManager.js b/cocos2d/core/event-manager/CCEventManager.js index f5b47abd22..9fa5c881b9 100644 --- a/cocos2d/core/event-manager/CCEventManager.js +++ b/cocos2d/core/event-manager/CCEventManager.js @@ -122,7 +122,7 @@ cc.eventManager = /** @lends cc.eventManager# */{ _toAddedListeners: [], _dirtyNodes: [], _inDispatch: 0, - _isEnabled: true, + _isEnabled: false, _nodePriorityIndex: 0, _internalCustomListenerIDs:[cc.game.EVENT_HIDE, cc.game.EVENT_SHOW], @@ -131,6 +131,9 @@ cc.eventManager = /** @lends cc.eventManager# */{ // Mark the node dirty only when there is an event listener associated with it. if (this._nodeListenersMap[node.__instanceId] != null) this._dirtyNodes.push(node); + var _children = node.getChildren(); + for(var i = 0, len = _children.length; i < len; i++) + this._setDirtyForNode(_children[i]); }, /** @@ -271,22 +274,28 @@ cc.eventManager = /** @lends cc.eventManager# */{ }, _sortEventListeners: function (listenerID) { - var dirtyFlag = this.DIRTY_NONE; - if (this._priorityDirtyFlagMap[listenerID]) - dirtyFlag = this._priorityDirtyFlagMap[listenerID]; + var dirtyFlag = this.DIRTY_NONE, locFlagMap = this._priorityDirtyFlagMap; + if (locFlagMap[listenerID]) + dirtyFlag = locFlagMap[listenerID]; if (dirtyFlag != this.DIRTY_NONE) { + // Clear the dirty flag first, if `rootNode` is null, then set its dirty flag of scene graph priority + locFlagMap[listenerID] = this.DIRTY_NONE; + if (dirtyFlag & this.DIRTY_FIXED_PRIORITY) this._sortListenersOfFixedPriority(listenerID); - if (dirtyFlag & this.DIRTY_SCENE_GRAPH_PRIORITY) - this._sortListenersOfSceneGraphPriority(listenerID); - - this._priorityDirtyFlagMap[listenerID] = this.DIRTY_NONE; + if (dirtyFlag & this.DIRTY_SCENE_GRAPH_PRIORITY){ + var rootNode = cc.director.getRunningScene(); + if(rootNode) + this._sortListenersOfSceneGraphPriority(listenerID, rootNode); + else + locFlagMap[listenerID] = this.DIRTY_SCENE_GRAPH_PRIORITY; + } } }, - _sortListenersOfSceneGraphPriority: function (listenerID) { + _sortListenersOfSceneGraphPriority: function (listenerID, rootNode) { var listeners = this._getListeners(listenerID); if (!listeners) return; @@ -295,7 +304,6 @@ cc.eventManager = /** @lends cc.eventManager# */{ if(!sceneGraphListener || sceneGraphListener.length === 0) return; - var rootNode = cc.director.getRunningScene(); // Reset priority index this._nodePriorityIndex = 0; this._nodePriorityMap = {}; @@ -538,7 +546,7 @@ cc.eventManager = /** @lends cc.eventManager# */{ if (fixedPriorityListeners.length !== 0) { for (; i < listeners.gt0Index; ++i) { selListener = fixedPriorityListeners[i]; - if (!selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { + if (selListener.isEnabled() && !selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { shouldStopPropagation = true; break; } @@ -549,7 +557,7 @@ cc.eventManager = /** @lends cc.eventManager# */{ if (sceneGraphPriorityListeners && !shouldStopPropagation) { // priority == 0, scene graph priority for (j = 0; j < sceneGraphPriorityListeners.length; j++) { selListener = sceneGraphPriorityListeners[j]; - if (!selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { + if (selListener.isEnabled() && !selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { shouldStopPropagation = true; break; } @@ -559,7 +567,7 @@ cc.eventManager = /** @lends cc.eventManager# */{ if (fixedPriorityListeners && !shouldStopPropagation) { // priority > 0 for (; i < fixedPriorityListeners.length; ++i) { selListener = fixedPriorityListeners[i]; - if (!selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { + if (selListener.isEnabled() && !selListener._isPaused() && selListener._isRegistered() && onEvent(selListener, eventOrArgs)) { shouldStopPropagation = true; break; } @@ -777,6 +785,23 @@ cc.eventManager = /** @lends cc.eventManager# */{ for (i = 0; i < listenersCopy.length; i++) _t.removeListener(listenersCopy[i]); listenersCopy.length = 0; + + // Bug fix: ensure there are no references to the node in the list of listeners to be added. + // If we find any listeners associated with the destroyed node in this list then remove them. + // This is to catch the scenario where the node gets destroyed before it's listener + // is added into the event dispatcher fully. This could happen if a node registers a listener + // and gets destroyed while we are dispatching an event (touch etc.) + var locToAddedListeners = _t._toAddedListeners; + for (i = 0; i < locToAddedListeners.length; ) { + var listener = locToAddedListeners[i]; + if (listener._getSceneGraphPriority() == listenerType) { + listener._setSceneGraphPriority(null); // Ensure no dangling ptr to the target node. + listener._setRegistered(false); + locToAddedListeners.splice(i, 1); + } else + ++i; + } + if (recursive === true) { var locChildren = listenerType.getChildren(), len; for (i = 0, len = locChildren.length; i< len; i++) diff --git a/extensions/gui/scrollview/CCScrollView.js b/extensions/gui/scrollview/CCScrollView.js index 696f4e7f71..b534f9c88a 100644 --- a/extensions/gui/scrollview/CCScrollView.js +++ b/extensions/gui/scrollview/CCScrollView.js @@ -39,6 +39,7 @@ var SCROLL_DEACCEL_DIST = 1.0; var BOUNCE_DURATION = 0.15; var INSET_RATIO = 0.2; var MOVE_INCH = 7.0/160.0; +var BOUNCE_BACK_FACTOR = 0.35; cc.convertDistanceFromPointToInch = function(pointDis){ var eglViewer = cc.view; @@ -165,8 +166,8 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ return; } if (!this._bounceable) { - var minOffset = this._getMinContainerOffset(); - var maxOffset = this._getMaxContainerOffset(); + var minOffset = this.minContainerOffset(); + var maxOffset = this.maxContainerOffset(); offset.x = Math.max(minOffset.x, Math.min(maxOffset.x, offset.x)); offset.y = Math.max(minOffset.y, Math.min(maxOffset.y, offset.y)); @@ -260,7 +261,7 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ * Returns the current container's minimum offset. You may want this while you animate scrolling by yourself * @return {cc.Point} Returns the current container's minimum offset. */ - _getMinContainerOffset:function () { + minContainerOffset:function () { var locContainer = this._container; var locContentSize = locContainer.getContentSize(), locViewSize = this._viewSize; return cc.p(locViewSize.width - locContentSize.width * locContainer.getScaleX(), @@ -271,7 +272,7 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ * Returns the current container's maximum offset. You may want this while you animate scrolling by yourself * @return {cc.Point} Returns the current container's maximum offset. */ - _getMaxContainerOffset:function () { + maxContainerOffset:function () { return cc.p(0.0, 0.0); }, @@ -428,14 +429,30 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ var newPoint = this.convertTouchToNodeSpace(touch); var moveDistance = cc.pSub(newPoint, this._touchPoint); - var dis = 0.0, locDirection = this._direction; - if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) + var dis = 0.0, locDirection = this._direction, pos; + if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL){ dis = moveDistance.y; - else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) + pos = this._container.getPositionY(); + if (!(this.minContainerOffset().y <= pos && pos <= this.maxContainerOffset().y)) + moveDistance.y *= BOUNCE_BACK_FACTOR; + } else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL){ dis = moveDistance.x; - else + pos = this._container.getPositionX(); + if (!(this.minContainerOffset().x <= pos && pos <= this.maxContainerOffset().x)) + moveDistance.x *= BOUNCE_BACK_FACTOR; + }else { dis = Math.sqrt(moveDistance.x * moveDistance.x + moveDistance.y * moveDistance.y); + pos = this._container.getPositionY(); + var _minOffset = this.minContainerOffset(), _maxOffset = this.maxContainerOffset(); + if (!(_minOffset.y <= pos && pos <= _maxOffset.y)) + moveDistance.y *= BOUNCE_BACK_FACTOR; + + pos = this._container.getPositionX(); + if (!(_minOffset.x <= pos && pos <= _maxOffset.x)) + moveDistance.x *= BOUNCE_BACK_FACTOR; + } + if (!this._touchMoved && Math.abs(cc.convertDistanceFromPointToInch(dis)) < MOVE_INCH ){ //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); return; @@ -449,7 +466,7 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ this._touchPoint = newPoint; this._touchMoved = true; - if (cc.rectContainsPoint(frame, this.convertToWorldSpace(newPoint))) { + if (this._dragging) { switch (locDirection) { case cc.SCROLLVIEW_DIRECTION_VERTICAL: moveDistance.x = 0.0; @@ -527,10 +544,10 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ updateInset:function () { if (this.getContainer() != null) { var locViewSize = this._viewSize; - var tempOffset = this._getMaxContainerOffset(); + var tempOffset = this.maxContainerOffset(); this._maxInset.x = tempOffset.x + locViewSize.width * INSET_RATIO; this._maxInset.y = tempOffset.y + locViewSize.height * INSET_RATIO; - tempOffset = this._getMinContainerOffset(); + tempOffset = this.minContainerOffset(); this._minInset.x = tempOffset.x - locViewSize.width * INSET_RATIO; this._minInset.y = tempOffset.y - locViewSize.height * INSET_RATIO; } @@ -683,8 +700,8 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ * @param animated If YES, relocation is animated */ _relocateContainer:function (animated) { - var min = this._getMinContainerOffset(); - var max = this._getMaxContainerOffset(); + var min = this.minContainerOffset(); + var max = this.maxContainerOffset(); var locDirection = this._direction; var oldPoint = this._container.getPosition(); @@ -724,8 +741,8 @@ cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ maxInset = this._maxInset; minInset = this._minInset; } else { - maxInset = this._getMaxContainerOffset(); - minInset = this._getMinContainerOffset(); + maxInset = this.maxContainerOffset(); + minInset = this.minContainerOffset(); } //check to see if offset lies within the inset bounds @@ -873,10 +890,10 @@ window._p = cc.ScrollView.prototype; // Extended properties /** @expose */ _p.minOffset; -cc.defineGetterSetter(_p, "minOffset", _p._getMinContainerOffset); +cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset); /** @expose */ _p.maxOffset; -cc.defineGetterSetter(_p, "maxOffset", _p._getMaxContainerOffset); +cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset); /** @expose */ _p.bounceable; cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable); diff --git a/extensions/gui/scrollview/CCTableView.js b/extensions/gui/scrollview/CCTableView.js index fc7c5f3e10..a001a8a3b7 100644 --- a/extensions/gui/scrollview/CCTableView.js +++ b/extensions/gui/scrollview/CCTableView.js @@ -319,7 +319,7 @@ cc.TableView = cc.ScrollView.extend(/** @lends cc.TableView# */{ if (this._direction == cc.SCROLLVIEW_DIRECTION_HORIZONTAL) { this.setContentOffset(cc.p(0, 0)); } else { - this.setContentOffset(cc.p(0, this._getMinContainerOffset().y)); + this.setContentOffset(cc.p(0, this.minContainerOffset().y)); } this._oldDirection = this._direction; }