-
Notifications
You must be signed in to change notification settings - Fork 819
/
EventDispatcher.as
executable file
·215 lines (179 loc) · 8.96 KB
/
EventDispatcher.as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// =================================================================================================
//
// Starling Framework
// Copyright Gamua GmbH. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.events
{
import flash.utils.Dictionary;
import starling.core.starling_internal;
import starling.display.DisplayObject;
use namespace starling_internal;
/** The EventDispatcher class is the base class for all classes that dispatch events.
* This is the Starling version of the Flash class with the same name.
*
* <p>The event mechanism is a key feature of Starling's architecture. Objects can communicate
* with each other through events. Compared the the Flash event system, Starling's event system
* was simplified. The main difference is that Starling events have no "Capture" phase.
* They are simply dispatched at the target and may optionally bubble up. They cannot move
* in the opposite direction.</p>
*
* <p>As in the conventional Flash classes, display objects inherit from EventDispatcher
* and can thus dispatch events. Beware, though, that the Starling event classes are
* <em>not compatible with Flash events:</em> Starling display objects dispatch
* Starling events, which will bubble along Starling display objects - but they cannot
* dispatch Flash events or bubble along Flash display objects.</p>
*
* @see Event
* @see starling.display.DisplayObject DisplayObject
*/
public class EventDispatcher
{
private var _eventListeners:Dictionary;
/** Helper object. */
private static var sBubbleChains:Array = [];
/** Creates an EventDispatcher. */
public function EventDispatcher()
{ }
/** Registers an event listener at a certain object. */
public function addEventListener(type:String, listener:Function):void
{
if (_eventListeners == null)
_eventListeners = new Dictionary();
var listeners:Vector.<Function> = _eventListeners[type] as Vector.<Function>;
if (listeners == null)
_eventListeners[type] = new <Function>[listener];
else if (listeners.indexOf(listener) == -1) // check for duplicates
listeners[listeners.length] = listener; // avoid 'push'
}
/** Removes an event listener from the object. */
public function removeEventListener(type:String, listener:Function):void
{
if (_eventListeners)
{
var listeners:Vector.<Function> = _eventListeners[type] as Vector.<Function>;
var numListeners:int = listeners ? listeners.length : 0;
if (numListeners > 0)
{
// we must not modify the original vector, but work on a copy.
// (see comment in 'invokeEvent')
var index:int = listeners.indexOf(listener);
if (index != -1)
{
var restListeners:Vector.<Function> = listeners.slice(0, index);
for (var i:int=index+1; i<numListeners; ++i)
restListeners[i-1] = listeners[i];
_eventListeners[type] = restListeners;
}
}
}
}
/** Removes all event listeners with a certain type, or all of them if type is null.
* Be careful when removing all event listeners: you never know who else was listening. */
public function removeEventListeners(type:String=null):void
{
if (type && _eventListeners)
delete _eventListeners[type];
else
_eventListeners = null;
}
/** Dispatches an event to all objects that have registered listeners for its type.
* If an event with enabled 'bubble' property is dispatched to a display object, it will
* travel up along the line of parents, until it either hits the root object or someone
* stops its propagation manually. */
public function dispatchEvent(event:Event):void
{
var bubbles:Boolean = event.bubbles;
if (!bubbles && (_eventListeners == null || !(event.type in _eventListeners)))
return; // no need to do anything
// we save the current target and restore it later;
// this allows users to re-dispatch events without creating a clone.
var previousTarget:EventDispatcher = event.target;
event.setTarget(this);
if (bubbles && this is DisplayObject) bubbleEvent(event);
else invokeEvent(event);
if (previousTarget) event.setTarget(previousTarget);
}
/** @private
* Invokes an event on the current object. This method does not do any bubbling, nor
* does it back-up and restore the previous target on the event. The 'dispatchEvent'
* method uses this method internally. */
internal function invokeEvent(event:Event):Boolean
{
var listeners:Vector.<Function> = _eventListeners ?
_eventListeners[event.type] as Vector.<Function> : null;
var numListeners:int = listeners == null ? 0 : listeners.length;
if (numListeners)
{
event.setCurrentTarget(this);
// we can enumerate directly over the vector, because:
// when somebody modifies the list while we're looping, "addEventListener" is not
// problematic, and "removeEventListener" will create a new Vector, anyway.
for (var i:int=0; i<numListeners; ++i)
{
var listener:Function = listeners[i] as Function;
var numArgs:int = listener.length;
if (numArgs == 0) listener();
else if (numArgs == 1) listener(event);
else listener(event, event.data);
if (event.stopsImmediatePropagation)
return true;
}
return event.stopsPropagation;
}
else
{
return false;
}
}
/** @private */
internal function bubbleEvent(event:Event):void
{
// we determine the bubble chain before starting to invoke the listeners.
// that way, changes done by the listeners won't affect the bubble chain.
var chain:Vector.<EventDispatcher>;
var element:DisplayObject = this as DisplayObject;
var length:int = 1;
if (sBubbleChains.length > 0) { chain = sBubbleChains.pop(); chain[0] = element; }
else chain = new <EventDispatcher>[element];
while ((element = element.parent) != null)
chain[int(length++)] = element;
for (var i:int=0; i<length; ++i)
{
var stopPropagation:Boolean = chain[i].invokeEvent(event);
if (stopPropagation) break;
}
chain.length = 0;
sBubbleChains[sBubbleChains.length] = chain; // avoid 'push'
}
/** Dispatches an event with the given parameters to all objects that have registered
* listeners for the given type. The method uses an internal pool of event objects to
* avoid allocations. */
public function dispatchEventWith(type:String, bubbles:Boolean=false, data:Object=null):void
{
if (bubbles || hasEventListener(type))
{
var event:Event = Event.fromPool(type, bubbles, data);
dispatchEvent(event);
Event.toPool(event);
}
}
/** If called with one argument, figures out if there are any listeners registered for
* the given event type. If called with two arguments, also determines if a specific
* listener is registered. */
public function hasEventListener(type:String, listener:Function=null):Boolean
{
var listeners:Vector.<Function> = _eventListeners ? _eventListeners[type] : null;
if (listeners == null) return false;
else
{
if (listener != null) return listeners.indexOf(listener) != -1;
else return listeners.length != 0;
}
}
}
}