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
@@ -0,0 +1,36 @@
1
+ module Fusuma
2
+ module Plugin
3
+ module Events
4
+ module Records
5
+ module TouchRecords
6
+ module Features
7
+ module Direction
8
+
9
+ def self.prepended(base)
10
+ base.class_eval do
11
+ attr_reader :direction
12
+ end
13
+ end
14
+
15
+ def initialize(direction:, **args)
16
+ super(**args)
17
+ @direction = direction.to_s
18
+ end
19
+
20
+ def ==(other)
21
+ super(other) && direction == other.direction
22
+ end
23
+
24
+ protected
25
+
26
+ def config_index_keys
27
+ super << Config::Index::Key.new(@direction)
28
+ end
29
+
30
+ end # module WithDirection
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ require_relative './base'
2
+ require_relative './features/direction'
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module Fusuma
7
+ module Plugin
8
+ module Events
9
+ module Records
10
+ module TouchRecords
11
+ class PinchRecord < Base
12
+ prepend Features::Direction
13
+
14
+ def repeatable?
15
+ true
16
+ end
17
+
18
+ end # class PinchRecord
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require_relative './base'
2
+ require_relative './features/direction'
3
+
4
+ # frozen_string_literal: true
5
+
6
+ module Fusuma
7
+ module Plugin
8
+ module Events
9
+ module Records
10
+ module TouchRecords
11
+ class RotateRecord < Base
12
+ prepend Features::Direction
13
+
14
+ def repeatable?
15
+ true
16
+ end
17
+
18
+ end # class RotateRecord
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,5 @@
1
1
  require_relative './base'
2
+ require_relative './features/direction'
2
3
 
3
4
  # frozen_string_literal: true
4
5
 
@@ -8,23 +9,12 @@ module Fusuma
8
9
  module Records
9
10
  module TouchRecords
10
11
  class SwipeRecord < Base
11
-
12
- def initialize(direction:, **args)
13
- super(**args)
14
- @direction = direction.to_s
15
- end
12
+ prepend Features::Direction
16
13
 
17
14
  def repeatable?
18
15
  true
19
16
  end
20
17
 
21
- protected
22
-
23
- def config_index_keys
24
- super << Config::Index::Key.new(@direction)
25
- end
26
-
27
-
28
18
  end # class SwipeRecord
29
19
  end
30
20
  end
@@ -1,58 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fusuma/plugin/parsers/parser'
4
+
3
5
  module Fusuma
4
6
  module Plugin
5
7
  module Parsers
6
8
  class TouchParser < Parser
7
9
  DEFAULT_SOURCE = 'libinput_command_input'
8
10
 
