hokusai-zero 0.2.6.pre.pinephone → 0.2.6.pre.pinephone3
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.
- checksums.yaml +4 -4
- data/ast/src/core/input.c +0 -85
- data/ast/src/core/input.h +0 -32
- data/hokusai.gemspec +1 -1
- data/ui/examples/embedded.rb +0 -1
- data/ui/examples/forum/file.rb +1 -1
- data/ui/examples/forum/post.rb +0 -1
- data/ui/examples/forum.rb +7 -7
- data/ui/examples/keyboard.rb +47 -0
- data/ui/examples/shader/test.rb +28 -18
- data/ui/examples/spreadsheet.rb +12 -11
- data/ui/lib/lib_hokusai.rb +19 -39
- data/ui/spec/hokusai/e2e/keyboard_spec.rb +52 -0
- data/ui/src/hokusai/assets/icons/outline/arrow-big-up.svg +19 -0
- data/ui/src/hokusai/assets/icons/outline/backspace.svg +20 -0
- data/ui/src/hokusai/backends/raylib.rb +7 -5
- data/ui/src/hokusai/backends/sdl2/config.rb +9 -6
- data/ui/src/hokusai/backends/sdl2/font.rb +3 -1
- data/ui/src/hokusai/backends/sdl2.rb +164 -65
- data/ui/src/hokusai/blocks/input.rb +1 -1
- data/ui/src/hokusai/blocks/keyboard.rb +234 -0
- data/ui/src/hokusai/blocks/slider.rb +139 -0
- data/ui/src/hokusai/commands/rect.rb +2 -2
- data/ui/src/hokusai/event.rb +1 -1
- data/ui/src/hokusai/events/{embedded.rb → touch.rb} +12 -18
- data/ui/src/hokusai/painter.rb +5 -5
- data/ui/src/hokusai/types.rb +180 -17
- data/ui/src/hokusai/util/wrap_stream.rb +197 -0
- data/ui/src/hokusai.rb +61 -30
- data/xmake.lua +1 -1
- metadata +9 -6
- data/ui/src/hokusai/backends/embedded/config.rb +0 -47
- data/ui/src/hokusai/backends/embedded/font.rb +0 -112
- data/ui/src/hokusai/backends/embedded/keys.rb +0 -124
- data/ui/src/hokusai/backends/embedded.rb +0 -564
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
module Hokusai::Blocks
|
3
|
+
class Slider < Hokusai::Block
|
4
|
+
template <<~EOF
|
5
|
+
[template]
|
6
|
+
empty {
|
7
|
+
@click="start_slider"
|
8
|
+
@mousemove="move_slider"
|
9
|
+
@mouseup="stop_slider"
|
10
|
+
}
|
11
|
+
EOF
|
12
|
+
|
13
|
+
uses(empty: Hokusai::Blocks::Empty)
|
14
|
+
|
15
|
+
computed :fill, default: [61,171,211], convert: Hokusai::Color
|
16
|
+
computed :initial, default: 0.0, convert: proc(&:to_f)
|
17
|
+
computed :size, default: 50.0, convert: proc(&:to_f)
|
18
|
+
computed :step, default: 20.0, convert: proc(&:to_f)
|
19
|
+
computed :min, default: 0.0, convert: proc(&:to_f)
|
20
|
+
computed :max, default: 100.0, convert: proc(&:to_f)
|
21
|
+
|
22
|
+
attr_reader :slider_width, :slider_start, :steps_x, :steps_val
|
23
|
+
attr_accessor :sliding, :slider_x, :last_index
|
24
|
+
|
25
|
+
def initialize(**args)
|
26
|
+
@sliding = false
|
27
|
+
@slider_width = 0.0
|
28
|
+
@slider_start = 0.0
|
29
|
+
@slider_x = 0.0
|
30
|
+
@last_index = 0
|
31
|
+
@configured = false
|
32
|
+
|
33
|
+
super
|
34
|
+
|
35
|
+
@last_max = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_slider(event)
|
39
|
+
self.sliding = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_resize(canvas)
|
43
|
+
# create our buckets for steps
|
44
|
+
@slider_start = canvas.x
|
45
|
+
@slider_width = canvas.width
|
46
|
+
|
47
|
+
@steps_val = [*(min..max).step(step).to_a, max]
|
48
|
+
steps_val.pop if steps_val[-1] == steps_val[-2]
|
49
|
+
|
50
|
+
step_x = (slider_width) / (steps_val.size - 1)
|
51
|
+
@steps_x = (slider_start..(slider_start + slider_width)).step(step_x).to_a
|
52
|
+
|
53
|
+
(steps_val.size - steps_x.size).times do |i|
|
54
|
+
steps_x << steps_x.last + step_x * i
|
55
|
+
end
|
56
|
+
|
57
|
+
steps_x[-1] = slider_start + slider_width if slider_start + slider_width != steps_x.last
|
58
|
+
end
|
59
|
+
|
60
|
+
def move_slider(event)
|
61
|
+
if sliding && event.left.down
|
62
|
+
pos = event.pos.x
|
63
|
+
index = steps_x.size - 1
|
64
|
+
|
65
|
+
(0...steps_x.size - 1).each do |i|
|
66
|
+
next if steps_x[i + 1] && pos - steps_x[i + 1] > step
|
67
|
+
|
68
|
+
if pos - steps_x[i] > pos - steps_x[i + 1]
|
69
|
+
index = i
|
70
|
+
break
|
71
|
+
else
|
72
|
+
index = i + 1
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
self.last_index = index
|
78
|
+
|
79
|
+
emit("updated", steps_val[index])
|
80
|
+
else
|
81
|
+
self.sliding = false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def stop_slider(event)
|
86
|
+
if event.left.up
|
87
|
+
self.sliding = false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def render(canvas)
|
92
|
+
if max != @last_max
|
93
|
+
on_resize(canvas)
|
94
|
+
|
95
|
+
@last_max = max
|
96
|
+
end
|
97
|
+
|
98
|
+
unless @setup || steps_val.nil? || initial.nil?
|
99
|
+
steps_val.each_with_index do |val, index|
|
100
|
+
if val == initial
|
101
|
+
self.last_index = index
|
102
|
+
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
@setup = true
|
108
|
+
end
|
109
|
+
|
110
|
+
slider_x = steps_x[last_index]
|
111
|
+
padding = Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0)
|
112
|
+
percent = slider_x * 100.00
|
113
|
+
x = slider_x + padding.left
|
114
|
+
x = (canvas.x + canvas.width) - (size / 2) - 2 if x > (canvas.x + canvas.width) - (size / 2)
|
115
|
+
x = canvas.x if x < canvas.x
|
116
|
+
cursor = (x + size / 2)
|
117
|
+
|
118
|
+
draw do
|
119
|
+
# draw background
|
120
|
+
rect(canvas.x + padding.left, canvas.y + padding.top, canvas.width - padding.right - padding.left, size) do |command|
|
121
|
+
command.round = size / 2
|
122
|
+
command.color = Hokusai::Color.new(33, 33, 33)
|
123
|
+
command.padding = Hokusai::Padding.new(5.0, 20.0, 5.0, 20.0)
|
124
|
+
end
|
125
|
+
|
126
|
+
rect(canvas.x + padding.left + 1, canvas.y + padding.top + 1, x + (size / 2) - canvas.x - 2, size - 2) do |command|
|
127
|
+
command.round = size / 2
|
128
|
+
command.color = fill
|
129
|
+
end
|
130
|
+
|
131
|
+
circle(cursor + padding.left, canvas.y + (size / 2) + padding.top, size / 2) do |command|
|
132
|
+
command.color = Hokusai::Color.new(233, 233, 233)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
yield canvas
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -11,8 +11,8 @@ module Hokusai
|
|
11
11
|
@height = height.to_f
|
12
12
|
@outline = Outline.default
|
13
13
|
@rounding = 0.0
|
14
|
-
@color = Color.new(
|
15
|
-
@outline_color = Color.new(0, 0, 0,
|
14
|
+
@color = Color.new(255, 255, 255, 0)
|
15
|
+
@outline_color = Color.new(0, 0, 0, 0)
|
16
16
|
@padding = Padding.new(0.0, 0.0, 0.0, 0.0)
|
17
17
|
@gradient = nil
|
18
18
|
end
|
data/ui/src/hokusai/event.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module Hokusai
|
2
|
-
class
|
2
|
+
class TouchEvent < Event
|
3
3
|
extend Forwardable
|
4
4
|
|
5
|
-
def_delegators :@
|
6
|
-
|
7
|
-
|
5
|
+
def_delegators :@touch, :tapped?, :swiped?, :longtapped?, :longtapping?, :touching?,
|
6
|
+
:duration, :direction, :distance, :angle, :position, :last_position,
|
7
|
+
:touch_len, :touch_count, :timer
|
8
8
|
|
9
9
|
attr_reader :input
|
10
10
|
|
11
11
|
def initialize(input, state)
|
12
12
|
@input = input
|
13
13
|
@state = state
|
14
|
-
@
|
14
|
+
@touch = input.touch
|
15
15
|
end
|
16
16
|
|
17
17
|
def hovered(canvas)
|
@@ -28,37 +28,31 @@ module Hokusai
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
class TapHoldEvent <
|
31
|
+
class TapHoldEvent < TouchEvent
|
32
32
|
name "taphold"
|
33
33
|
|
34
34
|
def capture(block, canvas)
|
35
|
-
if
|
36
|
-
block
|
37
|
-
|
38
|
-
if matches(block)
|
39
|
-
captures << block
|
40
|
-
end
|
41
|
-
elsif hold
|
42
|
-
block.node.meta.blur
|
35
|
+
if matches(block) && longtapped? && hovered(canvas)
|
36
|
+
captures << block
|
43
37
|
end
|
44
38
|
end
|
45
39
|
end
|
46
40
|
|
47
|
-
class PinchEvent <
|
41
|
+
class PinchEvent < TouchEvent
|
48
42
|
name "pinch"
|
49
43
|
|
50
44
|
def capture(block, canvas)
|
51
|
-
if
|
45
|
+
if false && matches(block)
|
52
46
|
captures << block
|
53
47
|
end
|
54
48
|
end
|
55
49
|
end
|
56
50
|
|
57
|
-
class SwipeEvent <
|
51
|
+
class SwipeEvent < TouchEvent
|
58
52
|
name "swipe"
|
59
53
|
|
60
54
|
def capture(block, canvas)
|
61
|
-
if
|
55
|
+
if swiped? && matches(block)
|
62
56
|
captures << block
|
63
57
|
end
|
64
58
|
end
|
data/ui/src/hokusai/painter.rb
CHANGED
@@ -46,10 +46,10 @@ module Hokusai
|
|
46
46
|
keypress: KeyPressEvent.new(input, state)
|
47
47
|
}
|
48
48
|
|
49
|
-
|
49
|
+
add_touch_events(events, input, state) unless input.touch.nil?
|
50
50
|
end
|
51
51
|
|
52
|
-
def
|
52
|
+
def add_touch_events(events, input, state)
|
53
53
|
events.merge!({
|
54
54
|
taphold: TapHoldEvent.new(input, state),
|
55
55
|
pinch: PinchEvent.new(input, state),
|
@@ -181,7 +181,7 @@ module Hokusai
|
|
181
181
|
events[:mousedown].bubble
|
182
182
|
events[:mouseup].bubble
|
183
183
|
|
184
|
-
unless input.
|
184
|
+
unless input.touch.nil?
|
185
185
|
events[:taphold].bubble
|
186
186
|
events[:pinch].bubble
|
187
187
|
events[:swipe].bubble
|
@@ -277,12 +277,12 @@ module Hokusai
|
|
277
277
|
end
|
278
278
|
events[:mousemove].capture(block, canvas)
|
279
279
|
|
280
|
-
if block_is_hovered || block.node.meta.focused
|
280
|
+
if block_is_hovered || block.node.meta.focused || input.keyboard_override
|
281
281
|
events[:keyup].capture(block, canvas)
|
282
282
|
events[:keypress].capture(block, canvas)
|
283
283
|
end
|
284
284
|
|
285
|
-
unless input.
|
285
|
+
unless input.touch.nil?
|
286
286
|
events[:taphold].capture(block, canvas)
|
287
287
|
events[:pinch].capture(block, canvas)
|
288
288
|
events[:swipe].capture(block, canvas)
|
data/ui/src/hokusai/types.rb
CHANGED
@@ -96,6 +96,14 @@ module Hokusai
|
|
96
96
|
alias_method :r, :right
|
97
97
|
alias_method :b, :bottom
|
98
98
|
|
99
|
+
def width
|
100
|
+
right + left
|
101
|
+
end
|
102
|
+
|
103
|
+
def height
|
104
|
+
top + bottom
|
105
|
+
end
|
106
|
+
|
99
107
|
def self.convert(value)
|
100
108
|
case value
|
101
109
|
when String
|
@@ -240,31 +248,184 @@ module Hokusai
|
|
240
248
|
end
|
241
249
|
end
|
242
250
|
|
243
|
-
class
|
244
|
-
|
251
|
+
class Touch
|
252
|
+
attr_accessor :stack, :archive
|
253
|
+
def initialize
|
254
|
+
@stack = []
|
255
|
+
@archive = []
|
256
|
+
@tapped = false
|
257
|
+
@swiped = false
|
258
|
+
@pinched = false
|
259
|
+
# @file = File.open("touch.log", "w")
|
260
|
+
end
|
261
|
+
|
262
|
+
def tapped?
|
263
|
+
@tapped
|
264
|
+
end
|
265
|
+
|
266
|
+
def swiped?
|
267
|
+
@swiped
|
268
|
+
end
|
269
|
+
|
270
|
+
def pinched?
|
271
|
+
@pinched
|
272
|
+
end
|
273
|
+
|
274
|
+
def longtapping?(stuff = "ok")
|
275
|
+
log("#{touching?} - #{elapsed(token)} - #{stuff}") if touching?
|
276
|
+
touching? && elapsed(token) > 5
|
277
|
+
end
|
245
278
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
279
|
+
def longtapped?
|
280
|
+
@longtapped
|
281
|
+
end
|
282
|
+
|
283
|
+
def touching?
|
284
|
+
type == :down || type == :move
|
285
|
+
end
|
286
|
+
|
287
|
+
def duration
|
288
|
+
if longtapping?
|
289
|
+
return elapsed(token)
|
253
290
|
end
|
291
|
+
|
292
|
+
first, last = archive[-2..-1]
|
254
293
|
|
255
|
-
|
256
|
-
|
294
|
+
last[:start] - first[:start]
|
295
|
+
end
|
296
|
+
|
297
|
+
def distance
|
298
|
+
raise Hokusai::Error.new("Archive is empty") if archive.empty?
|
299
|
+
first, last = archive[-2..-1]
|
300
|
+
|
301
|
+
x = last[:x] - first[:x]
|
302
|
+
y = last[:y] - first[:y]
|
303
|
+
|
304
|
+
[x, y]
|
305
|
+
end
|
306
|
+
|
307
|
+
def direction
|
308
|
+
raise Hokusai::Error.new("Archive is empty") if archive.empty?
|
309
|
+
|
310
|
+
first, last = archive[-2..-1]
|
311
|
+
|
312
|
+
x = last[:x] - first[:x]
|
313
|
+
y = last[:y] - first[:y]
|
314
|
+
|
315
|
+
if x.abs > y.abs
|
316
|
+
# swiping left/right
|
317
|
+
last[:x] > first[:x] ? :right : :left
|
318
|
+
else
|
319
|
+
# swiping up/down
|
320
|
+
last[:y] > first[:y] ? :down : :up
|
257
321
|
end
|
258
322
|
end
|
259
323
|
|
324
|
+
def angle
|
325
|
+
raise Hokusai::Error.new("Archive is empty") if archive.empty?
|
260
326
|
|
261
|
-
|
262
|
-
|
327
|
+
last, first = archive[-2..-1]
|
328
|
+
|
329
|
+
x = last[:x] - first[:x]
|
330
|
+
y = last[:y] - first[:y]
|
331
|
+
|
332
|
+
(Math.atan2(x, y) * (-180 / Math::PI)).round(0).to_i
|
333
|
+
end
|
334
|
+
|
335
|
+
def log(str)
|
336
|
+
# Thread.new do
|
337
|
+
# @file.write_nonblock("#{str}\n")
|
338
|
+
# end
|
339
|
+
end
|
340
|
+
|
341
|
+
def record(finger, x, y)
|
342
|
+
log("recording #{token}")
|
343
|
+
if type == :down
|
344
|
+
push(:move, finger, x, y)
|
345
|
+
log("state is move")
|
346
|
+
elsif type == :move
|
347
|
+
stack.last[:x] = x
|
348
|
+
stack.last[:y] = y
|
349
|
+
|
350
|
+
log("updated state move")
|
351
|
+
else
|
352
|
+
@longtapped = false
|
353
|
+
@swiped = false
|
354
|
+
@tapped = false
|
355
|
+
push(:down, finger, x, y)
|
356
|
+
log("state is down")
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def clear
|
361
|
+
log("clearing")
|
362
|
+
if type == :move
|
363
|
+
log("elapsed: #{elapsed(token)}")
|
364
|
+
if elapsed(token) > 0.05 && within(10.0)
|
365
|
+
@longtapped = true
|
366
|
+
log('longtap')
|
367
|
+
else
|
368
|
+
@swiped = true
|
369
|
+
log('swipe')
|
370
|
+
end
|
371
|
+
elsif type == :down
|
372
|
+
@tapped = true
|
373
|
+
log('tap')
|
374
|
+
else
|
375
|
+
@longtapped = false
|
376
|
+
@swiped = false
|
377
|
+
@tapped = false
|
378
|
+
end
|
379
|
+
|
380
|
+
self.archive = stack.dup
|
381
|
+
stack.clear
|
382
|
+
end
|
383
|
+
|
384
|
+
def elapsed(token)
|
385
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - token[:start]
|
386
|
+
end
|
387
|
+
|
388
|
+
def within(threshold)
|
389
|
+
move = stack.last
|
390
|
+
down = stack[-2]
|
391
|
+
|
392
|
+
t1 = (move[:x] - down[:x]).abs
|
393
|
+
t2 = (move[:y] - down[:y]).abs
|
394
|
+
|
395
|
+
t1 < threshold && t2 < threshold
|
396
|
+
end
|
397
|
+
|
398
|
+
def pop
|
399
|
+
stack.pop
|
400
|
+
end
|
401
|
+
|
402
|
+
def push(type, finger, x, y)
|
403
|
+
log("push: #{type}")
|
404
|
+
stack << {
|
405
|
+
type: type,
|
406
|
+
i: finger,
|
407
|
+
x: x,
|
408
|
+
y: y,
|
409
|
+
start: Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
410
|
+
}
|
411
|
+
end
|
412
|
+
|
413
|
+
def index
|
414
|
+
token&.[](:finger)
|
415
|
+
end
|
416
|
+
|
417
|
+
def type
|
418
|
+
token&.[](:type)
|
419
|
+
end
|
420
|
+
|
421
|
+
def token
|
422
|
+
@stack.last
|
263
423
|
end
|
264
424
|
end
|
265
425
|
|
266
426
|
class Input
|
267
|
-
|
427
|
+
attr_accessor :keyboard_override
|
428
|
+
attr_reader :raw, :touch
|
268
429
|
|
269
430
|
def hash
|
270
431
|
[self.class, mouse.pos.x, mouse.pos.y, mouse.scroll, mouse.left.clicked, mouse.left.down, mouse.left.up].hash
|
@@ -272,12 +433,14 @@ module Hokusai
|
|
272
433
|
|
273
434
|
def initialize(raw)
|
274
435
|
@raw = raw
|
436
|
+
@touch = nil
|
437
|
+
@keyboard_override = false
|
275
438
|
end
|
276
439
|
|
277
|
-
def
|
278
|
-
|
440
|
+
def support_touch!
|
441
|
+
@touch ||= Touch.new
|
279
442
|
|
280
|
-
|
443
|
+
self
|
281
444
|
end
|
282
445
|
|
283
446
|
def keyboard
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require "rouge"
|
2
|
+
require "hokusai"
|
3
|
+
require_relative "./lib/hokusai_code/formatter"
|
4
|
+
|
5
|
+
module Hokusai::Util
|
6
|
+
class Wrapped
|
7
|
+
attr_accessor :text, :x, :y, :extra
|
8
|
+
|
9
|
+
def initialize(text, x, y, extra)
|
10
|
+
@text = text
|
11
|
+
@x = x
|
12
|
+
@y = y
|
13
|
+
@extra = extra
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class SelectionWrapper
|
18
|
+
attr_accessor :x, :y, :width, :height
|
19
|
+
|
20
|
+
def initialize(x, y, w, h)
|
21
|
+
@x = x
|
22
|
+
@y = y
|
23
|
+
@width = w
|
24
|
+
@height = h
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(w)
|
28
|
+
@width += w
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class WrapStream
|
33
|
+
attr_accessor :width, :current_width, :x, :y,
|
34
|
+
:origin_x, :origin_y, :buffer, :stack
|
35
|
+
attr_reader :on_text_cb, :on_text_selection_cb, :on_advancex_cb, :selector, :padding
|
36
|
+
|
37
|
+
def initialize(width, &measure)
|
38
|
+
@width = width
|
39
|
+
@current_width = 0.0
|
40
|
+
|
41
|
+
@origin_x = 0.0
|
42
|
+
@origin_y = 0.0
|
43
|
+
@x = @origin_x
|
44
|
+
@y = @origin_y
|
45
|
+
|
46
|
+
@stack = []
|
47
|
+
@buffer = ""
|
48
|
+
|
49
|
+
@measure_cb = measure
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_advancex(&block)
|
53
|
+
@on_advancex_cb = block
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_text_selection(selector, padding, &block)
|
57
|
+
@selector = selector
|
58
|
+
@padding = padding || Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0)
|
59
|
+
@on_text_selection_cb = block
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_text(&block)
|
63
|
+
@on_text_cb = block
|
64
|
+
end
|
65
|
+
|
66
|
+
def measure(string, extra)
|
67
|
+
@measure_cb.call(string, extra)
|
68
|
+
end
|
69
|
+
|
70
|
+
def flush
|
71
|
+
sx = x + padding.left
|
72
|
+
wrapper = nil
|
73
|
+
stack.each do |(range, extra)|
|
74
|
+
str = buffer[range]
|
75
|
+
if selector
|
76
|
+
str.split("").each do |char|
|
77
|
+
nw, nh = measure(char, extra)
|
78
|
+
ox = on_advancex_cb.call(char.codepoints.first)
|
79
|
+
|
80
|
+
if selector.selected(sx, y + padding.top - selector.offset_y, nw, nh)
|
81
|
+
wrapper ||= SelectionWrapper.new(sx, y + padding.top, 0.0, nh)
|
82
|
+
wrapper << ox
|
83
|
+
end
|
84
|
+
|
85
|
+
sx += ox
|
86
|
+
end
|
87
|
+
|
88
|
+
on_text_selection_cb.call(wrapper) if wrapper
|
89
|
+
wrapper = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
nw, _ = measure(str, extra)
|
93
|
+
|
94
|
+
# hard break on the buffer, split on character
|
95
|
+
wrap_and_call(str, extra)
|
96
|
+
|
97
|
+
self.x += nw
|
98
|
+
end
|
99
|
+
|
100
|
+
self.current_width = 0.0
|
101
|
+
self.buffer = ""
|
102
|
+
stack.clear
|
103
|
+
self.x = origin_x
|
104
|
+
end
|
105
|
+
|
106
|
+
def wrap(text, extra)
|
107
|
+
offset = 0
|
108
|
+
size = text.size
|
109
|
+
|
110
|
+
stack << [((buffer.size)..(text.size + buffer.size - 1)), extra]
|
111
|
+
|
112
|
+
while offset < size
|
113
|
+
char = text[offset]
|
114
|
+
w, h = measure(char, extra)
|
115
|
+
|
116
|
+
# if it's a newline we want to break
|
117
|
+
if char =~ /\n|\r\n/
|
118
|
+
flush
|
119
|
+
|
120
|
+
stack << [(0...(text.size - offset - 1)), extra]
|
121
|
+
self.y += h
|
122
|
+
self.x = origin_x
|
123
|
+
offset += 1
|
124
|
+
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
# if adding this char extends beyond the boundary
|
129
|
+
if current_width + w > width
|
130
|
+
# find the last space
|
131
|
+
idx = buffer.rindex(" ")
|
132
|
+
|
133
|
+
# if there is a break in this line split the buffer
|
134
|
+
# and render the current line
|
135
|
+
unless idx.nil? || idx < (buffer.size / 2)
|
136
|
+
cur = []
|
137
|
+
nex = []
|
138
|
+
found = false
|
139
|
+
|
140
|
+
# We need to split up both the buffer, and the ranges
|
141
|
+
while payload = stack.shift
|
142
|
+
range, xtra = payload
|
143
|
+
if range.include?(idx)
|
144
|
+
# putting the space on this line
|
145
|
+
cur << [(range.begin..idx), xtra]
|
146
|
+
# pp [range, idx]
|
147
|
+
nex << [(0..(range.end - idx - 1)), xtra] unless idx == range.end
|
148
|
+
|
149
|
+
found = true
|
150
|
+
elsif !found
|
151
|
+
cur << payload
|
152
|
+
else
|
153
|
+
nex << [((range.begin - idx - 1)..(range.end - idx - 1)), xtra]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
scur = buffer[0..idx]
|
158
|
+
snex = buffer[(idx + 1)..-1]
|
159
|
+
|
160
|
+
cur.each do |(range, xtra)|
|
161
|
+
str = scur[range]
|
162
|
+
|
163
|
+
nw, _ = measure(str, xtra)
|
164
|
+
wrap_and_call(str, xtra)
|
165
|
+
|
166
|
+
self.x += nw
|
167
|
+
end
|
168
|
+
|
169
|
+
self.buffer = snex + char
|
170
|
+
self.stack = nex
|
171
|
+
self.y += h
|
172
|
+
self.current_width = measure(buffer, extra).first
|
173
|
+
self.x = origin_x
|
174
|
+
else
|
175
|
+
# break on this word
|
176
|
+
flush
|
177
|
+
|
178
|
+
self.y += h
|
179
|
+
self.current_width = measure(char, extra).first
|
180
|
+
self.buffer = char
|
181
|
+
stack << [(0...(text.size - offset)), extra]
|
182
|
+
end
|
183
|
+
else
|
184
|
+
self.current_width += w
|
185
|
+
buffer << char
|
186
|
+
end
|
187
|
+
|
188
|
+
offset += 1
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
private def wrap_and_call(text, extra)
|
194
|
+
on_text_cb.call Wrapped.new(text, x, y, extra)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|