hokusai-zero 0.2.6 → 0.2.8

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +0 -2
  4. data/README.md +1 -1
  5. data/ast/src/core/ast.c +3 -11
  6. data/ast/src/core/hml.c +214 -40
  7. data/ast/src/core/hml.h +1 -0
  8. data/ast/src/core/input.h +0 -1
  9. data/ast/src/core/log.c +87 -0
  10. data/ast/src/core/log.h +41 -0
  11. data/ast/src/core/util.c +23 -23
  12. data/ast/src/core/util.h +7 -7
  13. data/ast/test/parser.c +1 -0
  14. data/ext/extconf.rb +6 -6
  15. data/hokusai.gemspec +1 -2
  16. data/ui/examples/drag.rb +154 -0
  17. data/ui/examples/embedded.rb +58 -0
  18. data/ui/examples/forum/file.rb +1 -1
  19. data/ui/examples/forum/post.rb +0 -1
  20. data/ui/examples/forum.rb +7 -7
  21. data/ui/examples/game.rb +143 -0
  22. data/ui/examples/keyboard.rb +47 -0
  23. data/ui/examples/overlay.rb +233 -0
  24. data/ui/examples/provider.rb +56 -0
  25. data/ui/examples/shader/test.rb +155 -0
  26. data/ui/examples/shader.rb +100 -0
  27. data/ui/examples/spreadsheet.rb +12 -11
  28. data/ui/examples/wiki.rb +82 -0
  29. data/ui/lib/lib_hokusai.rb +43 -24
  30. data/ui/spec/hokusai/e2e/client_spec.rb +0 -1
  31. data/ui/spec/hokusai/e2e/keyboard_spec.rb +52 -0
  32. data/ui/spec/spec_helper.rb +1 -1
  33. data/ui/src/hokusai/assets/arrow-down-line.png +0 -0
  34. data/ui/src/hokusai/assets/arrow-down-wide-line.png +0 -0
  35. data/ui/src/hokusai/assets/icons/outline/arrow-big-up.svg +19 -0
  36. data/ui/src/hokusai/assets/icons/outline/backspace.svg +20 -0
  37. data/ui/src/hokusai/automation/driver_commands/base.rb +2 -8
  38. data/ui/src/hokusai/automation/driver_commands/trigger_keyboard.rb +3 -6
  39. data/ui/src/hokusai/automation/driver_commands/trigger_mouse.rb +12 -5
  40. data/ui/src/hokusai/automation/server.rb +2 -3
  41. data/ui/src/hokusai/backends/raylib/config.rb +2 -1
  42. data/ui/src/hokusai/backends/raylib/font.rb +55 -4
  43. data/ui/src/hokusai/backends/raylib.rb +199 -36
  44. data/ui/src/hokusai/backends/sdl2/config.rb +9 -6
  45. data/ui/src/hokusai/backends/sdl2/font.rb +3 -1
  46. data/ui/src/hokusai/backends/sdl2.rb +188 -93
  47. data/ui/src/hokusai/blocks/color_picker.rb +1080 -0
  48. data/ui/src/hokusai/blocks/dynamic.rb +2 -0
  49. data/ui/src/hokusai/blocks/input.rb +2 -2
  50. data/ui/src/hokusai/blocks/keyboard.rb +249 -0
  51. data/ui/src/hokusai/blocks/panel.rb +2 -0
  52. data/ui/src/hokusai/blocks/scrollbar.rb +7 -0
  53. data/ui/src/hokusai/blocks/selectable.rb +1 -0
  54. data/ui/src/hokusai/blocks/shader_begin.rb +22 -0
  55. data/ui/src/hokusai/blocks/shader_end.rb +12 -0
  56. data/ui/src/hokusai/blocks/slider.rb +139 -0
  57. data/ui/src/hokusai/blocks/text_stream.rb +130 -0
  58. data/ui/src/hokusai/blocks/texture.rb +23 -0
  59. data/ui/src/hokusai/blocks/translation.rb +91 -0
  60. data/ui/src/hokusai/commands/rect.rb +12 -3
  61. data/ui/src/hokusai/commands/rotation.rb +21 -0
  62. data/ui/src/hokusai/commands/scale.rb +20 -0
  63. data/ui/src/hokusai/commands/shader.rb +33 -0
  64. data/ui/src/hokusai/commands/texture.rb +26 -0
  65. data/ui/src/hokusai/commands/translation.rb +20 -0
  66. data/ui/src/hokusai/commands.rb +49 -3
  67. data/ui/src/hokusai/event.rb +2 -1
  68. data/ui/src/hokusai/events/keyboard.rb +11 -18
  69. data/ui/src/hokusai/events/mouse.rb +10 -8
  70. data/ui/src/hokusai/events/touch.rb +62 -0
  71. data/ui/src/hokusai/meta.rb +13 -6
  72. data/ui/src/hokusai/mounting/loop_entry.rb +4 -4
  73. data/ui/src/hokusai/mounting/update_entry.rb +5 -6
  74. data/ui/src/hokusai/painter.rb +31 -8
  75. data/ui/src/hokusai/types/display.rb +155 -0
  76. data/ui/src/hokusai/types/keyboard.rb +168 -0
  77. data/ui/src/hokusai/types/mouse.rb +36 -0
  78. data/ui/src/hokusai/types/primitives.rb +56 -0
  79. data/ui/src/hokusai/types/touch.rb +181 -0
  80. data/ui/src/hokusai/types.rb +20 -244
  81. data/ui/src/hokusai/util/selection.rb +28 -7
  82. data/ui/src/hokusai/util/wrap_stream.rb +268 -0
  83. data/ui/src/hokusai.rb +72 -35
  84. data/xmake.lua +2 -1
  85. metadata +39 -22
  86. data/ui/src/hokusai/assets/chevron-down.svg +0 -1
