fusuma-plugin-touchscreen 0.0.1.alpha → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -0
  3. data/README.md +71 -2
  4. data/fusuma-plugin-touchscreen.gemspec +3 -2
  5. data/lib/fusuma/plugin/buffers/touch_buffer.rb +17 -81
  6. data/lib/fusuma/plugin/detectors/touch_detector.rb +31 -14
  7. data/lib/fusuma/plugin/detectors/touch_detectors/base.rb +7 -0
  8. data/lib/fusuma/plugin/detectors/touch_detectors/edge_detector.rb +0 -0
  9. data/lib/fusuma/plugin/detectors/touch_detectors/hold_detector.rb +11 -3
  10. data/lib/fusuma/plugin/detectors/touch_detectors/pinch_detector.rb +59 -1
  11. data/lib/fusuma/plugin/detectors/touch_detectors/rotate_detector.rb +65 -1
  12. data/lib/fusuma/plugin/detectors/touch_detectors/swipe_detector.rb +11 -9
  13. data/lib/fusuma/plugin/detectors/touch_detectors/tap_detector.rb +2 -2
  14. data/lib/fusuma/plugin/detectors/touch_detectors/tap_hold_base.rb +21 -0
  15. data/lib/fusuma/plugin/devices/touchscreen_device.rb +0 -0
  16. data/lib/fusuma/plugin/events/records/touch_record.rb +14 -1
  17. data/lib/fusuma/plugin/events/records/touch_records/base.rb +2 -0
  18. data/lib/fusuma/plugin/events/records/touch_records/features/direction.rb +36 -0
  19. data/lib/fusuma/plugin/events/records/touch_records/hold_record.rb +0 -0
  20. data/lib/fusuma/plugin/events/records/touch_records/pinch_record.rb +23 -0
  21. data/lib/fusuma/plugin/events/records/touch_records/rotate_record.rb +23 -0
  22. data/lib/fusuma/plugin/events/records/touch_records/swipe_record.rb +2 -12
  23. data/lib/fusuma/plugin/events/records/touch_records/tap_record.rb +0 -0
  24. data/lib/fusuma/plugin/parsers/touch_parser.rb +2 -47
  25. data/lib/fusuma/plugin/touchscreen/math.rb +68 -0
  26. data/lib/fusuma/plugin/touchscreen/version.rb +1 -1
  27. data/lib/fusuma/plugin/touchscreen.rb +0 -0
  28. data/spec/fusuma/plugin/buffers/touch_buffer_spec.rb +216 -0
  29. data/spec/fusuma/plugin/detectors/hold_detector_spec.rb +71 -0
  30. data/spec/fusuma/plugin/detectors/pinch_detector_spec.rb +96 -0
  31. data/spec/fusuma/plugin/detectors/rotate_detector_spec.rb +123 -0
  32. data/spec/fusuma/plugin/detectors/swipe_detector_spec.rb +141 -0
  33. data/spec/fusuma/plugin/detectors/tap_detector_spec.rb +84 -0
  34. data/spec/fusuma/plugin/detectors/touch_detector_spec.rb +266 -0
  35. data/spec/fusuma/plugin/devices/touchscreen_device_spec.rb +6 -3
  36. data/spec/fusuma/plugin/parsers/touch_parser_spec.rb +30 -5
  37. data/spec/samples/1-finger-hold.txt +0 -0
  38. data/spec/samples/2-fingers-rotate-clockwise.txt +426 -0
  39. data/spec/samples/2-fingers-rotate-counterclockwise.txt +459 -0
  40. data/spec/samples/2-fingers-swipe-right.txt +0 -0
  41. data/spec/samples/3-fingers-pinch-in.txt +140 -0
  42. data/spec/samples/3-fingers-pinch-out.txt +171 -0
  43. data/spec/samples/3-fingers-tap.txt +0 -0
  44. data/spec/{fixtures/libinput-list-devices_ms-surface-3-pro.txt → samples/libinput-list-devices.txt} +0 -0
  45. data/spec/spec_helper.rb +8 -0
  46. data/spec/support/helpers/events_and_records.rb +43 -0
  47. data/spec/support/shared_contexts/with_touch_buffer.rb +10 -0
  48. data/spec/support/shared_examples/real_sample.rb +29 -0
  49. metadata +40 -10
  50. data/lib/fusuma/utils/angle.rb +0 -12
  51. data/spec/samples/libinput-devices.txt +0 -182
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 382351b2d3d10f48ec04e2c6615805d8dbc1861ca22521728104a223077864e1
4
- data.tar.gz: ec9eb76e6b29b083eb6e43d94876251bc8326eba145ea338ed314bc76a324ce8
3
+ metadata.gz: 34475baf5bb86b2a999b1d4184e07207f6cd9b978c9c593650cfb3198b7431c6
4
+ data.tar.gz: f83b7d67b09d75403f1aa1232ea24980d34faf6a09f1dfd38b5ea3f441f8d101
5
5
  SHA512:
