fusuma-plugin-touchscreen 0.0.1.alpha

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 (32) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +2 -0
  4. data/fusuma-plugin-touchscreen.gemspec +28 -0
  5. data/lib/fusuma/plugin/buffers/touch_buffer.rb +197 -0
  6. data/lib/fusuma/plugin/detectors/touch_detector.rb +95 -0
  7. data/lib/fusuma/plugin/detectors/touch_detectors/base.rb +17 -0
  8. data/lib/fusuma/plugin/detectors/touch_detectors/edge_detector.rb +13 -0
  9. data/lib/fusuma/plugin/detectors/touch_detectors/hold_detector.rb +26 -0
  10. data/lib/fusuma/plugin/detectors/touch_detectors/pinch_detector.rb +13 -0
  11. data/lib/fusuma/plugin/detectors/touch_detectors/rotate_detector.rb +13 -0
  12. data/lib/fusuma/plugin/detectors/touch_detectors/swipe_detector.rb +59 -0
  13. data/lib/fusuma/plugin/detectors/touch_detectors/tap_detector.rb +32 -0
  14. data/lib/fusuma/plugin/devices/touchscreen_device.rb +21 -0
  15. data/lib/fusuma/plugin/events/records/touch_record.rb +38 -0
  16. data/lib/fusuma/plugin/events/records/touch_records/base.rb +56 -0
  17. data/lib/fusuma/plugin/events/records/touch_records/hold_record.rb +21 -0
  18. data/lib/fusuma/plugin/events/records/touch_records/swipe_record.rb +33 -0
  19. data/lib/fusuma/plugin/events/records/touch_records/tap_record.rb +21 -0
  20. data/lib/fusuma/plugin/parsers/touch_parser.rb +103 -0
  21. data/lib/fusuma/plugin/touchscreen/version.rb +9 -0
  22. data/lib/fusuma/plugin/touchscreen.rb +11 -0
  23. data/lib/fusuma/utils/angle.rb +12 -0
  24. data/spec/fixtures/libinput-list-devices_ms-surface-3-pro.txt +182 -0
  25. data/spec/fusuma/plugin/devices/touchscreen_device_spec.rb +33 -0
  26. data/spec/fusuma/plugin/parsers/touch_parser_spec.rb +26 -0
  27. data/spec/samples/1-finger-hold.txt +90 -0
  28. data/spec/samples/2-fingers-swipe-right.txt +46 -0
  29. data/spec/samples/3-fingers-tap.txt +9 -0
  30. data/spec/samples/libinput-devices.txt +182 -0
  31. data/spec/spec_helper.rb +101 -0
  32. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 382351b2d3d10f48ec04e2c6615805d8dbc1861ca22521728104a223077864e1