@@ -45,6 +45,16 @@ module Hokusai
45
45
  keyup: KeyUpEvent.new(input, state),
46
46
  keypress: KeyPressEvent.new(input, state)
47
47
  }
48
+
49
+ add_touch_events(events, input, state) unless input.touch.nil?
50
+ end
51
+
52
+ def add_touch_events(events, input, state)
53
+ events.merge!({
54
+ taphold: TapHoldEvent.new(input, state),
55
+ pinch: PinchEvent.new(input, state),
56
+ swipe: SwipeEvent.new(input, state),
57
+ })
48
58
  end
49
59
 
50
60
  def on_before_render(&block)
@@ -86,7 +96,7 @@ module Hokusai
86
96
  root_entry = PainterEntry.new(root, canvas.x, canvas.y, canvas.width, canvas.height)
87
97
  groups << [root_entry, measure(root_children, canvas)]
88
98
 
89
- mouse_y = input.mouse.pos[:y]
99
+ mouse_y = input.mouse.pos.y
90
100
  can_capture = mouse_y >= (canvas.y || 0.0) && mouse_y <= (canvas.y || 0.0) + canvas.height
91
101
 
92
102
  hovered = false
@@ -100,7 +110,6 @@ module Hokusai
100
110
  while group = group_children.shift
101
111
  z = group.block.node.meta.get_prop(:z)&.to_i || 0
102
112
  ztarget = group.block.node.meta.get_prop(:ztarget)
103
- entry = PainterEntry.new(group.block, group.x, group.y, group.w, group.h)
104
113
 
105
114
  if (zindex_counter > 0 || z > 0)
106
115
  case ztarget
@@ -108,12 +117,17 @@ module Hokusai
108
117
  entry = PainterEntry.new(group.block, zroot_x || 0.0, zroot_y || 0.0, zroot_w, zroot_h).freeze
109
118
  when ZTARGET_PARENT
110
119
  entry = PainterEntry.new(group.block, group_parent.x || 0.0, group_parent.y || 0.0, group_parent.w, group_parent.h).freeze
120
+ else
121
+ entry = PainterEntry.new(group.block, group.x, group.y, group.w, group.h).freeze
111
122
  end
123
+ else
124
+ entry = PainterEntry.new(group.block, group.x, group.y, group.w, group.h).freeze
112
125
  end