9
- =begin
10
- 3-finger touch:
11
-
12
- event4 TOUCH_DOWN +0.000s 0 (0) 58.64/44.04 (148.16/73.74mm)
13
- event4 TOUCH_DOWN +0.000s 1 (1) 49.33/47.67 (124.63/79.84mm)
14
- event4 TOUCH_DOWN +0.000s 2 (2) 43.54/64.23 (110.00/107.56mm)
15
- event4 TOUCH_UP +0.079s 2 (2)
16
- event4 TOUCH_UP +0.099s 1 (1)
17
- event4 TOUCH_UP +0.108s 0 (0)
18
- =end
19
-
20
- =begin
21
- 2-finger swipe:
22
-
23
- event4 TOUCH_DOWN +0.000s 0 (0) 27.07/42.62 (68.39/71.37mm)
24
- event4 TOUCH_DOWN +0.000s 1 (1) 26.45/53.27 (66.82/89.21mm)
25
- event4 TOUCH_MOTION +0.051s 1 (1) 26.84/53.27 (67.82/89.21mm)
26
- event4 TOUCH_MOTION +0.061s 0 (0) 27.40/42.91 (69.24/71.86mm)
27
- event4 TOUCH_MOTION +0.061s 1 (1) 27.21/53.28 (68.74/89.23mm)
28
- event4 TOUCH_MOTION +0.071s 0 (0) 28.03/43.27 (70.82/72.47mm)
29
- event4 TOUCH_MOTION +0.071s 1 (1) 27.67/53.33 (69.92/89.30mm)
30
- event4 TOUCH_MOTION +0.082s 0 (0) 28.59/43.49 (72.24/72.84mm)
31
- event4 TOUCH_MOTION +0.082s 1 (1) 28.43/53.37 (71.84/89.37mm)
32
- event4 TOUCH_MOTION +0.091s 0 (0) 29.32/43.66 (74.08/73.12mm)
33
- event4 TOUCH_MOTION +0.091s 1 (1) 28.90/53.41 (73.03/89.44mm)
34
- event4 TOUCH_MOTION +0.101s 0 (0) 30.17/43.99 (76.24/73.67mm)
35
- event4 TOUCH_MOTION +0.101s 1 (1) 29.84/53.21 (75.39/89.12mm)
36
- event4 TOUCH_MOTION +0.111s 0 (0) 30.51/44.08 (77.08/73.81mm)
37
- event4 TOUCH_MOTION +0.111s 1 (1) 30.47/53.35 (76.97/89.35mm)
38
- event4 TOUCH_MOTION +0.121s 0 (0) 31.84/44.59 (80.45/74.67mm)
39
- event4 TOUCH_MOTION +0.121s 1 (1) 31.65/53.73 (79.97/89.98mm)
40
- event4 TOUCH_MOTION +0.131s 0 (0) 32.56/44.76 (82.26/74.95mm)
41
- event4 TOUCH_MOTION +0.131s 1 (1) 32.62/54.21 (82.42/90.79mm)
42
- event4 TOUCH_MOTION +0.141s 0 (0) 33.51/44.94 (84.66/75.26mm)
43
- event4 TOUCH_MOTION +0.141s 1 (1) 33.49/54.46 (84.61/91.21mm)
44
- event4 TOUCH_MOTION +0.151s 0 (0) 34.88/45.38 (88.13/76.00mm)
45
- event4 TOUCH_MOTION +0.151s 1 (1) 35.05/54.74 (88.55/91.67mm)
46
- event4 TOUCH_MOTION +0.162s 0 (0) 36.14/45.72 (91.32/76.56mm)
47
- event4 TOUCH_MOTION +0.162s 1 (1) 36.62/55.28 (92.53/92.58mm)
48
- event4 TOUCH_MOTION +0.171s 0 (0) 37.53/46.12 (94.82/77.23mm)
49
- event4 TOUCH_MOTION +0.171s 1 (1) 38.21/55.84 (96.55/93.51mm)
50
- event4 TOUCH_MOTION +0.181s 0 (0) 39.94/47.08 (100.92/78.84mm)
51
- event4 TOUCH_MOTION +0.181s 1 (1) 39.91/56.03 (100.84/93.84mm)
52
- event4 TOUCH_UP +0.220s 0 (0)
53
- event4 TOUCH_UP +0.220s 1 (1)
54
- =end
55
-
56
11
  def parse_record(record)
57
12
  MultiLogger.debug("#{self.class.name}##{__method__}")
58
13
  MultiLogger.debug(" record = #{record.inspect}")
@@ -0,0 +1,68 @@
1
+ module Fusuma
2
+ module Plugin
3
+ module Touchscreen
4
+ module Math
5
+
6
+ def self.angles_difference(angle1, angle2)
7
+ normalized_angle1 = normalize_angle(angle1)
8
+ normalized_angle2 = normalize_angle(angle2)
9
+
10
+ raw_difference = normalized_angle2 - normalized_angle1
11
+
12
+ if raw_difference < -180
13
+ raw_difference += 360
14
+ elsif raw_difference > 180
15
+ raw_difference -= 360
16
+ end
17
+
18
+ raw_difference
19
+ end
20
+
21
+ def self.normalize_angle(angle)
22
+ angle %= 360
23
+ angle < 0 ? angle + 360 : angle
24
+ end
25
+ private_class_method :normalize_angle
26
+
27
+ def self.angles_average(angles)
28
+ sum_x = 0.0
29
+ sum_y = 0.0
30
+
31
+ angles.each do |angle|
32
+ radians = angle * ::Math::PI / 180.0
33
+ sum_x += ::Math.cos(radians)
34
+ sum_y += ::Math.sin(radians)
35
+ end
36
+
37
+ average_radians = ::Math.atan2(sum_y, sum_x)
38
+ average_degrees = average_radians * 180.0 / ::Math::PI
39
+ average_degrees += 360.0 if average_degrees < 0
40
+
41
+ average_degrees
42
+ end
43
+
44
+ def self.distance(point1, point2)
45
+ ::Math.sqrt((point1[:x] - point2[:x])**2 + (point1[:y] - point2[:y])**2)
46
+ end
47
+
48
+ def self.distance_from_line(x, y, k, b)
49
+ (k * x + b - y).abs / ::Math.sqrt(k**2 + 1)
50
+ end
51
+
52
+ def self.center(points)
53
+ x = points.map { |p| p[:x] }.reduce(:+) / points.size
54
+ y = points.map { |p| p[:y] }.reduce(:+) / points.size
55
+ { x: x, y: y }
56
+ end
57
+
58
+ def self.angle_between(base_point, other_point)
59
+ radians = ::Math.atan2(other_point[:y] - base_point[:y], other_point[:x] - base_point[:x])
60
+ degrees = radians * 180.0 / ::Math::PI
61
+ degrees += 360.0 if degrees < 0
62
+ degrees
63
+ end
64
+
65
+ end # module Math
66
+ end
67
+ end
68
+ end
@@ -3,7 +3,7 @@
3
3
  module Fusuma