6
- metadata.gz: 3052cd87167d7db34cf511302472765b33d0956a66477068a271d8c525c75fe35281ea83db6172bcb22cd11e032e3ee447813affc7a70a84390369f55c26d127
7
- data.tar.gz: ec2bd3533144d81b222a57ab82b982cf625a8dfdcaf4416d00845b2ce0456edbb93e44fbe96117426e71ad73154d2906354cec357fccdbae1ae39f0df1a41f55
6
+ metadata.gz: 9d62633fd2feeacd1bcb880919b4665381d804bf0b80526ecb0b985488ad8be71fbbc6a41bf43ebf2c2b2d668c92856d529e9a99ef6a8e95dc2a9c6a2ede24e0
7
+ data.tar.gz: 8a411319e9ad71cdcf7d97c79c80d72a668c1858fa2c67bd8b0ae081733926157451a25da9a4805ab5d68e3c0d33c309b94ad25539e47267abdae12715632a43
data/LICENSE CHANGED
File without changes
data/README.md CHANGED
@@ -1,2 +1,71 @@
1
- # fusuma-plugin-touchscreen
2
- The Fusuma plugin for touchscreens
1
+ # Fusuma::Plugin::Touchscreen
2
+
3
+ The Fusuma plugin for touchscreens.
4
+
5
+ ## Fusuma
6
+
7
+ Fusuma is a multitouch gesture recognizer for Linux desktops.
8
+ You can read more about it on [the Fusuma GitHub page](https://github.com/iberianpig/fusuma).
9
+
10
+ This plugin adds support for touchscreens.
11
+
12
+ ## Installation
13
+
14
+ First you need to [install Fusuma](https://github.com/iberianpig/fusuma#installation).
15
+
16
+ Then the plugin can be installed as [any other Fusuma plugin](https://github.com/iberianpig/fusuma#fusuma-plugins):
17
+
18
+ ```bash
19
+ sudo gem install fusuma-plugin-touchscreen
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ If your Fusuma is already configured, you don't need to do anything else.
25
+ The plugin uses the very same configuration file / entries.
26
+
27
+ (So yes, your touchpad and your touchscreen will share the same gestures.)
28
+
29
+ Read more [how to configure Fusuma](https://github.com/iberianpig/fusuma#customize-gesture-mapping).
30
+
31
+ ## Supported features
32
+
33
+ Tap, Hold, Swipe, Pinch, Rotate.
34
+
35
+ One or more fingers, as many as your device supports (through libinput).
36
+
37
+ begin / update / end events (for all but Tap of course).
38
+
39
+ ## Known issues
40
+
41
+ As Fusuma itself, this plugin depends on the output of the `libinput debug-events`
42
+ command which may be unstable.
43
+
44
+ It is a plan to write some code to interact with libinput directly,
45
+ but that's a task for the future.
46
+
47
+ Threshold options are hard-coded and not configurable yet. That would be the next step.
48
+
49
+ ## Possible issues
50
+
51
+ This plugin is tested on Microsoft Surface Pro 3 only.
52
+ If you can test it on other devices, please share your experience.
53
+
54
+ ## Contributing
55
+
56
+ Bug reports and pull requests are welcome
57
+ on GitHub at https://github.com/Phaengris/fusuma-plugin-touchscreen
58
+
59
+ This project is intended to be a safe, welcoming space for collaboration,
60
+ and contributors are expected to adhere to
61
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
62
+
63
+ ## License
64
+
65
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
66
+
67
+ ## Code of Conduct
68
+
69
+ Everyone interacting in the Fusuma::Plugin::Touchscreen project’s codebases,
70
+ issue trackers, chat rooms and mailing lists is expected to follow
71
+ the [code of conduct](https://github.com/iberianpig/fusuma-plugin-tap/blob/master/CODE_OF_CONDUCT.md).
@@ -22,7 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.required_ruby_version = '>= 2.3' # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
26
-
25
+ spec.required_ruby_version = ">= 2.5.1" # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
26
+ # support bionic (18.04LTS) 2.5.1
27
+
27
28
  spec.add_dependency 'fusuma', '~> 2.0'
28
29
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fusuma/plugin/buffers/buffer'
4
+ require 'fusuma/plugin/touchscreen/math'
5
+
3
6
  module Fusuma
4
7
  module Plugin
5
8
  module Buffers
@@ -34,24 +37,12 @@ module Fusuma
34
37
  end
35
38
 
36
39
  def events
37
- raise "Not supported, use finger_events_map instead"
40
+ raise NoMethodError, "Not supported, use finger_events_map instead"
38
41
  end
39
42
 
40
43
  def clear_expired(current_time: Time.now)
41
44
  @seconds_to_keep ||= (config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP)
42
45
 
43
- # @finger_events_map.each do |finger, events|
44
- # next if events.empty?
45
- #
46
- # if events.last.record.status == "end"
47
- # @finger_events_map.delete finger
48
- # else
49
- # @finger_events_map[finger].select! do |e|
50
- # current_time - e.time < @seconds_to_keep
51
- # end
52
- # @finger_events_map.delete finger if @finger_events_map[finger].empty?
53
- # end
54
- # end
55
46
  clear if ended?
56
47
  @finger_events_map.each do |finger, events|
57
48
  next if events.empty?
@@ -89,11 +80,13 @@ module Fusuma
89
80
 
90
81
  def moved?
91
82
  # TODO: a quicker way to do this?
92
- @mem[:moved] ||= finger_movements.any? && finger_movements.all? { |finger, movement| movement[:distance] > jitter_threshold }
83
+ @mem[:moved] ||= @finger_events_map.any? && @finger_events_map.keys.all? { |finger| finger_movements.key?(finger) }
93
84
  end
94
85
 
95
86
  def duration
96
- @mem[:duration] ||= if ended?
87
+ @mem[:duration] ||= if @finger_events_map.empty?
88
+ 0
89
+ elsif ended?
97
90
  end_time - begin_time
98
91
  else
99
92
  Time.now - begin_time
@@ -113,78 +106,21 @@ module Fusuma
113
106
  position_events = events.select { |e| e.record.position? }
114
107
  next if position_events.size < 2 # we need at least first and last position
115
108
 
116
- first_position = position_events.first
117
- last_position = position_events.last
118
-
119
- case
120
- when (first_position.record.x_mm - last_position.record.x_mm).abs < axis_threshold
121
- orientation = :vertical
122
- direction = (last_position.record.y_mm - first_position.record.y_mm) <=> 0
123
- x_axis = (first_position.record.x_mm + last_position.record.x_mm) / 2.0
124
- when (first_position.record.y_mm - last_position.record.y_mm).abs < axis_threshold
125
- orientation = :horizontal
126
- direction = (last_position.record.x_mm - first_position.record.x_mm) <=> 0
127
- y_axis = (first_position.record.y_mm + last_position.record.y_mm) / 2.0
128
- else
129
- orientation = :diagonal
130
- k = (first_position.record.y_mm - last_position.record.y_mm) / (first_position.record.x_mm - last_position.record.x_mm)
131
- b = first_position.record.y_mm - k * first_position.record.x_mm
132
- direction_x = (last_position.record.x_mm - first_position.record.x_mm) <=> 0
133
- direction_y = (last_position.record.y_mm - first_position.record.y_mm) <=> 0
134
- end
109
+ first_position = { x: position_events.first.record.x_mm, y: position_events.first.record.y_mm }
110
+ last_position = { x: position_events.last.record.x_mm, y: position_events.last.record.y_mm }
111
+ distance = Touchscreen::Math.distance(first_position, last_position)
112
+ next if distance < movement_threshold
135
113
 
136
- prev_position = first_position
137
- catch(:interrupted_movement) do
138
- position_events.each do |position|
139
- delta_x = position.record.x_mm - prev_position.record.x_mm
140
- delta_y = position.record.y_mm - prev_position.record.y_mm
141
-
142
- jitter_x = delta_x.abs < jitter_threshold
143
- jitter_y = delta_y.abs < jitter_threshold
144
- next if jitter_x && jitter_y
145
-
146
- case orientation
147
- when :vertical
148
- throw(:interrupted_movement) unless jitter_y || (delta_y <=> 0) == direction
149
- throw(:interrupted_movement) unless (position.record.x_mm - x_axis).abs < jitter_threshold
150
- when :horizontal
151
- throw(:interrupted_movement) unless jitter_x || (delta_x <=> 0) == direction
152
- throw(:interrupted_movement) unless (position.record.y_mm - y_axis).abs < jitter_threshold
153
- else
154
- throw(:interrupted_movement) unless jitter_x || (delta_x <=> 0) == direction_x
155
- throw(:interrupted_movement) unless jitter_y || (delta_y <=> 0) == direction_y
156
- throw(:interrupted_movement) unless (position.record.y_mm - k * position.record.x_mm - b).abs < jitter_threshold
157
- end
158
- prev_position = position
159
- end
160
-
161
- case orientation
162
- when :vertical
163
- angle = direction == 1 ? 90 : 270
164
- distance = (last_position.record.y_mm - first_position.record.y_mm).abs
165
- when :horizontal
166
- angle = direction == 1 ? 0 : 180
167
- distance = (last_position.record.x_mm - first_position.record.x_mm).abs
168
- else
169
- angle = (Math.atan(k) * 180 / Math::PI).round
170
- angle += 360 if angle < 0
171
- distance = Math.sqrt((last_position.record.x_mm - first_position.record.x_mm)**2 + (last_position.record.y_mm - first_position.record.y_mm)**2)
172
- end
173
- next if distance < jitter_threshold
174
-
175
- [finger, { angle: angle, distance: distance }]
176
- end
114
+ # TODO: check if there were trajectory changes in between?
115
+
116
+ [finger, { first_position: first_position, last_position: last_position }]
177
117
  end.compact.to_h
178
118
  end
179
119
 
180
120
  private
181
121
 
182
- def axis_threshold
183
- 2.0 # TODO: make it configurable
184
- end
185
-
186
- def jitter_threshold
187
- 5.0 # TODO: configurable
122
+ def movement_threshold
123
+ 0.5 # TODO: configurable
188
124
  end
189
125
 
190
126
  def reset_memoized
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fusuma/plugin/detectors/detector'
4
+
3
5
  require_relative './touch_detectors/tap_detector'
4
6
  require_relative './touch_detectors/hold_detector'
5
7
  require_relative './touch_detectors/swipe_detector'
@@ -16,13 +18,13 @@ module Fusuma
16
18
  def initialize(*)
17
19
  super
18
20
  @detectors = [
19
- Fusuma::Plugin::Detectors::TouchDetectors::TapDetector,
20
- Fusuma::Plugin::Detectors::TouchDetectors::SwipeDetector,
21
- # Fusuma::Plugin::Detectors::TouchDetectors::PinchDetector,
22
- # Fusuma::Plugin::Detectors::TouchDetectors::RotateDetector,
23
- # Fusuma::Plugin::Detectors::TouchDetectors::EdgeDetector
24
- ].map(&:new)
25
- @detectors << (@hold_detector = Fusuma::Plugin::Detectors::TouchDetectors::HoldDetector.new)
21
+ Fusuma::Plugin::Detectors::TouchDetectors::TapDetector.new,
22
+ (@hold_detector = Fusuma::Plugin::Detectors::TouchDetectors::HoldDetector.new),
23
+ Fusuma::Plugin::Detectors::TouchDetectors::SwipeDetector.new,
24
+ Fusuma::Plugin::Detectors::TouchDetectors::PinchDetector.new,
25
+ # Fusuma::Plugin::Detectors::TouchDetectors::RotateDetector.new,
26
+ # Fusuma::Plugin::Detectors::TouchDetectors::EdgeDetector.new
27
+ ]
26
28
  @last_known_gesture = nil
27
29
  end
28
30
 
@@ -51,10 +53,11 @@ module Fusuma
51
53
  return events if touch_buffer.nil? || touch_buffer.empty?
52
54
  end
53
55
 
54
- if @touch_buffer.ended? && @last_known_gesture
55
- events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture.record.repeatable?
56
- @last_known_gesture = nil
57
- end
56
+ # current gesture ended or even new gesture began
57
+ # if (@touch_buffer.ended? || @touch_buffer.began?) && @last_known_gesture
58
+ # events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture.record.repeatable?
59
+ # @last_known_gesture = nil
60
+ # end
58
61
 
59
62
  gesture_record = nil
60
63
  if touch_buffer
@@ -68,16 +71,30 @@ module Fusuma
68
71
  end
69
72
 
70
73
  if gesture_record
71
- @touch_buffer.clear
74
+ # repeat previous gesture
72
75
  if gesture_record.repeatable? && @last_known_gesture&.record == gesture_record
73
76
  @last_known_gesture = create_event(record: gesture_record)
74
- events << create_event(record: gesture_record.create_index_record(status: 'update', trigger: :repeat))
77
+ if touch_buffer.ended?
78
+ events << create_event(record: gesture_record.create_index_record(status: 'end', trigger: :repeat))
79
+ else
80
+ events << create_event(record: gesture_record.create_index_record(status: 'update', trigger: :repeat))
81
+ end
82
+ # new gesture (repeatable or not)
75
83
  else
76
84
  events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture&.record&.repeatable?
77
85
  @last_known_gesture = create_event(record: gesture_record)
78
86
  events << create_event(record: gesture_record.create_index_record)
79
- events << create_event(record: gesture_record.create_index_record(status: 'begin', trigger: :repeat)) if gesture_record.repeatable?
87
+ if gesture_record.repeatable?
88
+ events << create_event(record: gesture_record.create_index_record(status: 'begin', trigger: :repeat))
89
+ # already ended? (in this very same tick)
90
+ events << create_event(record: gesture_record.create_index_record(status: 'end', trigger: :repeat)) if touch_buffer.ended?
91
+ end
80
92
  end
93
+ @touch_buffer.clear
94
+ # no new gesture, but may be the previous one ended?
95
+ elsif @touch_buffer&.ended? && @last_known_gesture
96
+ events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture.record.repeatable?
97
+ @last_known_gesture = nil
81
98
  end
82
99
 
83
100
  events
@@ -3,6 +3,9 @@ module Fusuma
3
3
  module Detectors
4
4
  module TouchDetectors
5
5
  class Base
6
+ def detect
7
+ raise NotImplementedError, "override #{self.class.name}##{__method__}"
8
+ end
6
9
 
7
10
  protected
8
11
 
@@ -10,6 +13,10 @@ module Fusuma
10
13
  0.5 # TODO: configurable
11
14
  end
12
15
 
16
+ def jitter_threshold
17
+ 5.0 # TODO: configurable
18
+ end
19
+
13
20
  end
14
21
  end
15
22
  end
@@ -1,16 +1,24 @@
1
- require_relative './base'
1
+ require_relative './tap_hold_base'
2
2
 
3
3
  module Fusuma
4
4
  module Plugin
5
5
  module Detectors
6
6
  module TouchDetectors
7
- class HoldDetector < Base
7
+ class HoldDetector < TapHoldBase
8
8
 
9
9
  def detect(touch_buffer)
10
10
  MultiLogger.debug('> hold detector')
11
11
 
12
12
  MultiLogger.debug(' no movement?')
13
- return if touch_buffer.moved?
13
+ if touch_buffer.moved?
14
+ touch_buffer.finger_movements.each do |finger, movement|
15
+ distance = Touchscreen::Math.distance(movement[:first_position], movement[:last_position])
16
+ if distance > jitter_threshold
17
+ MultiLogger.debug(" finger #{finger} moved too much: #{distance} > #{jitter_threshold}")
18
+ return
19
+ end
20
+ end
21
+ end
14
22
 
15
23
  MultiLogger.debug(" tap / hold threshold? (duration #{touch_buffer.duration})")
16
24
  return unless touch_buffer.duration > tap_hold_threshold
@@ -1,4 +1,5 @@
1
1
  require_relative './base'
2
+ require 'fusuma/plugin/touchscreen/math'
2
3
 
3
4
  module Fusuma
4
5
  module Plugin
@@ -6,7 +7,64 @@ module Fusuma
6
7
  module TouchDetectors
7
8
  class PinchDetector < Base
8
9
 
9
- end
10
+ def detect(touch_buffer)
11
+ MultiLogger.debug('> pinch detector')
12
+
13
+ MultiLogger.debug(' movement?')
14
+ return unless touch_buffer.moved?
15
+
16
+ MultiLogger.debug(' at least 2 fingers?')
17
+ return unless touch_buffer.finger_movements.size >= 2
18
+
19
+ MultiLogger.debug(' distance change between first 2 fingers?')
20
+ first_finger, second_finger = touch_buffer.finger_movements.values[0..1]
21
+ begin_distance = Touchscreen::Math.distance(first_finger[:first_position], second_finger[:first_position])
22
+ end_distance = Touchscreen::Math.distance(first_finger[:last_position], second_finger[:last_position])
23
+ distance = end_distance - begin_distance
24
+ return unless distance.abs > jitter_threshold
25
+
26
+ direction = distance <=> 0
27
+ MultiLogger.debug(" assuming direction is #{direction}, testing all pairs of fingers")
28
+ is_pinch = catch(:not_a_pinch) do
29
+ pairs = touch_buffer.finger_movements.keys.combination(2).to_a
30
+ pairs.each do |finger1, finger2|
31
+ begin_distance = Touchscreen::Math.distance(
32
+ touch_buffer.finger_movements[finger1][:first_position],
33
+ touch_buffer.finger_movements[finger2][:first_position]
34
+ )
35
+ end_distance = Touchscreen::Math.distance(
36
+ touch_buffer.finger_movements[finger1][:last_position],
37
+ touch_buffer.finger_movements[finger2][:last_position]
38
+ )
39
+ MultiLogger.debug(" fingers #{finger1} and #{finger2} moved from #{begin_distance} to #{end_distance}")
40
+ this_distance = end_distance - begin_distance
41
+ if this_distance.abs < jitter_threshold
42
+ MultiLogger.debug(" !distance between fingers #{finger1} and #{finger2} is not changed enough (#{this_distance.abs}), not a pinch")
43
+ throw(:not_a_pinch)
44
+ end
45
+ this_direction = this_distance <=> 0
46
+ if this_direction != direction
47
+ MultiLogger.debug(" !fingers #{finger1} and #{finger2} moved in direction #{this_direction}, not a pinch")
48
+ throw(:not_a_pinch)
49
+ end
50
+ end
51
+ true
52
+ end
53
+ return unless is_pinch
54
+
55
+ direction = direction == -1 ? :in : :out
56
+ MultiLogger.debug(" pinch #{direction} detected!")
57
+
58
+ Plugin::Events::Records::TouchRecords::PinchRecord.new(finger: touch_buffer.finger, direction: direction)
59
+ end
60
+
61
+ private
62
+
63
+ def jitter_threshold
64
+ 5.0 # TODO: configurable
65
+ end
66
+
67
+ end # class PinchDetector
10
68
  end
11
69
  end
12
70
  end
@@ -1,4 +1,5 @@
1
1
  require_relative './base'
2
+ require 'fusuma/plugin/touchscreen/math'
2
3
 
3
4
  module Fusuma
4
5
  module Plugin
@@ -6,7 +7,70 @@ module Fusuma
6
7
  module TouchDetectors
7
8
  class RotateDetector < Base
8
9
 
9
- end
10
+ def detect(touch_buffer)
11
+ MultiLogger.debug('> rotate detector')
12
+
13
+ MultiLogger.debug(' movement?')
14
+ return unless touch_buffer.moved?
15
+
16
+ MultiLogger.debug(' at least 2 fingers?')
17
+ return unless touch_buffer.finger_movements.size >= 2
18
+
19
+ # MultiLogger.debug(' center is static?')
20
+ # begin_center = Touchscreen::Math.center(touch_buffer.finger_movements.map { |_, v| v[:first_position] })
21
+ # end_center = Touchscreen::Math.center(touch_buffer.finger_movements.map { |_, v| v[:last_position] })
22
+ # return unless Touchscreen::Math.distance(begin_center, end_center) <= center_jitter_threshold
23
+
24
+ MultiLogger.debug(' angle change for the first finger?')
25
+ center = Touchscreen::Math.center(touch_buffer.finger_movements.map { |_, v| v[:first_position] })
26
+ first_finger = touch_buffer.finger_movements.values.first
27
+ begin_angle = Touchscreen::Math.angle_between(center, first_finger[:first_position])
28
+ end_angle = Touchscreen::Math.angle_between(center, first_finger[:last_position])
29
+ angle_change = Touchscreen::Math.angles_difference(end_angle, begin_angle)
30
+ MultiLogger.debug(" (#{angle_change})")
31
+ return unless angle_change.abs > angle_threshold
32
+
33
+ direction = angle_change <=> 0
34
+ MultiLogger.debug(" assuming direction is #{direction} (from #{begin_angle} to #{end_angle}), testing all fingers")
35
+
36
+ is_rotate = catch(:not_a_rotate) do
37
+ touch_buffer.finger_movements.except(0).each do |finger, movement|
38
+ begin_angle = Touchscreen::Math.angle_between(center, movement[:first_position])
39
+ end_angle = Touchscreen::Math.angle_between(center, movement[:last_position])
40
+ this_angle_change = Touchscreen::Math.angles_difference(end_angle, begin_angle)
41
+ if this_angle_change.abs < angle_threshold
42
+ MultiLogger.debug(" !angle for finger #{finger} is not changed enough (#{this_angle_change.abs}), not a rotate")
43
+ throw(:not_a_rotate)
44
+ end
45
+ this_direction = this_angle_change <=> 0
46
+ if this_direction != direction
47
+ MultiLogger.debug(" !finger #{finger} moved in direction #{this_direction} (from #{begin_angle} to #{end_angle}, not a rotate")
48
+ throw(:not_a_rotate)
49
+ end
50
+ end
51
+ true
52
+ end
53
+ return unless is_rotate
54
+
55
+ direction = direction == -1 ? :clockwise : :counterclockwise
56
+ MultiLogger.debug(" rotate #{direction} detected!")
57
+
58
+ Plugin::Events::Records::TouchRecords::RotateRecord.new(finger: touch_buffer.finger, direction: direction)
59
+ end
60
+
61
+ private
62
+
63
+ def angle_threshold
64
+ # such low value is because of how a human usually rotates fingers
65
+ # it is barely possible to draw a geometrically perfect circle
66
+ # in reality one finger can move very little, and another can move a lot
67
+ # so for the "base" finger the angle change will be very small, and for the "moving" finger it will be big
68
+ # if we increase the threshold, we will get a lot of false negatives
69
+ # which will make continuous rotate gestures very bulky
70
+ 0.1 # TODO: make configurable
71
+ end
72
+
73
+ end # class RotateDetector
10
74
  end
11
75
  end
12
76
  end
@@ -1,11 +1,11 @@
1
1
  require_relative './base'
2
- require_relative '../../../utils/angle'
2
+ require 'fusuma/plugin/touchscreen/math'
3
3
 
4
4
  module Fusuma
5
5
  module Plugin
6
6
  module Detectors
7
7
  module TouchDetectors
8
- class SwipeDetector
8
+ class SwipeDetector < Base
9
9
 
10
10
  def detect(touch_buffer)
11
11
  MultiLogger.debug('> swipe detector')
@@ -14,24 +14,26 @@ module Fusuma
14
14
  return unless touch_buffer.moved?
15
15
 
16
16
  MultiLogger.debug(' angles?')
17
- angles = touch_buffer.finger_movements.map { |_, movement| movement[:angle] }
17
+ angles = touch_buffer.finger_movements.map do |_, movement|
18
+ Touchscreen::Math.angle_between(movement[:first_position], movement[:last_position])
19
+ end
18
20
  angles.combination(2).each do |angle1, angle2|
19
- if Utils::Angle.difference(angle1, angle2) > movement_angle_threshold
21
+ if Touchscreen::Math.angles_difference(angle1, angle2).abs > movement_angle_threshold
20
22
  MultiLogger.debug(" !too much difference between #{angle1} and #{angle2}, not a swipe")
21
23
  return
22
24
  end
23
25
  end
24
- angle = angles.inject(:+).to_f / angles.size
26
+ angle = Touchscreen::Math.angles_average(angles)
25
27
 
26
28
  MultiLogger.debug(' direction?')
27
29
  case angle
28
- when 0...(direction_angle_width / 2), (360 - (direction_angle_width / 2))...360
30
+ when 0..(direction_angle_width / 2), (360 - (direction_angle_width / 2))..360
29
31
  direction = :right
30
- when (90 - (direction_angle_width / 2))...(90 + (direction_angle_width / 2))
32
+ when (90 - (direction_angle_width / 2))..(90 + (direction_angle_width / 2))
31
33
  direction = :down
32
- when (180 - (direction_angle_width / 2))...(180 + (direction_angle_width / 2))
34
+ when (180 - (direction_angle_width / 2))..(180 + (direction_angle_width / 2))
33
35
  direction = :left
34
- when (270 - (direction_angle_width / 2))...(270 + (direction_angle_width / 2))
36
+ when (270 - (direction_angle_width / 2))..(270 + (direction_angle_width / 2))
35
37
  direction = :up
36
38
  else
37
39
  MultiLogger.debug(" !gesture angle of #{angle} does not fall into any direction")
@@ -1,10 +1,10 @@
1
- require_relative './base'
1
+ require_relative './tap_hold_base'
2
2
 
3
3
  module Fusuma
4
4
  module Plugin
5
5
  module Detectors
6
6
  module TouchDetectors
7
- class TapDetector < Base
7
+ class TapDetector < TapHoldBase
8
8
 
9
9
  def detect(touch_buffer)
10
10
  MultiLogger.debug('> tap detector')
@@ -0,0 +1,21 @@
1
+ module Fusuma
2
+ module Plugin
3
+ module Detectors
4
+ module TouchDetectors
5
+ class TapHoldBase
6
+
7
+ protected
8
+
9
+ def tap_hold_threshold
10
+ 0.5 # TODO: configurable
11
+ end
12
+
13
+ def jitter_threshold
14
+ 5.0 # TODO: configurable
15
+ end
16
+
17
+ end # class TapHoldBase
18
+ end
19
+ end
20
+ end
21
+ end
File without changes
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ require 'fusuma/plugin/events/records/record'
2
2
 
3
3
  require_relative './touch_records/tap_record'
4
4
  require_relative './touch_records/hold_record'
@@ -31,6 +31,19 @@ module Fusuma
31
31
  def position?
32
32
  @x_px || @y_px
33
33
  end
34
+
35
+ def inspect
36
+ s = "#{self.class} status: #{status} finger: #{finger} time offset: #{time_offset}"
37
+ s << " x_px: #{x_px}" if x_px
38
+ s << " y_px: #{y_px}" if y_px
39
+ s << " x_mm: #{x_mm}" if x_mm
40
+ s << " y_mm: #{y_mm}" if y_mm
41
+ s
42
+ end
43
+
44
+ def to_s
45
+ inspect
46
+ end
34
47
  end
35
48
  end
36
49
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fusuma/plugin/events/records/record'
4
+
3
5
  module Fusuma
4
6
  module Plugin
5
7
  module Events