Module: Ruby2D::Interactive

Included in:
Button, Renderable
Defined in:
lib/ruby2d/interactive.rb

Overview

Per-object event handling for objects that participate in Window's interactive registry. Provides on / off / interactive? and the _fire_event dispatch entry point. Mixed into Renderable and Button.

Including objects must expose x, y, z, width, height, and contains?(x, y) so Window::ObjectEventDispatch can hit-test them.

Constant Summary collapse

OBJECT_EVENT_FILTER_PREDICATES =

Per-object events that take a value matcher (button) in the kwarg form. :hover, :hover_out, and :mouse_scroll carry no matchable field.

{
  mouse_down: :button?, mouse_held: :button?, mouse_up: :button?,
  click: :button?, drag: :button?
}.freeze
OBJECT_EVENTS =

The full per-object event vocabulary: the filterable events above plus the three that carry no matchable field. Derived from the predicates map so the two can't drift, and mirrors what Window::ObjectEventDispatch fires.

(OBJECT_EVENT_FILTER_PREDICATES.keys + %i[hover hover_out mouse_scroll]).freeze

Instance Method Summary collapse

Instance Method Details

#_fire_event(type, event) ⇒ Object

Dispatch an event to stored handlers (called by Window)



80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruby2d/interactive.rb', line 80

def _fire_event(type, event)
  return unless @_object_events

  handlers = @_object_events[type]
  return unless handlers

  # Snapshot the values: a handler may register another handler for the same
  # event type mid-dispatch, which would otherwise mutate the hash we're
  # iterating ("can't add a new key into hash during iteration").
  handlers.values.each { |proc| proc.call(event) }
end

#interactive?(event = nil) ⇒ Boolean

Check if this object has any event handlers

Returns:

  • (Boolean)


69
70
71
72
73
74
75
76
77
# File 'lib/ruby2d/interactive.rb', line 69

def interactive?(event = nil)
  return false unless @_object_events

  if event
    @_object_events.key?(event) && !@_object_events[event].empty?
  else
    @_object_events.any? { |_type, handlers| !handlers.empty? }
  end
end

#off(descriptor) ⇒ Object

Remove a per-object event handler (or several, given an array).



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/ruby2d/interactive.rb', line 49

def off(descriptor)
  return descriptor.each { |d| off(d) } if descriptor.is_a?(Array)

  unless descriptor.is_a?(Window::ObjectEventDescriptor)
    raise Error,
          "Cannot remove event handler: expected a descriptor returned by `on`, got #{descriptor.inspect}"
  end

  return unless @_object_events

  handlers = @_object_events[descriptor.type]
  return unless handlers

  handlers.delete(descriptor.id)
  @_object_events.delete(descriptor.type) if handlers.empty?

  Window.unregister_interactive(self) unless interactive?
end

#on(event = nil, **filters, &proc) ⇒ Object

Register a per-object event handler. Two forms:

obj.on(:click) { |event| ... }
obj.on(click: :left) { ... }                   # filtered
obj.on(click: [:left, :right]) { ... }         # array → match any
obj.on(mouse_down: :left, click: :left) { ... }  # multi-event

Raises:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ruby2d/interactive.rb', line 29

def on(event = nil, **filters, &proc)
  raise Error, '`on` requires a block' unless proc
  if event.is_a?(Symbol) && filters.empty?
    raise Error, "`#{event}` is not a valid object event" unless OBJECT_EVENTS.include?(event)
    register_object_event_handler(event, proc)
  elsif event.nil? && !filters.empty?
    descriptors = filters.map do |type, matcher|
      predicate = OBJECT_EVENT_FILTER_PREDICATES[type] or
        raise Error, "`#{type}` does not support filtering with `on event: value`"
      values = Array(matcher)
      wrapped = ->(e) { proc.call(e) if values.any? { |v| e.send(predicate, v) } }
      register_object_event_handler(type, wrapped)
    end
    descriptors.size == 1 ? descriptors.first : descriptors
  else
    raise Error, '`on` requires either an event symbol or event filters'
  end
end