4
4
  module Plugin
5
5
  module Touchscreen
6
- VERSION = "0.0.1.alpha"
6
+ VERSION = "0.1.0"
7
7
  end
8
8
  end
9
9
  end
File without changes
@@ -0,0 +1,216 @@
1
+ require 'spec_helper'
2
+ require 'timecop'
3
+ require 'fusuma/plugin/events/event'
4
+ require 'fusuma/plugin/buffers/touch_buffer'
5
+ require 'fusuma/plugin/events/records/touch_record'
6
+
7
+ module Fusuma
8
+ RSpec.describe Plugin::Buffers::TouchBuffer do
9
+ subject { described_class.new }
10
+
11
+ it 'builds finger_events_map' do
12
+ event = generate_touch_event
13
+ subject.buffer(event)
14
+ expect(subject.finger_events_map[event.record.finger]).not_to be_empty
15
+ expect(subject.finger_events_map[event.record.finger].first).to eq event
16
+ end
17
+
18
+
19
+ it 'clears finger_events_map' do
20
+ event = generate_touch_event
21
+ subject.buffer(event)
22
+ subject.clear
23
+ expect(subject.finger_events_map).to be_empty
24
+ end
25
+
26
+ describe '#clear_expired' do
27
+ it 'outdated' do
28
+ allow(subject).to receive(:config_params).and_call_original
29
+ allow(subject).to receive(:config_params).with(:seconds_to_keep).and_return(50)
30
+ expired_event = generate_touch_event(time: Time.now - 100)
31
+ actual_event = generate_touch_event
32
+ subject.buffer(expired_event)
33
+ subject.buffer(actual_event)
34
+ subject.clear_expired
35
+ expect(subject.finger_events_map[actual_event.record.finger]).not_to include(expired_event)
36
+ expect(subject.finger_events_map[actual_event.record.finger]).to include(actual_event)
37
+ end
38
+
39
+ it 'ended' do
40
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'begin')))
41
+ subject.clear_expired
42
+ expect(subject.finger_events_map).not_to be_empty
43
+
44
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'end')))
45
+ subject.clear_expired
46
+ expect(subject.finger_events_map).to be_empty
47
+ end
48
+ end
49
+
50
+ describe '#empty?' do
51
+ it 'is empty' do
52
+ expect(subject.empty?).to be_truthy
53
+ end
54
+
55
+ it 'is not empty' do
56
+ event = generate_touch_event
57
+ subject.buffer(event)
58
+ expect(subject.empty?).to be_falsey
59
+ end
60
+ end
61
+
62
+ describe '#finger' do
63
+ it 'returns 0 if finger_events_map is empty' do
64
+ expect(subject.finger).to eq 0
65
+ end
66
+
67
+ it 'returns count of fingers' do
68
+ subject.buffer(generate_touch_event(record: generate_touch_record(finger: 1)))
69
+ subject.buffer(generate_touch_event(record: generate_touch_record(finger: 2)))
70
+ expect(subject.finger).to eq 2
71
+ end
72
+
73
+ it 'is based on count of fingers, not finger number' do
74
+ subject.buffer(generate_touch_event(record: generate_touch_record(finger: 2)))
75
+ expect(subject.finger).to eq 1
76
+ end
77
+ end
78
+
79
+ describe '#began?' do
80
+ it 'not began yet' do
81
+ expect(subject.began?).to be_falsey
82
+ end
83
+
84
+ it 'began' do
85
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'begin')))
86
+ expect(subject.began?).to be_truthy
87
+ end
88
+
89
+ it 'doesn\'t contain begin events (being updated or so, but began before that)' do
90
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'update')))
91
+ expect(subject.began?).to be_falsey
92
+ end
93
+
94
+ it 'one finger began, other continued to update' do
95
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'begin', finger: 1)))
96
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'update', finger: 2)))
97
+ expect(subject.began?).to be_falsey
98
+ end
99
+ end
100
+
101
+ describe '#ended?' do
102
+ it 'not ended yet' do
103
+ expect(subject.ended?).to be_falsey
104
+ end
105
+
106
+ it 'ended' do
107
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'end')))
108
+ expect(subject.ended?).to be_truthy
109
+ end
110
+
111
+ it 'doesn\'t contain end events (being updated or so, but ended before that)' do
112
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'update')))
113
+ expect(subject.ended?).to be_falsey
114
+ end
115
+
116
+ it 'one finger ended, other continued to update' do
117
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'end', finger: 1)))
118
+ subject.buffer(generate_touch_event(record: generate_touch_record(status: 'update', finger: 2)))
119
+ expect(subject.ended?).to be_falsey
120
+ end
121
+ end
122
+
123
+ describe '#finger_movements / #moved' do
124
+ before do
125
+ allow(subject).to receive(:movement_threshold).and_return(0.5)
126
+ end
127
+
128
+ it 'returns empty array if finger_events_map is empty' do
129
+ expect(subject.finger_movements).to be_empty
130
+ expect(subject.moved?).to be_falsey
131
+ end
132
+
133
+ it 'no movement if only one event' do
134
+ event = generate_touch_event
135
+ subject.buffer(event)
136
+ expect(subject.finger_movements).to be_empty
137
+ expect(subject.moved?).to be_falsey
138
+ end
139
+
140
+ it 'does not detect movement lesser than movement threshold' do
141
+ event1 = generate_touch_event(record: generate_touch_record(status: 'begin', x_mm: 0, y_mm: 0))
142
+ event2 = generate_touch_event(record: generate_touch_record(status: 'update', x_mm: 0.4, y_mm: 0))
143
+ subject.buffer(event1)
144
+ subject.buffer(event2)
145
+ expect(subject.finger_movements).to be_empty
146
+ expect(subject.moved?).to be_falsey
147
+ end
148
+
149
+ it 'detects movement outside jitter threshold' do
150
+ event1 = generate_touch_event(record: generate_touch_record(status: 'begin', x_mm: 0, y_mm: 0))
151
+ event2 = generate_touch_event(record: generate_touch_record(status: 'update', x_mm: 1, y_mm: 2))
152
+ subject.buffer(event1)
153
+ subject.buffer(event2)
154
+ expect(subject.finger_movements).to eq({ 0 => { first_position: { x: 0.0, y: 0.0 }, last_position: { x: 1.0, y: 2.0 } } })
155
+ expect(subject.moved?).to be_truthy
156
+ end
157
+ end
158
+
159
+ describe '#begin_time' do
160
+ it 'returns nil if no begin event' do
161
+ expect(subject.begin_time).to be_nil
162
+ end
163
+
164
+ it 'returns begin event time' do
165
+ begin_event = generate_touch_event(time: Time.now - 60, record: generate_touch_record(status: 'begin'))
166
+ subject.buffer(begin_event)
167
+ update_event = generate_touch_event(time: Time.now, record: generate_touch_record(status: 'end'))
168
+ subject.buffer(update_event)
169
+ expect(subject.begin_time).to eq(begin_event.time)
170
+ end
171
+ end
172
+
173
+ describe '#end_time' do
174
+ it 'returns nil if no end event' do
175
+ expect(subject.end_time).to be_nil
176
+ end
177
+
178
+ it 'returns end event time' do
179
+ begin_event = generate_touch_event(time: Time.now - 60, record: generate_touch_record(status: 'begin'))
180
+ subject.buffer(begin_event)
181
+ end_event = generate_touch_event(time: Time.now, record: generate_touch_record(status: 'end'))
182
+ subject.buffer(end_event)
183
+ expect(subject.end_time).to eq(end_event.time)
184
+ end
185
+ end
186
+
187
+ describe '#duration' do
188
+ it 'zero if nothing happened' do
189
+ expect(subject.duration).to eq(0)
190
+ end
191
+
192
+ it 'gesture ended' do
193
+ t = Time.now
194
+ begin_event = generate_touch_event(time: t - 60, record: generate_touch_record(status: 'begin'))
195
+ subject.buffer(begin_event)
196
+ update_event = generate_touch_event(time: t - 40, record: generate_touch_record(status: 'update'))
197
+ subject.buffer(update_event)
198
+ end_event = generate_touch_event(time: t - 20, record: generate_touch_record(status: 'end'))
199
+ subject.buffer(end_event)
200
+ expect(subject.duration).to eq(40)
201
+ end
202
+
203
+ it 'gesture not ended' do
204
+ t = Time.now
205
+ Timecop.freeze(t) do
206
+ begin_event = generate_touch_event(time: t - 60, record: generate_touch_record(status: 'begin'))
207
+ subject.buffer(begin_event)
208
+ update_event = generate_touch_event(time: t - 40, record: generate_touch_record(status: 'update'))
209
+ subject.buffer(update_event)
210
+ expect(subject.duration.round).to eq(60)
211
+ end
212
+ end
213
+ end # duration
214
+
215
+ end
216
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'timecop'
3
+ require 'fusuma/plugin/events/event'
4
+ require 'fusuma/plugin/parsers/touch_parser'
5
+ require 'fusuma/plugin/buffers/touch_buffer'
6
+ require 'fusuma/plugin/detectors/touch_detectors/hold_detector'
7
+ require 'fusuma/plugin/events/records/touch_records/hold_record'
8
+
9
+ module Fusuma
10
+ RSpec.describe Plugin::Detectors::TouchDetectors::HoldDetector do
11
+ subject { described_class.new }
12
+ let(:touch_buffer) { Plugin::Buffers::TouchBuffer.new }
13
+ let(:t) { Time.now }
14
+
15
+ before do
16
+ allow(touch_buffer).to receive(:movement_threshold).and_return(0.5)
17
+ allow(subject).to receive(:tap_hold_threshold).and_return(0.5)
18
+ allow(subject).to receive(:jitter_threshold).and_return(5.0)
19
+ end
20
+
21
+ it 'no events' do
22
+ expect(subject.detect(touch_buffer)).to be nil
23
+ end
24
+
25
+ it 'under tap_hold_threshold' do
26
+ touch_buffer.buffer(generate_touch_event(time: t - 0.1, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
27
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 10)))
28
+
29
+ expect(subject.detect(touch_buffer)).to be nil
30
+ end
31
+
32
+ it 'not moved' do
33
+ touch_buffer.buffer(generate_touch_event(time: t - 1, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
34
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 10)))
35
+
36
+ expect(subject.detect(touch_buffer)).to be_a(Plugin::Events::Records::TouchRecords::HoldRecord)
37
+ end
38
+
39
+ it 'moved under jitter_threshold' do
40
+ touch_buffer.buffer(generate_touch_event(time: t - 1, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
41
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 15)))
42
+
43
+ expect(subject.detect(touch_buffer)).to be_a(Plugin::Events::Records::TouchRecords::HoldRecord)
44
+ end
45
+
46
+ it 'moved more than jitter_threshold' do
47
+ touch_buffer.buffer(generate_touch_event(time: t - 1, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
48
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 20)))
49
+
50
+ expect(subject.detect(touch_buffer)).to be nil
51
+ end
52
+
53
+ it 'works on real data' do
54
+ parser = Plugin::Parsers::TouchParser.new
55
+ buffer = Plugin::Buffers::TouchBuffer.new
56
+ lines = File.readlines("spec/samples/1-finger-hold.txt").map(&:strip).reject(&:empty?)
57
+ t = Time.now - 30
58
+ lines.each do |line|
59
+ record = parser.parse_record(line)
60
+ next unless record
61
+
62
+ event = Plugin::Events::Event.new(tag: 'libinput_touch_parser', record: record, time: t += 1)
63
+ buffer.buffer(event)
64
+ end
65
+ gesture = subject.detect(buffer)
66
+ expect(gesture).to be_a(Plugin::Events::Records::TouchRecords::HoldRecord)
67
+ expect(gesture.finger).to eq(1)
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,96 @@
1
+ require 'fusuma/plugin/detectors/touch_detectors/pinch_detector'
2
+
3
+ module Fusuma
4
+ RSpec.describe Plugin::Detectors::TouchDetectors::PinchDetector do
5
+ include_context 'with touch buffer'
6
+ subject { described_class.new }
7
+
8
+ before do
9
+ allow(subject).to receive(:jitter_threshold).and_return(5.0)
10
+ end
11
+
12
+ it 'no events' do
13
+ expect(subject.detect(touch_buffer)).to be_nil
14
+ end
15
+
16
+ it 'not moved' do
17
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
18
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 10)))
19
+
20
+ expect(subject.detect(touch_buffer)).to be_nil
21
+ end
22
+
23
+ it 'moved, only one finger' do
24
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
25
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 20, y_mm: 20)))
26
+
27
+ expect(subject.detect(touch_buffer)).to be_nil
28
+ end
29
+
30
+ it 'moved, not enough distance change between first 2 fingers' do
31
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 10, y_mm: 10)))
32
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 8, y_mm: 10)))
33
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 2, x_mm: 10, y_mm: 10)))
34
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 2, x_mm: 12, y_mm: 10)))
35
+
36
+ expect(subject.detect(touch_buffer)).to be_nil
37
+ end
38
+
39
+ it 'just two fingers' do
40
+ # inside down left
41
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 0, y_mm: 0)))
42
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 10, y_mm: 10)))
43
+ # inside up right
44
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 2, x_mm: 20, y_mm: 20)))
45
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 2, x_mm: 10, y_mm: 10)))
46
+
47
+ gesture = subject.detect(touch_buffer)
48
+ expect(gesture).to be_a(Plugin::Events::Records::TouchRecords::PinchRecord)
49
+ expect(gesture.finger).to eq(2)
50
+ expect(gesture.direction).to eq('in')
51
+ end
52
+
53
+ it 'three fingers, the third finger moved in wrong direction' do
54
+ # outside up right
55
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 20, y_mm: 10)))
56
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 30, y_mm: 0)))
57
+ # outside down left
58
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 2, x_mm: 10, y_mm: 20)))
59
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 2, x_mm: 0, y_mm: 30)))
60
+ # inside up left
61
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 3, x_mm: 30, y_mm: 30)))
62
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 3, x_mm: 20, y_mm: 20)))
63
+
64
+ expect(subject.detect(touch_buffer)).to be_nil
65
+ end
66
+
67
+ it 'three fingers' do
68
+ # outside up right
69
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 1, x_mm: 20, y_mm: 10)))
70
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 1, x_mm: 30, y_mm: 0)))
71
+ # outside down left
72
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 2, x_mm: 10, y_mm: 20)))
73
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 2, x_mm: 0, y_mm: 30)))
74
+ # outside down right
75
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'begin', finger: 3, x_mm: 20, y_mm: 20)))
76
+ touch_buffer.buffer(generate_touch_event(time: t, record: generate_touch_record(status: 'update', finger: 3, x_mm: 30, y_mm: 30)))
77
+
78
+ gesture = subject.detect(touch_buffer)
79
+ expect(gesture).to be_a(Plugin::Events::Records::TouchRecords::PinchRecord)
80
+ expect(gesture.direction).to eq('out')
81
+ expect(gesture.finger).to eq(3)
82
+ end
83
+
84
+ it_behaves_like 'real sample',
85
+ detector_class: Plugin::Detectors::TouchDetectors::PinchDetector,
86
+ sample_path: 'spec/samples/3-fingers-pinch-in.txt',
87
+ expected_gesture_class: Plugin::Events::Records::TouchRecords::PinchRecord,
88
+ expected_gesture_attributes: { finger: 3, direction: 'in' }
89
+ it_behaves_like 'real sample',
90
+ detector_class: Plugin::Detectors::TouchDetectors::PinchDetector,
91
+ sample_path: 'spec/samples/3-fingers-pinch-out.txt',
92
+ expected_gesture_class: Plugin::Events::Records::TouchRecords::PinchRecord,
93
+ expected_gesture_attributes: { finger: 3, direction: 'out' }
94
+
95
+ end
96
+ end