4
+ data.tar.gz: ec9eb76e6b29b083eb6e43d94876251bc8326eba145ea338ed314bc76a324ce8
5
+ SHA512:
6
+ metadata.gz: 3052cd87167d7db34cf511302472765b33d0956a66477068a271d8c525c75fe35281ea83db6172bcb22cd11e032e3ee447813affc7a70a84390369f55c26d127
7
+ data.tar.gz: ec2bd3533144d81b222a57ab82b982cf625a8dfdcaf4416d00845b2ce0456edbb93e44fbe96117426e71ad73154d2906354cec357fccdbae1ae39f0df1a41f55
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Mikhail Fedotov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # fusuma-plugin-touchscreen
2
+ The Fusuma plugin for touchscreens
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'fusuma/plugin/touchscreen/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'fusuma-plugin-touchscreen'
9
+ spec.version = Fusuma::Plugin::Touchscreen::VERSION
10
+ spec.authors = ['Mikhail Fedotov']
11
+ spec.email = ['myf.ivm@gmail.com']
12
+
13
+ spec.summary = ' Touchscreen support plugin for Fusuma '
14
+ spec.description = ' fusuma-plugin-touchscreen is Fusuma plugin for support touchscreen devices. '
15
+ spec.homepage = 'https://github.com/Phaengris/fusuma-plugin-touchscreen'
16
+ spec.license = 'MIT'
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ spec.files = Dir['{bin,lib,exe}/**/*', 'LICENSE*', 'README*', '*.gemspec']
20
+ spec.test_files = Dir['{test,spec,features}/**/*']
21
+ spec.bindir = 'exe'
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = '>= 2.3' # https://packages.ubuntu.com/search?keywords=ruby&searchon=names&exact=1&suite=all&section=main
26
+
27
+ spec.add_dependency 'fusuma', '~> 2.0'
28
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Buffers
6
+ class TouchBuffer < Buffer
7
+ DEFAULT_SOURCE = "libinput_touch_parser"
8
+ DEFAULT_SECONDS_TO_KEEP = 100
9
+
10
+ attr_reader :finger_events_map
11
+
12
+ def initialize(*args)
13
+ super()
14
+ @finger_events_map = {}
15
+ @mem = {}
16
+ @last_event_time = nil
17
+ end
18
+
19
+ def config_param_types
20
+ {
21
+ source: [String],
22
+ seconds_to_keep: [Float, Integer]
23
+ }
24
+ end
25
+
26
+ def buffer(event)
27
+ return if event&.tag != source
28
+
29
+ @finger_events_map[event.record.finger] ||= []
30
+ @finger_events_map[event.record.finger].push(event)
31
+ reset_memoized
32
+
33
+ self
34
+ end
35
+
36
+ def events
37
+ raise "Not supported, use finger_events_map instead"
38
+ end
39
+
40
+ def clear_expired(current_time: Time.now)
41
+ @seconds_to_keep ||= (config_params(:seconds_to_keep) || DEFAULT_SECONDS_TO_KEEP)
42
+
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
+ clear if ended?
56
+ @finger_events_map.each do |finger, events|
57
+ next if events.empty?
58
+
59
+ @finger_events_map[finger].select! do |e|
60
+ current_time - e.time < @seconds_to_keep
61
+ end
62
+ end
63
+ @finger_events_map.delete_if { |_, events| events.empty? }
64
+
65
+ reset_memoized
66
+ end
67
+
68
+ def clear
69
+ super
70
+ @finger_events_map = {}
71
+ reset_memoized
72
+ end
73
+
74
+ def empty?
75
+ @finger_events_map.empty?
76
+ end
77
+
78
+ def finger
79
+ @mem[:finger] ||= @finger_events_map.keys.count
80
+ end
81
+
82
+ def began?
83
+ @mem[:began] ||= @finger_events_map.any? && @finger_events_map.all? { |_, events| events.first&.record.status == "begin" }
84
+ end
85
+
86
+ def ended?
87
+ @mem[:ended] ||= @finger_events_map.any? && @finger_events_map.all? { |_, events| events.last&.record.status == "end" }
88
+ end
89
+
90
+ def moved?
91
+ # TODO: a quicker way to do this?
92
+ @mem[:moved] ||= finger_movements.any? && finger_movements.all? { |finger, movement| movement[:distance] > jitter_threshold }
93
+ end
94
+
95
+ def duration
96
+ @mem[:duration] ||= if ended?
97
+ end_time - begin_time
98
+ else
99
+ Time.now - begin_time
100
+ end
101
+ end
102
+
103
+ def begin_time
104
+ @mem[:begin_time] ||= @finger_events_map.values.map { |events| events.first.time }.min
105
+ end
106
+
107
+ def end_time
108
+ @mem[:end_time] ||= @finger_events_map.values.map { |events| events.last.time }.max
109
+ end
110
+
111
+ def finger_movements
112
+ @mem[:finger_movements] ||= @finger_events_map.map do |finger, events|
113
+ position_events = events.select { |e| e.record.position? }
114
+ next if position_events.size < 2 # we need at least first and last position
115
+
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
135
+
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
177
+ end.compact.to_h
178
+ end
179
+
180
+ private
181
+
182
+ def axis_threshold
183
+ 2.0 # TODO: make it configurable
184
+ end
185
+
186
+ def jitter_threshold
187
+ 5.0 # TODO: configurable
188
+ end
189
+
190
+ def reset_memoized
191
+ @mem = {} if @mem.any?
192
+ end
193
+
194
+ end # class TouchBuffer
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './touch_detectors/tap_detector'
4
+ require_relative './touch_detectors/hold_detector'
5
+ require_relative './touch_detectors/swipe_detector'
6
+ require_relative './touch_detectors/pinch_detector'
7
+ require_relative './touch_detectors/rotate_detector'
8
+ require_relative './touch_detectors/edge_detector'
9
+
10
+ module Fusuma
11
+ module Plugin
12
+ module Detectors
13
+ class TouchDetector < Detector
14
+ SOURCES = %w[touch timer].freeze
15
+
16
+ def initialize(*)
17
+ super
18
+ @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)
26
+ @last_known_gesture = nil
27
+ end
28
+
29
+ def detect(buffers)
30
+ events = []
31
+
32
+ timer_buffer = buffers.find { |b| b.type == 'timer' }
33
+ touch_buffer = buffers.find { |b| b.type == 'touch' }
34
+ @touch_buffer = touch_buffer || @touch_buffer
35
+
36
+ if timer_buffer &&
37
+ timer_buffer.events.any? &&
38
+ @last_known_gesture &&
39
+ (timer_buffer.events.last.time - @last_known_gesture.time) > event_expire_time
40
+
41
+ # TODO: should we also clear the touch buffer?
42
+ events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture.record.repeatable?
43
+ @last_known_gesture = nil
44
+ end
45
+
46
+ if timer_buffer
47
+ # if this is a timer tick, we have to have saved touch buffer to work with
48
+ return events if @touch_buffer.nil? || @touch_buffer.empty?
49
+ else
50
+ # if not, we have to have a new touch buffer events
51
+ return events if touch_buffer.nil? || touch_buffer.empty?
52
+ end
53
+
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
58
+
59
+ gesture_record = nil
60
+ if touch_buffer
61
+ @detectors.each do |detector|
62
+ gesture_record = detector.detect(@touch_buffer)
63
+ break if gesture_record
64
+ end
65
+ else
66
+ return events if @touch_buffer.empty?
67
+ gesture_record = @hold_detector.detect(@touch_buffer)
68
+ end
69
+
70
+ if gesture_record
71
+ @touch_buffer.clear
72
+ if gesture_record.repeatable? && @last_known_gesture&.record == gesture_record
73
+ @last_known_gesture = create_event(record: gesture_record)
74
+ events << create_event(record: gesture_record.create_index_record(status: 'update', trigger: :repeat))
75
+ else
76
+ events << create_event(record: @last_known_gesture.record.create_index_record(status: 'end', trigger: :repeat)) if @last_known_gesture&.record&.repeatable?
77
+ @last_known_gesture = create_event(record: gesture_record)
78
+ 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?
80
+ end
81
+ end
82
+
83
+ events
84
+ end
85
+
86
+ private
87
+
88
+ def event_expire_time
89
+ 2.0 # TODO: make configurable
90
+ end
91
+
92
+ end # class TouchDetector
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,17 @@
1
+ module Fusuma
2
+ module Plugin
3
+ module Detectors
4
+ module TouchDetectors
5
+ class Base
6
+
7
+ protected
8
+
9
+ def tap_hold_threshold
10
+ 0.5 # TODO: configurable
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ require_relative './base'
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Detectors
6
+ module TouchDetectors
7
+ class EdgeDetector < Base
8
+
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require_relative './base'
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Detectors
6
+ module TouchDetectors
7
+ class HoldDetector < Base
8
+
9
+ def detect(touch_buffer)
10
+ MultiLogger.debug('> hold detector')
11
+
12
+ MultiLogger.debug(' no movement?')
13
+ return if touch_buffer.moved?
14
+
15
+ MultiLogger.debug(" tap / hold threshold? (duration #{touch_buffer.duration})")
16
+ return unless touch_buffer.duration > tap_hold_threshold
17
+
18
+ MultiLogger.debug(" hold (#{touch_buffer.finger}) detected!")
19
+ Plugin::Events::Records::TouchRecords::HoldRecord.new(finger: touch_buffer.finger)
20
+ end
21
+
22
+ end # class HoldDetector
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ require_relative './base'
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Detectors
6
+ module TouchDetectors
7
+ class PinchDetector < Base
8
+
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative './base'
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Detectors
6
+ module TouchDetectors
7
+ class RotateDetector < Base
8
+
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ require_relative './base'
2
+ require_relative '../../../utils/angle'
3
+
4
+ module Fusuma
5
+ module Plugin
6
+ module Detectors
7
+ module TouchDetectors
8
+ class SwipeDetector
9
+
10
+ def detect(touch_buffer)
11
+ MultiLogger.debug('> swipe detector')
12
+
13
+ MultiLogger.debug(' movement?')
14
+ return unless touch_buffer.moved?
15
+
16
+ MultiLogger.debug(' angles?')
17
+ angles = touch_buffer.finger_movements.map { |_, movement| movement[:angle] }
18
+ angles.combination(2).each do |angle1, angle2|
19
+ if Utils::Angle.difference(angle1, angle2) > movement_angle_threshold
20
+ MultiLogger.debug(" !too much difference between #{angle1} and #{angle2}, not a swipe")
21
+ return
22
+ end
23
+ end
24
+ angle = angles.inject(:+).to_f / angles.size
25
+
26
+ MultiLogger.debug(' direction?')
27
+ case angle
28
+ when 0...(direction_angle_width / 2), (360 - (direction_angle_width / 2))...360
29
+ direction = :right
30
+ when (90 - (direction_angle_width / 2))...(90 + (direction_angle_width / 2))
31
+ direction = :down
32
+ when (180 - (direction_angle_width / 2))...(180 + (direction_angle_width / 2))
33
+ direction = :left
34
+ when (270 - (direction_angle_width / 2))...(270 + (direction_angle_width / 2))
35
+ direction = :up
36
+ else
37
+ MultiLogger.debug(" !gesture angle of #{angle} does not fall into any direction")
38
+ return
39
+ end
40
+ MultiLogger.debug(" swipe #{direction} detected!")
41
+
42
+ Plugin::Events::Records::TouchRecords::SwipeRecord.new(finger: touch_buffer.finger, direction: direction)
43
+ end
44
+
45
+ private
46
+
47
+ def movement_angle_threshold
48
+ 30 # TODO: configurable
49
+ end
50
+
51
+ def direction_angle_width
52
+ 45 # TODO: configurable
53
+ end
54
+
55
+ end # class SwipeDetector
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ require_relative './base'
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Detectors
6
+ module TouchDetectors
7
+ class TapDetector < Base
8
+
9
+ def detect(touch_buffer)
10
+ MultiLogger.debug('> tap detector')
11
+
12
+ MultiLogger.debug(' no movement?')
13
+ return if touch_buffer.moved?
14
+
15
+ MultiLogger.debug(' began?')
16
+ return unless touch_buffer.began?
17
+
18
+ MultiLogger.debug(' ended?')
19
+ return unless touch_buffer.ended?
20
+
21
+ MultiLogger.debug(" tap / hold threshold? (duration #{touch_buffer.duration})")
22
+ return unless touch_buffer.duration <= tap_hold_threshold
23
+
24
+ MultiLogger.debug(" tap (#{touch_buffer.finger}) detected!")
25
+ Plugin::Events::Records::TouchRecords::TapRecord.new(finger: touch_buffer.finger)
26
+ end
27
+
28
+ end # class TapDetector
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Touchscreen
6
+ module DevicePatch
7
+ module ClassMethods
8
+ def all
9
+ @all ||= super.tap do |devices|
10
+ devices.each do |device|
11
+ device.assign_attributes(available: true) if device.capabilities.match?(/touch/)
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ Fusuma::Device.singleton_class.prepend(ClassMethods)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './touch_records/tap_record'
4
+ require_relative './touch_records/hold_record'
5
+ require_relative './touch_records/swipe_record'
6
+
7
+ module Fusuma
8
+ module Plugin
9
+ module Events
10
+ module Records
11
+ class TouchRecord < Record
12
+ attr_reader :status,
13
+ :finger,
14
+ :x_px,
15
+ :y_px,
16
+ :x_mm,
17
+ :y_mm,
18
+ :time_offset
19
+
20
+ def initialize(status:, finger:, time_offset:, x_px: nil, y_px: nil, x_mm: nil, y_mm: nil)
21
+ super()
22
+ @status = status.to_s
23
+ @finger = finger.to_i
24
+ @time_offset = time_offset.to_f
25
+ @x_px = x_px.to_f if x_px
26
+ @y_px = y_px.to_f if y_px
27
+ @x_mm = x_mm.to_f if x_mm
28
+ @y_mm = y_mm.to_f if y_mm
29
+ end
30
+
31
+ def position?
32
+ @x_px || @y_px
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fusuma
4
+ module Plugin
5
+ module Events
6
+ module Records
7
+ module TouchRecords
8
+ class Base < Record
9
+ attr_reader :finger
10
+
11
+ def initialize(finger:)
12
+ super()
13
+ @finger = finger.to_i
14
+ end
15
+
16
+ def repeatable?
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def ==(other)
21
+ return false unless other.is_a?(self.class)
22
+ @finger == other.finger
23
+ end
24
+
25
+ def create_index_record(status: nil, trigger: :oneshot)
26
+ keys = config_index_keys
27
+ keys << Config::Index::Key.new(status) if status
28
+ Events::Records::IndexRecord.new(index: Config::Index.new(keys), trigger: trigger)
29
+ end
30
+
31
+ protected
32
+
33
+ def config_index_keys
34
+ [
35
+ Config::Index::Key.new(gesture_type),
36
+ Config::Index::Key.new(@finger),
37
+ ]
38
+ end
39
+
40
+ private
41
+
42
+ def self.gesture_type
43
+ @gesture_type ||= self.to_s.split('::').last.downcase.delete_suffix('record')
44
+ end
45
+
46
+ def gesture_type
47
+ # yes, that's not a good practice, but I'm not sure if we need this method in the public interface
48
+ self.class.send(:gesture_type)
49
+ end
50
+
51
+ end # class Base
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ require_relative './base'
2
+
3
+ # frozen_string_literal: true
4
+
5
+ module Fusuma
6
+ module Plugin
7
+ module Events
8
+ module Records
9
+ module TouchRecords
10
+ class HoldRecord < Base
11
+
12
+ def repeatable?
13
+ true
14
+ end
15
+
16
+ end # class HoldRecord
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end