Permalink
Browse files

Allow responses to specific grid buttons

  • Loading branch information...
1 parent 80f62de commit cba9ebe90f2e9f8c0fcd34644b801b58d03e83fe @thomasjachmann committed Mar 4, 2013
Showing with 139 additions and 5 deletions.
  1. +1 −1 README.rdoc
  2. +77 −4 lib/launchpad/interaction.rb
  3. +61 −0 test/test_interaction.rb
View
@@ -83,7 +83,6 @@ For more details, see the examples. examples/color_picker.rb is the most complex
== Future plans
-* interaction responses for presses on single grid buttons/button areas
* bitmap rendering
* internal tracking of LED states for both buffers
@@ -93,6 +92,7 @@ For more details, see the examples. examples/color_picker.rb is the most complex
* logging
* reworked multi threading for action handling
* compatibility with ruby 1.8.7 and 2.0.0
+* interaction responses for presses on single grid buttons/button areas/columns/rows
=== v.0.2.2
@@ -179,6 +179,10 @@ def stop
# [<tt>:exclusive</tt>] <tt>true/false</tt>,
# whether to deregister all other responses to the specified actions,
# optional, defaults to +false+
+ # [<tt>:x</tt>] x coordinate(s), can contain arrays and ranges, when specified
+ # without y coordinate, it's interpreted as a whole column
+ # [<tt>:y</tt>] y coordinate(s), can contain arrays and ranges, when specified
+ # without x coordinate, it's interpreted as a whole row
#
# Takes a block which will be called when an action matching the parameters occurs.
#
@@ -192,7 +196,11 @@ def response_to(types = :all, state = :both, opts = nil, &block)
opts ||= {}
no_response_to(types, state) if opts[:exclusive] == true
Array(state == :both ? %w(down up) : state).each do |state|
- types.each {|type| responses[type.to_sym][state.to_sym] << block}
+ types.each do |type|
+ combined_types(type, opts).each do |combined_type|
+ responses[combined_type][state.to_sym] << block
+ end
+ end
end
nil
end
@@ -207,11 +215,22 @@ def response_to(types = :all, state = :both, opts = nil, &block)
# optional, defaults to +nil+, meaning "all responses"
# [+state+] button state to respond to,
# additional value <tt>:both</tt>
- def no_response_to(types = nil, state = :both)
+ #
+ # Optional options hash:
+ #
+ # [<tt>:x</tt>] x coordinate(s), can contain arrays and ranges, when specified
+ # without y coordinate, it's interpreted as a whole column
+ # [<tt>:y</tt>] y coordinate(s), can contain arrays and ranges, when specified
+ # without x coordinate, it's interpreted as a whole row
+ def no_response_to(types = nil, state = :both, opts = nil)
logger.debug "removing response to #{types.inspect} for state #{state.inspect}"
types = Array(types)
Array(state == :both ? %w(down up) : state).each do |state|
- types.each {|type| responses[type.to_sym][state.to_sym].clear}
+ types.each do |type|
+ combined_types(type, opts).each do |combined_type|
+ responses[combined_type][state.to_sym].clear
+ end
+ end
end
nil
end
@@ -239,6 +258,52 @@ def respond_to(type, state, opts = nil)
def responses
@responses ||= Hash.new {|hash, key| hash[key] = {:down => [], :up => []}}
end
+
+ # Returns an array of grid positions for a range.
+ #
+ # Parameters:
+ #
+ # [+range+] the range definitions, can be
+ # * a Fixnum
+ # * a Range
+ # * an Array of Fixnum, Range or Array objects
+ def grid_range(range)
+ return nil if range.nil?
+ Array(range).flatten.map do |pos|
+ pos.respond_to?(:to_a) ? pos.to_a : pos
+ end.flatten.uniq
+ end
+
+ # Returns a list of combined types for the type and opts specified. Combined
+ # types are just the type, except for grid, where the opts are interpreted
+ # and all combinations of x and y coordinates are added as a position suffix.
+ #
+ # Example:
+ #
+ # combined_types(:grid, :x => 1..2, y => 2) => [:grid12, :grid22]
+ #
+ # Parameters (see Launchpad for values):
+ #
+ # [+type+] type of the button
+ #
+ # Optional options hash:
+ #
+ # [<tt>:x</tt>] x coordinate(s), can contain arrays and ranges, when specified
+ # without y coordinate, it's interpreted as a whole column
+ # [<tt>:y</tt>] y coordinate(s), can contain arrays and ranges, when specified
+ # without x coordinate, it's interpreted as a whole row
+ def combined_types(type, opts = nil)
+ if type.to_sym == :grid && opts
+ x = grid_range(opts[:x])
+ y = grid_range(opts[:y])
+ return [:grid] if x.nil? && y.nil? # whole grid
+ x ||= ['-'] # whole row
+ y ||= ['-'] # whole column
+ x.product(y).map {|x, y| :"grid#{x}#{y}"}
+ else
+ [type.to_sym]
+ end
+ end
# Reponds to an action by executing all matching responses.
#
@@ -248,7 +313,15 @@ def responses
def respond_to_action(action)
type = action[:type].to_sym
state = action[:state].to_sym
- (responses[type][state] + responses[:all][state]).each {|block| block.call(self, action)}
+ actions = []
+ if type == :grid
+ actions += responses[:"grid#{action[:x]}#{action[:y]}"][state]
+ actions += responses[:"grid#{action[:x]}-"][state]
+ actions += responses[:"grid-#{action[:y]}"][state]
+ end
+ actions += responses[type][state]
+ actions += responses[:all][state]
+ actions.compact.each {|block| block.call(self, action)}
nil
rescue Exception => e
logger.error "error when responding to action #{action.inspect}: #{e.inspect}"
View
@@ -13,6 +13,24 @@ def timeout(timeout = 0.02, &block)
false
end
+ def press(interaction, type, opts = nil)
+ interaction.respond_to(type, :down, opts)
+ interaction.respond_to(type, :up, opts)
+ end
+
+
+ def press_all(interaction)
+ %w(up down left right session user1 user2 mixer).each do |type|
+ press(interaction, type.to_sym)
+ end
+ 8.times do |y|
+ 8.times do |x|
+ press(interaction, :grid, :x => x, :y => y)
+ end
+ press(interaction, :"scene#{y + 1}")
+ end
+ end
+
describe '#initialize' do
it 'creates device if not given' do
@@ -367,6 +385,49 @@ def @device.read_pending_actions
@interaction.respond_to(:up, :down)
assert_equal [:up, :down, :up], @downs
end
+
+ describe 'allows to bind to specific grid buttons' do
+
+ before do
+ @downs = []
+ @action = lambda {|i, a| @downs << [a[:x], a[:y]]}
+ end
+
+ it 'one specific grid button' do
+ @interaction.response_to(:grid, :down, :x => 4, :y => 2, &@action)
+ press_all @interaction
+ assert_equal [[4, 2]], @downs
+ end
+
+ it 'a complete row of grid buttons' do
+ @interaction.response_to(:grid, :down, :y => 2, &@action)
+ press_all @interaction
+ assert_equal [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2]], @downs
+ end
+
+ it 'a complete column of grid buttons' do
+ @interaction.response_to(:grid, :down, :x => 3, &@action)
+ press_all @interaction
+ assert_equal [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7]], @downs
+ end
+
+ it 'a complex range of grid buttons' do
+ @interaction.response_to(:grid, :down, :x => [1,[2]], :y => [1, 3..5], &@action)
+ press_all @interaction
+ assert_equal [[1, 1], [2, 1], [1, 3], [2, 3], [1, 4], [2, 4], [1, 5], [2, 5]], @downs
+ end
+
+ it 'a specific grid buttons, a column, a row, all grid buttons and all buttons' do
+ @interaction.response_to(:all, :down) {|i, a| @downs << [a[:x], a[:y], :all]}
+ @interaction.response_to(:grid, :down) {|i, a| @downs << [a[:x], a[:y], :grid]}
+ @interaction.response_to(:grid, :down, :x => 0) {|i, a| @downs << [a[:x], a[:y], :col]}
+ @interaction.response_to(:grid, :down, :y => 0) {|i, a| @downs << [a[:x], a[:y], :row]}
+ @interaction.response_to(:grid, :down, :x => 0, :y => 0, &@action)
+ press @interaction, :grid, :x => 0, :y => 0
+ assert_equal [[0, 0], [0, 0, :col], [0, 0, :row], [0, 0, :grid], [0, 0, :all]], @downs
+ end
+
+ end
end

0 comments on commit cba9ebe

Please sign in to comment.