Module: GlimR::EventListener

Included in:
SceneObject
Defined in:
lib/glimr/eventlistener.rb

Overview

The EventListener mixin implements an event listening interface similar to <a href=“www.w3.org/TR/DOM-Level-2-Events/”> W3C’s ECMAScript DOM event API</a>, with the addition of event multicasting.

EventListener provides the following methods: #add_event_listener, #remove_event_listener, #dispatch_event and #multicast_event.

The list of event listeners can be modified with #add_event_listener and #remove_event_listener.

E.g.

l = event_listener.add_event_listener(:hello){|listener, event|
  puts "Goodbye!"
}
event_listener.remove_event_listener(:hello, l)
event_listener.add_event_listener(:hello){|listener, event|
  puts "Hello, World!"
}
another_hello_listener.add_event_listener(:hello){|l, e|
  puts "Hello from me too!"
}
event_listener.attach another_hello_listener

Also provides a method_missing that captures method calls starting with on_ and parses them as requests to add event listener for the event type.

E.g. to add a bubbling event listener for the event type :frame, it’s possible to do

obj.on_frame{|o,e| puts 'got frame event'}

And for a capturing listener

obj.on_frame(true){|o,e| puts 'got it first!'}

To send a targeted event to the EventListener, use #dispatch_event.

E.g.

event_listener.dispatch_event(Event.new(:hello))
# Hello, World!

To broadcast an event to all listeners for the event type in the scene graph, use #multicast_event.

E.g.

event_listener.multicast_event(Event.new(:hello))
# Hello from me too!
# Hello, World!

Instance Attribute Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(mn, *a, &b) ⇒ Object



101
102
103
104
105
106
107
# File 'lib/glimr/eventlistener.rb', line 101

def method_missing(mn, *a, &b)
  if mn.to_s[0,3] == "on_"
    add_event_listener(mn.to_s[3..-1].gsub(/=$/,''), *a, &b)
  else
    super
  end
end

Instance Attribute Details

#event_listenersObject

Returns the value of attribute event_listeners.



65
66
67
# File 'lib/glimr/eventlistener.rb', line 65

def event_listeners
  @event_listeners
end

#listener_countObject (readonly)

Returns the value of attribute listener_count.



66
67
68
# File 'lib/glimr/eventlistener.rb', line 66

def listener_count
  @listener_count
end

Instance Method Details

#add_event_listener(type, listener = nil, use_capture = false, &block) ⇒ Object

Adds an event listener for the given event type. If use_capture is true, listens on capture phase. Otherwise listens on bubble phase. If listener is nil or false, the given block is used as the listener instead.



76
77
78
79
80
81
# File 'lib/glimr/eventlistener.rb', line 76

def add_event_listener(type, listener=nil, use_capture=false, &block)
  listener ||= block
  type = type.to_sym
  @event_listeners[type][(use_capture ? :capture : :bubble)] << listener
  increment_listener_count type
end

#decrement_listener_count(type, count = 1) ⇒ Object

Subtracts count from the subtree total listener count for type. Sends changes to parent to keep its subtree total listener count correct.



189
190
191
# File 'lib/glimr/eventlistener.rb', line 189

def decrement_listener_count type, count=1
  increment_listener_count type, -count
end

#dispatch_event(evt) ⇒ Object

Sends the Event evt down to self and bubbles it up.



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
# File 'lib/glimr/eventlistener.rb', line 133

def dispatch_event(evt)
  evt.target = self
  ancestors = [self]
  o = self
  while o.parent and !o.parent.event_root and o.parent != evt.sender
    ancestors << o.parent
    o = o.parent
  end
  i = ancestors.size
  ancestors.reverse.each{|a|
    a.process_event(evt)
    break if evt.stopped
    i -= 1
    break if evt.phase == :bubble
  }
  # Now either the event was turned around at i or reached the bottom (i==1).

  # Continue by setting evt.phase to :bubble and sending it up from i.

  i += 1 if evt.target == self # bubble already called for self

  evt.phase = :bubble
  (ancestors[i..-1] || []).each{|a|
    break if evt.stopped
    a.process_event(evt)
  }
  return !evt.cancelled
end

#event_rootObject



159
160
161
# File 'lib/glimr/eventlistener.rb', line 159

def event_root
  false
end

#increment_listener_count(type, count = 1) ⇒ Object

Adds count to the subtree total listener count for type. Sends changes to parent to keep its subtree total listener count correct.

Raises:

  • (ArgumentError)


180
181
182
183
184
185
# File 'lib/glimr/eventlistener.rb', line 180

def increment_listener_count type, count=1
  c = @listener_count[type]
  raise ArgumentError, "Trying to decrement listener count too much." if -count > c
  @listener_count[type] += count
  parent.increment_listener_count type, count if parent.is_a? EventListener
end

#initialize(*a, &b) ⇒ Object



68
69
70
71
# File 'lib/glimr/eventlistener.rb', line 68

def initialize(*a,&b)
  @event_listeners = Hash.new{|h,k| h[k] = Hash.new{|h,k| h[k] = []} }
  @listener_count = Hash.new{|h,k| h[k] = 0 }
end

#multicast_event(evt) ⇒ Object

Sends the Event evt to first the capture listeners, then down to children, then to bubbling listeners.

Returns immediately if there are no listeners for the event type in or below this node.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/glimr/eventlistener.rb', line 115

def multicast_event(evt)
  return !evt.cancelled if @listener_count[evt.type] <= 0
  evt.phase = :capture
  process_event(evt)
  return !evt.cancelled if evt.stopped
  if evt.phase == :capture # still going down

    children.each{|c|
      c.multicast_event(evt) if c.is_a? EventListener
      return !evt.cancelled if evt.stopped
    }
  end
  return !evt.cancelled if evt.stopped
  evt.phase = :bubble
  process_event(evt)
  return !evt.cancelled
end

#process_event(evt) ⇒ Object

Processes the Event evt by calling all listeners on this object registered to handle the event.



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/glimr/eventlistener.rb', line 165

def process_event(evt)
  listeners_for(evt).each{|l|
    break if evt.stopped
    case l
    when Symbol
      __send__(l, self, evt)
    else
      l.call(self, evt)
    end
  }
  !evt.cancelled
end

#remove_event_listener(type, listener = nil, use_capture = false) ⇒ Object

Removes the given event listener from the event type. If no listener is given, removes all event listeners from the event type. If use_capture is true, removes capturing event listener(s), if false, removes bubbling event listener(s).



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/glimr/eventlistener.rb', line 86

def remove_event_listener(type, listener=nil, use_capture=false)
  type = type.to_sym
  if listener
    success = @event_listeners[type][(use_capture ? :capture : :bubble)].delete_first(listener)
    decrement_listener_count type if success
  else
    listeners = @event_listeners[type][(use_capture ? :capture : :bubble)]
    sz = listeners.size
    if sz > 0
      listeners.clear
      decrement_listener_count type, sz
    end
  end
end