fusuma-plugin-touchscreen 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
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