113
126
 
127
+
114
128
  canvas.reset(entry.x, entry.y, entry.w, entry.h)
115
129
 
116
- before_render&.call([group.block, group.parent], canvas, input.raw)
130
+ before_render&.call([group.block, group.parent], canvas, input)
117
131
 
118
132
  if capture
119
133
  capture_events(group.block, canvas, hovered: hovered)
@@ -170,6 +184,12 @@ module Hokusai
170
184
  events[:mouseout].bubble
171
185
  events[:mousedown].bubble
172
186
  events[:mouseup].bubble
187
+
188
+ unless input.touch.nil?
189
+ events[:taphold].bubble
190
+ events[:pinch].bubble
191
+ events[:swipe].bubble
192
+ end
173
193
  end
174
194
 
175
195
  after_render&.call
@@ -247,10 +267,7 @@ module Hokusai
247
267
  return
248
268
  end
249
269
 
250
- rect = canvas.to_hoku_rect
251
- block_is_hovered = LibHokusai.hoku_input_is_hovered(input.raw, rect)
252
-
253
- if block_is_hovered
270
+ if input.hovered?(canvas)
254
271
  events[:hover].capture(block, canvas)
255
272
  events[:click].capture(block, canvas)
256
273
  events[:wheel].capture(block, canvas)
@@ -261,10 +278,16 @@ module Hokusai
261
278
  end
262
279
  events[:mousemove].capture(block, canvas)
263
280
 
264
- if block_is_hovered || block.node.meta.focused
281
+ if input.hovered?(canvas) || block.node.meta.focused || input.keyboard_override
265
282
  events[:keyup].capture(block, canvas)
266
283
  events[:keypress].capture(block, canvas)
267
284
  end
285
+
286
+ unless input.touch.nil?
287
+ events[:taphold].capture(block, canvas)
288
+ events[:pinch].capture(block, canvas)
289
+ events[:swipe].capture(block, canvas)
290
+ end
268
291
  end
269
292
  end
270
293
  end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokusai
4
+ Outline = Struct.new(:top, :right, :bottom, :left) do
5
+ def self.default
6
+ new(0.0, 0.0, 0.0, 0.0)
7
+ end
8
+
9
+ def hash
10
+ [self.class, top, right, bottom, left].hash
11
+ end
12
+
13
+ def self.convert(value)
14
+ case value
15
+ when String
16
+ if value =~ /,/
17
+ convert(value.split(",").map(&:to_f))
18
+ else
19
+ convert(value.to_f)
20
+ end
21
+ when Float
22
+ new(value, value, value, value)
23
+ when Array
24
+ new(value[0] || 0.0, value[1] || 0.0, value[2] || 0.0, value[3] || 0.0)
25
+ when Outline
26
+ value
27
+ end
28
+ end
29
+
30
+ def present?
31
+ top > 0.0 || right > 0.0 || bottom > 0.0 || left > 0.0
32
+ end
33
+
34
+ def uniform?
35
+ top == right && top == bottom && top == left
36
+ end
37
+ end
38
+
39
+ class Padding
40
+ attr_reader :top, :left, :right, :bottom
41
+ def initialize(top, right, bottom, left)
42
+ @top = top
43
+ @left = left
44
+ @right = right
45
+ @bottom = bottom
46
+ end
47
+
48
+ alias_method :t, :top
49
+ alias_method :l, :left
50
+ alias_method :r, :right
51
+ alias_method :b, :bottom
52
+
53
+ def width
54
+ right + left
55
+ end
56
+
57
+ def height
58
+ top + bottom
59
+ end
60
+
61
+ def self.convert(value)
62
+ case value
63
+ when String
64
+ if value =~ /,/
65
+ convert(value.split(",").map(&:to_f))
66
+ else
67
+ convert(value.to_i)
68
+ end
69
+ when Integer
70
+ new(value, value, value, value)
71
+ when Array
72
+ new(value[0], value[1], value[2], value[3])
73
+ when Padding
74
+ value
75
+ else
76
+ raise Hokusai::Error.new("Unsupported conversion type #{value.class} for Hokusai::Padding")
77
+ end
78
+ end
79
+
80
+ def hash
81
+ [self.class, top, right, bottom, left].hash
82
+ end
83
+ end
84
+
85
+ class Canvas
86
+ attr_accessor :width, :height, :x, :y, :vertical, :reverse
87
+ def initialize(width, height, x = 0.0, y = 0.0, vertical = true, reverse = false)
88
+ @width = width
89
+ @height = height
90
+ @x = x
91
+ @y = y
92
+ @vertical = vertical
93
+ @reverse = reverse
94
+ end
95
+
96
+ def reset(x, y, width, height, vertical: true, reverse: false)
97
+ self.x = x
98
+ self.y = y
99
+ self.width = width
100
+ self.height = height
101
+ self.vertical = vertical
102
+ self.reverse = reverse
103
+ end
104
+
105
+ def to_bounds
106
+ Hokusai::Rect.new(x, y, width, height)
107
+ end
108
+
109
+ def hovered?(input)
110
+ input.hovered?(self)
111
+ end
112
+
113
+ def reverse?
114
+ reverse
115
+ end
116
+ end
117
+
118
+ # Color = Struct.new(:red, :green, :blue, :alpha) do
119
+ class Color
120
+ attr_accessor :red, :green, :blue, :alpha
121
+ def initialize(red, green, blue, alpha = 255)
122
+ @red = red.freeze
123
+ @green = green.freeze
124
+ @blue = blue.freeze
125
+ @alpha = alpha.freeze
126
+ end
127
+
128
+ alias_method :r, :red
129
+ alias_method :b, :blue
130
+ alias_method :g, :green
131
+ alias_method :a, :alpha
132
+
133
+ def self.convert(value)
134
+ case value
135
+ when String
136
+ value = value.split(",").map(&:to_i)
137
+ when Array
138
+ when Color
139
+ return value
140
+ else
141
+ raise Hokusai::Error.new("Unsupported conversion type #{value.class} for Hokusai::Color")
142
+ end
143
+
144
+ new(value[0], value[1], value[2], value[3] || 255)
145
+ end
146
+
147
+ def to_shader_value
148
+ [(r / 255.0), (g / 255.0), (b / 255.0), (a / 255.0)]
149
+ end
150
+
151
+ def hash
152
+ [self.class, r, g, b, a].hash
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokusai
4
+ KEY_CODES = {
5
+ null: 0, apostrophe: 39, comma: 44, minus: 45, period: 46,
6
+ slash: 47, zero: 48, one: 49, two: 50, three: 51, four: 52,
7
+ five: 53, six: 54, seven: 55, eight: 56, nine: 57, semicolon: 59,
8
+ equal: 61, a: 65, b: 66, c: 67, d: 68, e: 69, f: 70, g: 71, h: 72,
9
+ i: 73, j: 74, k: 75, l: 76, m: 77, n: 78, o: 79, p: 80, q: 81, r: 82,
10
+ s: 83, t: 84, u: 85, v: 86, w: 87, x: 88, y: 89, z: 90, left_bracket: 91,
11
+ backslash: 92, right_bracket: 93, grave: 96, space: 32, escape: 256,
12
+ enter: 257, tab: 258, backspace: 259, insert: 260, delete: 261, right: 262,
13
+ left: 263, down: 264, up: 265, page_up: 266, page_down: 267, home: 268, end: 269,
14
+ caps_lock: 280, scroll_lock: 281, num_lock: 282, print_screen: 283, pause: 284,
15
+ f1: 290, f2: 291, f3: 292, f4: 293, f5: 294, f6: 295, f7: 296, f8: 297,
16
+ f9: 298, f10: 299, f11: 300, f12: 301, left_shift: 340, left_control: 341,
17
+ left_alt: 342, left_super: 343, right_shift: 344, right_control: 345, right_alt: 346,
18
+ right_super: 347, kb_menu: 348, kp_0: 320, kp_1: 321, kp_2: 322, kp_3: 323,
19
+ kp_4: 324, kp_5: 325, kp_6: 326, kp_7: 327, kp_8: 328, kp_9: 329, kp_decimal: 330,
20
+ kp_divide: 331, kp_multiply: 332, kp_subtract: 333, kp_add: 334, kp_enter: 335,
21
+ kp_equal: 336, back: 4, menu: 5, volume_up: 24, volume_down: 25
22
+ }
23
+
24
+ class Keyboard
25
+ attr_accessor :shift, :control, :super, :alt
26
+ attr_reader :keys, :pressed, :released
27
+
28
+ def initialize
29
+ @shift = false
30
+ @control = false
31
+ @super = false
32
+ @alt = false
33
+
34
+ @keys = {}
35
+ @pressed = []
36
+ @released = []
37
+
38
+ # populate the key states
39
+ KEY_CODES.each do |symbol, code|
40
+ @keys[symbol] = { code: code, symbol: symbol, up: false, down: false, pressed: false, released: false }
41
+ end
42
+ end
43
+
44
+ def code
45
+ pressed[0]&.[](:code)
46
+ end
47
+
48
+ def char
49
+ pressed[0]&.[](:char)
50
+ end
51
+
52
+ def ctrl
53
+ @control
54
+ end
55
+
56
+ def reset
57
+ @pressed.clear
58
+ @released.clear
59
+
60
+ @shift = false
61
+ @control = false
62
+ @super = false
63
+ @alt = false
64
+ end
65
+
66
+
67
+ def key_is_letter?(symbol)
68
+ symbol == :a || symbol == :b || symbol == :c || symbol == :d ||
69
+ symbol == :e || symbol == :f || symbol == :g || symbol == :h ||
70
+ symbol == :i || symbol == :j || symbol == :k || symbol == :l ||
71
+ symbol == :m || symbol == :n || symbol == :o || symbol == :p ||
72
+ symbol == :q || symbol == :r || symbol == :s || symbol == :t ||
73
+ symbol == :u || symbol == :v || symbol == :w || symbol == :x ||
74
+ symbol == :y || symbol == :z
75
+ end
76
+
77
+ def char_code_from_key(key, shift)
78
+ code = keys[key][:code]
79
+
80
+ if !shift && key_is_letter?(key)
81
+ code += 32
82
+ elsif shift && key == :apostrophe
83
+ code = 34
84
+ elsif shift && key == :comma
85
+ code = 60
86
+ elsif shift && key == :minus
87
+ code = 95
88
+ elsif shift && key == :period
89
+ code = 62
90
+ elsif shift && key == :slash
91
+ code = 63
92
+ elsif shift && key == :zero
93
+ code = 41
94
+ elsif shift && key == :one
95
+ code = 33
96
+ elsif shift && key == :two
97
+ code = 64
98
+ elsif shift && key == :three
99
+ code = 35
100
+ elsif shift && key == :four
101
+ code = 36
102
+ elsif shift && key == :five
103
+ code = 37
104
+ elsif shift && key == :six
105
+ code = 94
106
+ elsif shift && key == :seven
107
+ code = 38
108
+ elsif shift && key == :eight
109
+ code = 42
110
+ elsif shift && key == :nine
111
+ code = 40
112
+ elsif shift && key == :semicolon
113
+ code = 58
114
+ elsif shift && key == :equal
115
+ code = 43
116
+ elsif shift && key == :left_bracket
117
+ code = 123
118
+ elsif shift && key == :backslash
119
+ code = 124
120
+ elsif shift && key == :right_bracket
121
+ code = 125
122
+ elsif shift && key == :grave
123
+ code = 126
124
+ end
125
+
126
+ return code if code <= 256
127
+ end
128
+
129
+ def set(key, down)
130
+ if down
131
+ case key
132
+ when :left_shift, :right_shift
133
+ @shift = true
134
+ when :left_control, :right_control
135
+ @control = true
136
+ when :left_super, :right_super
137
+ @super = true
138
+ when :left_alt, :right_alt
139
+ @alt = true
140
+ end
141
+ end
142
+
143
+ if down && keys[key][:up]
144
+ keys[key][:pressed] = true
145
+ keys[key][:released]= false
146
+
147
+ nkey = keys[key].dup
148
+ nkey.merge!({ char: char_code_from_key(key, shift)&.chr })
149
+
150
+ pressed << nkey
151
+ elsif !down && keys[key][:down]
152
+ keys[key][:pressed] = false
153
+ keys[key][:released] = true
154
+
155
+ nkey = keys[key].dup
156
+ nkey.merge!({ char: char_code_from_key(key, shift)&.chr })
157
+
158
+ released << nkey
159
+ else
160
+ keys[key][:pressed] = false
161
+ keys[key][:released] = false
162
+ end
163
+
164
+ keys[key][:down] = down
165
+ keys[key][:up] = !down
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokusai
4
+ class MouseButton
5
+ attr_accessor :up, :down, :clicked, :released
6
+
7
+ def initialize
8
+ @up = false
9
+ @down = false
10
+ @clicked = false
11
+ @released = false
12
+ end
13
+ end
14
+
15
+ class Mouse
16
+ attr_reader :pos, :delta, :left, :right, :middle, :scroll
17
+ attr_accessor :scroll_delta
18
+
19
+ def initialize
20
+ @pos = Vec2.new(0.0, 0.0)
21
+ @delta = Vec2.new(0.0, 0.0)
22
+ @scroll = 0.0
23
+ @scroll_delta = 0.0
24
+ @left = MouseButton.new
25
+ @middle = MouseButton.new
26
+ @right = MouseButton.new
27
+ end
28
+
29
+ def scroll=(val)
30
+ last = scroll
31
+ new_y = (last >= val) ? last - val : val - last
32
+ self.scroll_delta = new_y
33
+ @scroll = val
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokusai
4
+ class Vec2
5
+ attr_accessor :x, :y
6
+ def initialize(x, y)
7
+ @x = x
8
+ @y = y
9
+ end
10
+ end
11
+
12
+ class Rect
13
+ attr_accessor :x, :y, :width, :height
14
+
15
+ def initialize(x, y, width, height)
16
+ @x = x
17
+ @y = y
18
+ @width = width
19
+ @height = height
20
+ end
21
+ def to_hoku_rect
22
+ @hoku_rect ||= LibHokusai::HmlRect.create(x, y, width, height)
23
+ end
24
+
25
+ def includes_y?(y)
26
+ LibHokusai.hoku_rect_includes_y(to_hoku_rect, y)
27
+ end
28
+
29
+ def includes_x?(x)
30
+ LibHokusai.hoku_rect_includes_x(to_hoku_rect, x)
31
+ end
32
+
33
+ def move_x_left(times = 1)
34
+ LibHokusai.hoku_rect_x_left(to_hoku_rect, times)
35
+ end
36
+
37
+ def move_x_right(times = 1)
38
+ LibHokusai.hoku_rect_x_right(to_hoku_rect, times)
39
+ end
40
+
41
+ def move_y_up(times = 1)
42
+ LibHokusai.hoku_rect_y_up(to_hoku_rect, times)
43
+ end
44
+
45
+ def move_y_down(times = 1)
46
+ LibHokusai.hoku_rect_y_down(to_hoku_rect, times)
47
+ end
48
+
49
+ def self.from_hoku_rect(rect)
50
+ self.x = rect[:x]
51
+ self.y = rect[:y]
52
+ self.width = rect[:w]
53
+ self.height = rect[:h]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hokusai
4
+ class Touch
5
+ attr_accessor :stack, :archive
6
+
7
+ def initialize
8
+ @stack = []
9
+ @archive = []
10
+ @tapped = false
11
+ @swiped = false
12
+ @pinched = false
13
+ # @file = File.open("touch.log", "w")
14
+ end
15
+
16
+ def tapped?
17
+ @tapped
18
+ end
19
+
20
+ def swiped?
21
+ @swiped
22
+ end
23
+
24
+ def pinched?
25
+ @pinched
26
+ end
27
+
28
+ def longtapping?(stuff = "ok")
29
+ log("#{touching?} - #{elapsed(token)} - #{stuff}") if touching?
30
+ touching? && elapsed(token) > 5
31
+ end
32
+
33
+ def longtapped?
34
+ @longtapped
35
+ end
36
+
37
+ def touching?
38
+ type == :down || type == :move
39
+ end
40
+
41
+ def duration
42
+ if longtapping?
43
+ return elapsed(token)
44
+ end
45
+
46
+ first, last = archive[-2..-1]
47
+
48
+ last[:start] - first[:start]
49
+ end
50
+
51
+ def distance
52
+ raise Hokusai::Error.new("Archive is empty") if archive.empty?
53
+ first, last = archive[-2..-1]
54
+
55
+ x = last[:x] - first[:x]
56
+ y = last[:y] - first[:y]
57
+
58
+ [x, y]
59
+ end
60
+
61
+ def direction
62
+ raise Hokusai::Error.new("Archive is empty") if archive.empty?
63
+
64
+ first, last = archive[-2..-1]
65
+
66
+ x = last[:x] - first[:x]
67
+ y = last[:y] - first[:y]
68
+
69
+ if x.abs > y.abs
70
+ # swiping left/right
71
+ last[:x] > first[:x] ? :right : :left
72
+ else
73
+ # swiping up/down
74
+ last[:y] > first[:y] ? :down : :up
75
+ end
76
+ end
77
+
78
+ def angle
79
+ raise Hokusai::Error.new("Archive is empty") if archive.empty?
80
+
81
+ last, first = archive[-2..-1]
82
+
83
+ x = last[:x] - first[:x]
84
+ y = last[:y] - first[:y]
85
+
86
+ (Math.atan2(x, y) * (-180 / Math::PI)).round(0).to_i
87
+ end
88
+
89
+ def log(str)
90
+ # Thread.new do
91
+ # @file.write_nonblock("#{str}\n")
92
+ # end
93
+ end
94
+
95
+ def record(finger, x, y)
96
+ log("recording #{token}")
97
+ if type == :down
98
+ push(:move, finger, x, y)
99
+ log("state is move")
100
+ elsif type == :move
101
+ stack.last[:x] = x
102
+ stack.last[:y] = y
103
+
104
+ log("updated state move")
105
+ else
106
+ @longtapped = false
107
+ @swiped = false
108
+ @tapped = false
109
+ push(:down, finger, x, y)
110
+ log("state is down")
111
+ end
112
+ end
113
+
114
+ def clear
115
+ # log("clearing")
116
+ if type == :move
117
+ log("elapsed: #{elapsed(token)}")
118
+ if elapsed(token) > 300 && within(10.0)
119
+ @longtapped = true
120
+ log('longtap')
121
+ elsif within(10.0)
122
+ @tapped = true
123
+ else
124
+ @swiped = true
125
+ log('swipe')
126
+ end
127
+ elsif type == :down
128
+ @tapped = true
129
+ log('tap')
130
+ else
131
+ @longtapped = false
132
+ @swiped = false
133
+ @tapped = false
134
+ end
135
+
136
+ self.archive = stack.dup
137
+ stack.clear
138
+ end
139
+
140
+ def elapsed(token)
141
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - token[:start]
142
+ end
143
+
144
+ def within(threshold)
145
+ move = stack.last
146
+ down = stack[-2]
147
+
148
+ t1 = (move[:x] - down[:x]).abs
149
+ t2 = (move[:y] - down[:y]).abs
150
+
151
+ t1 < threshold && t2 < threshold
152
+ end
153
+
154
+ def pop
155
+ stack.pop
156
+ end
157
+
158
+ def push(type, finger, x, y)
159
+ log("push: #{type}")
160
+ stack << {
161
+ type: type,
162
+ i: finger,
163
+ x: x,
164
+ y: y,
165
+ start: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
166
+ }
167
+ end
168
+
169
+ def index
170
+ token&.[](:finger)
171
+ end
172
+
173
+ def type
174
+ token&.[](:type)
175
+ end
176
+
177
+ def token
178
+ @stack.last
179
+ end
180
+ end
181
+ end