fusuma-plugin-touchscreen 0.0.1.alpha → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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