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
@@ -1,266 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Hokusai
4
- # Rect = Struct.new(:x, :y, :width, :height) do
5
- class Rect
6
- attr_accessor :x, :y, :width, :height
7
-
8
- def initialize(x, y, width, height)
9
- @x = x
10
- @y = y
11
- @width = width
12
- @height = height
13
- end
14
- def to_hoku_rect
15
- @hoku_rect ||= LibHokusai::HmlRect.create(x, y, width, height)
16
- end
17
-
18
- def includes_y?(y)
19
- LibHokusai.hoku_rect_includes_y(to_hoku_rect, y)
20
- end
21
-
22
- def includes_x?(x)
23
- LibHokusai.hoku_rect_includes_x(to_hoku_rect, x)
24
- end
25
-
26
- def move_x_left(times = 1)
27
- LibHokusai.hoku_rect_x_left(to_hoku_rect, times)
28
- end
29
-
30
- def move_x_right(times = 1)
31
- LibHokusai.hoku_rect_x_right(to_hoku_rect, times)
32
- end
33
-
34
- def move_y_up(times = 1)
35
- LibHokusai.hoku_rect_y_up(to_hoku_rect, times)
36
- end
37
-
38
- def move_y_down(times = 1)
39
- LibHokusai.hoku_rect_y_down(to_hoku_rect, times)
40
- end
41
-
42
- def self.from_hoku_rect(rect)
43
- self.x = rect[:x]
44
- self.y = rect[:y]
45
- self.width = rect[:w]
46
- self.height = rect[:h]
47
- end
48
- end
49
-
50
- Outline = Struct.new(:top, :right, :bottom, :left) do
51
- def self.default
52
- new(0.0, 0.0, 0.0, 0.0)
53
- end
3
+ require_relative "./types/primitives"
4
+ require_relative "./types/display"
5
+ require_relative "./types/touch"
6
+ require_relative "./types/mouse"
7
+ require_relative "./types/keyboard"
54
8
 
55
- def hash
56
- [self.class, top, right, bottom, left].hash
57
- end
58
-
59
- def self.convert(value)
60
- case value
61
- when String
62
- if value =~ /,/
63
- convert(value.split(",").map(&:to_f))
64
- else
65
- convert(value.to_f)
66
- end
67
- when Float
68
- new(value, value, value, value)
69
- when Array
70
- new(value[0] || 0.0, value[1] || 0.0, value[2] || 0.0, value[3] || 0.0)
71
- when Outline
72
- value
73
- end
74
- end
75
-
76
- def present?
77
- top > 0.0 || right > 0.0 || bottom > 0.0 || left > 0.0
78
- end
79
-
80
- def uniform?
81
- top == right && top == bottom && top == left
82
- end
83
- end
84
-
85
- class Padding
86
- attr_reader :top, :left, :right, :bottom
87
- def initialize(top, right, bottom, left)
88
- @top = top
89
- @left = left
90
- @right = right
91
- @bottom = bottom
92
- end
93
-
94
- alias_method :t, :top
95
- alias_method :l, :left
96
- alias_method :r, :right
97
- alias_method :b, :bottom
98
-
99
- def self.convert(value)
100
- case value
101
- when String
102
- if value =~ /,/
103
- convert(value.split(",").map(&:to_f))
104
- else
105
- convert(value.to_i)
106
- end
107
- when Integer
108
- new(value, value, value, value)
109
- when Array
110
- new(value[0], value[1], value[2], value[3])
111
- when Padding
112
- value
113
- else
114
- raise Hokusai::Error.new("Unsupported conversion type #{value.class} for Hokusai::Padding")
115
- end
116
- end
117
-
118
- def hash
119
- [self.class, top, right, bottom, left].hash
120
- end
121
- end
122
-
123
- class Canvas
124
- attr_accessor :width, :height, :x, :y, :vertical, :reverse
125
- def initialize(width, height, x = 0.0, y = 0.0, vertical = true, reverse = false)
126
- @width = width
127
- @height = height
128
- @x = x
129
- @y = y
130
- @vertical = vertical
131
- @reverse = reverse
132
- end
133
-
134
- def reset(x, y, width, height, vertical: true, reverse: false)
135
- self.x = x
136
- self.y = y
137
- self.width = width
138
- self.height = height
139
- self.vertical = vertical
140
- self.reverse = reverse
141
- end
142
-
143
- def to_bounds
144
- Hokusai::Rect.new(x, y, width, height)
145
- end
146
-
147
- def reverse?
148
- reverse
149
- end
150
-
151
- def to_hoku_rect
152
- LibHokusai::HmlRect.create(x, y, width, height)
153
- end
154
- end
155
-
156
- # Color = Struct.new(:red, :green, :blue, :alpha) do
157
- class Color
158
- attr_reader :red, :green, :blue, :alpha
159
- def initialize(red, green, blue, alpha = 255)
160
- @red = red.freeze
161
- @green = green.freeze
162
- @blue = blue.freeze
163
- @alpha = alpha.freeze
164
- end
165
-
166
- alias_method :r, :red
167
- alias_method :b, :blue
168
- alias_method :g, :green
169
- alias_method :a, :alpha
170
-
171
- def self.convert(value)
172
- case value
173
- when String
174
- value = value.split(",").map(&:to_i)
175
- when Array
176
- when Color
177
- return value
178
- else
179
- raise Hokusai::Error.new("Unsupported conversion type #{value.class} for Hokusai::Color")
180
- end
181
-
182
- new(value[0], value[1], value[2], value[3] || 255)
183
- end
9
+ module Hokusai
10
+ class Input
11
+ attr_accessor :keyboard_override
12
+ attr_reader :raw, :touch
184
13
 
185
14
  def hash
186
- [self.class, r, g, b, a].hash
187
- end
188
- end
189
-
190
- class Keyboard
191
- attr_reader :raw
192
-
193
- [
194
- :shift, :super, :ctrl,
195
- :alt, :pressed, :pressed_len, :released,
196
- :released_len
197
- ].each do |name|
198
- define_method(name) do
199
- raw[name]
200
- end
201
- end
202
-
203
- def keyboard_key
204
- pressed.read_array_of_type(TYPE_UINT8, :read_uint8, pressed_len).first
205
- end
206
-
207
- def key
208
- keyboard_key[:key][:key]
209
- end
210
-
211
- def initialize(raw)
212
- @raw = raw
213
- end
214
- end
215
-
216
- class Mouse
217
- attr_reader :raw
218
-
219
- def initialize(raw)
220
- @raw = raw
15
+ [self.class, mouse.pos.x, mouse.pos.y, mouse.scroll, mouse.left.clicked, mouse.left.down, mouse.left.up].hash
221
16
  end
222
17
 
223
- [
224
- :pos, :delta, :scroll,
225
- :scroll_delta, :selection,
226
- :selection_type, :left,
227
- :middle, :right
228
- ].each do |name|
229
- define_method(name) do
230
- # instance_variable_set("@#{name}", raw[name]) if instance_variable_get("@#{name}").nil?
231
- #
232
- # instance_variable_get("@#{name}")
233
-
234
- raw[name]
235
- end
236
-
237
- define_method("#{name}=") do |val|
238
- raw[name] = val
239
- end
18
+ def initialize
19
+ @touch = nil
20
+ @keyboard_override = false
240
21
  end
241
- end
242
22
 
243
- class Input
244
- attr_reader :raw
245
-
246
- def hash
247
- [self.class, mouse.pos.x, mouse.pos.y, mouse.scroll, mouse.left.clicked, mouse.left.down, mouse.left.up].hash
248
- end
23
+ def support_touch!
24
+ @touch ||= Touch.new
249
25
 
250
- def initialize(raw)
251
- @raw = raw
26
+ self
252
27
  end
253
28
 
254
29
  def keyboard
255
- Keyboard.new(@raw[:keyboard])
30
+ @keyboard ||= Keyboard.new
256
31
  end
257
32
 
258
33
  def mouse
259
- @mouse ||= Mouse.new(@raw[:mouse])
34
+ @mouse ||= Mouse.new
260
35
  end
261
36
 
262
37
  def hovered?(canvas)
263
- LibHokusai.hoku_input_is_hovered(raw, canvas.to_hoku_rect)
38
+ pos = mouse.pos
39
+ pos.x >= canvas.x && pos.x <= canvas.x + canvas.width && pos.y >= canvas.y && pos.y <= canvas.y + canvas.height
264
40
  end
265
41
  end
266
42
  end
@@ -1,7 +1,7 @@
1
1
  module Hokusai::Util
2
2
  class Selection
3
3
  attr_reader :raw
4
- attr_accessor :started
4
+ attr_accessor :started, :cleared
5
5
 
6
6
  def initialize
7
7
  ptr = FFI::MemoryPointer.new :pointer
@@ -9,6 +9,9 @@ module Hokusai::Util
9
9
  @raw = LibHokusai::HokuSelection.new(ptr.get_pointer(0))
10
10
  ptr.free
11
11
  @started = false
12
+ @direction = nil
13
+ @changed_direction = false
14
+ @cleared = false
12
15
  end
13
16
 
14
17
  def type
@@ -30,9 +33,14 @@ module Hokusai::Util
30
33
  raw[:stop_y] = 0.0
31
34
  raw[:cursor] = nil
32
35
 
36
+ self.cleared = true
33
37
  activate!
34
38
  end
35
39
 
40
+ def changed_direction?
41
+ @changed_direction
42
+ end
43
+
36
44
  def active?
37
45
  type == :active
38
46
  end
@@ -73,8 +81,17 @@ module Hokusai::Util
73
81
  end
74
82
 
75
83
  def stop(x, y)
84
+ self.cleared = false
76
85
  raw[:stop_x] = x
77
86
  raw[:stop_y] = y
87
+
88
+ if up? && @direction == :down || down? && @direction == :up
89
+ @changed_direction = true
90
+ else
91
+ @changed_direction = false
92
+ end
93
+
94
+ @direction = up? ? :up : :down
78
95
  end
79
96
 
80
97
  def start_x
@@ -93,12 +110,12 @@ module Hokusai::Util
93
110
  raw[:start_y]
94
111
  end
95
112
 
96
- def up?
97
- stop_y < start_y
113
+ def up?(height = 0)
114
+ stop_y < start_y - height
98
115
  end
99
116
 
100
- def down?
101
- start_y <= stop_y
117
+ def down?(height = 0)
118
+ start_y <= stop_y - height
102
119
  end
103
120
 
104
121
  def left?
@@ -126,7 +143,7 @@ module Hokusai::Util
126
143
  return nil if raw[:cursor].null?
127
144
 
128
145
  if frozen?
129
- return [raw[:cursor][:x], raw[:cursor][:y] - offset_y, raw[:cursor][:w], raw[:cursor][:h]]
146
+ return [raw[:cursor][:x], raw[:cursor][:y] + offset_y, raw[:cursor][:w], raw[:cursor][:h]]
130
147
  end
131
148
 
132
149
  [raw[:cursor][:x], raw[:cursor][:y], raw[:cursor][:w], raw[:cursor][:h]]
@@ -139,7 +156,11 @@ module Hokusai::Util
139
156
  def selected(x, y, width, height)
140
157
  return false if none?
141
158
 
142
- LibHokusai.hoku_selection_selected(raw, x, y, width, height)
159
+ if frozen?
160
+ return LibHokusai.hoku_selection_selected(raw, x, y + offset_y, width, height)
161
+ else
162
+ LibHokusai.hoku_selection_selected(raw, x, y, width, height)
163
+ end
143
164
  end
144
165
  end
145
166
  end
@@ -0,0 +1,268 @@
1
+ class Wrapped
2
+ attr_accessor :text, :x, :y, :extra
3
+
4
+ def initialize(text, x, y, extra)
5
+ @text = text
6
+ @x = x
7
+ @y = y
8
+ @extra = extra
9
+ end
10
+ end
11
+
12
+ class SelectionWrapper
13
+ attr_accessor :x, :y, :width, :height, :offset, :buffer
14
+
15
+ def initialize(x, y, w, h)
16
+ @x = x
17
+ @y = y
18
+ @width = w
19
+ @height = h
20
+ @offset = 0.0
21
+ @buffer = ""
22
+ end
23
+
24
+ def <<(w)
25
+ @width += w
26
+ end
27
+ end
28
+
29
+ class WrapStream
30
+ attr_accessor :width, :current_width, :x, :y, :offset_y,
31
+ :origin_x, :origin_y, :buffer, :stack, :widths, :current_height,
32
+ :current_offset, :srange, :last, :first
33
+ attr_reader :on_text_cb, :on_text_selection_cb, :on_advancex_cb, :selector, :padding
34
+
35
+ def initialize(width, &measure)
36
+ @width = width
37
+ @current_width = 0.0
38
+
39
+ @origin_x = 0.0
40
+ @origin_y = 0.0
41
+ @x = @origin_x
42
+ @y = @origin_y
43
+
44
+ @current_offset = 0
45
+ @current_height = 0.0
46
+ @widths = []
47
+ @stack = []
48
+ @srange = nil
49
+ @selected = ""
50
+ @buffer = ""
51
+ @last = nil
52
+ @first = nil
53
+
54
+ @measure_cb = measure
55
+ end
56
+
57
+ def reset(width)
58
+ @width = width
59
+ @current_width = 0.0
60
+
61
+ @x = @origin_x
62
+ @y = @origin_y
63
+
64
+ @current_offset = 0
65
+ @current_height = 0.0
66
+ @widths = []
67
+ @stack = []
68
+ @selected = ""
69
+ @buffer = ""
70
+
71
+ @first = nil
72
+
73
+ if @selector&.cleared
74
+ @selector&.cursor = nil
75
+
76
+ @srange = nil
77
+ @last = nil
78
+ end
79
+ end
80
+
81
+ def on_text_selection(selector, padding, &block)
82
+ @selector = selector
83
+ @padding = padding || Hokusai::Padding.new(0.0, 0.0, 0.0, 0.0)
84
+ @on_text_selection_cb = block
85
+ end
86
+
87
+ def on_bounds(&block)
88
+ @on_bounds_cb = block
89
+ end
90
+
91
+ def on_text(&block)
92
+ @on_text_cb = block
93
+ end
94
+
95
+ def measure(string, extra)
96
+ @measure_cb.call(string, extra)
97
+ end
98
+
99
+ def flush
100
+ sx = x
101
+ wrapper = nil
102
+ ii = 0
103
+ in_bounds = @on_bounds_cb.nil? ? true : @on_bounds_cb.call(y - offset_y)
104
+
105
+ stack.each do |(range, extra)|
106
+ size = buffer[range].size
107
+ if selector && in_bounds
108
+ i = 0
109
+ while i < size
110
+ nw = widths[i]
111
+ nh = current_height
112
+
113
+ # A char is selected
114
+ if selector.active? && selector.selected(sx, y, nw, nh)
115
+ # srange is for selecting downward.
116
+ # We cache the first char (our pivot point) and as we continue processing
117
+ # we can just append the next offset to the range
118
+ # self.srange ||= ((current_offset + i)..(current_offset + i))
119
+
120
+ # Selecting upward is more tricky.
121
+ # We need to cache the current offset as the first and last.
122
+ # After we process the whole text, we can nil the first offset
123
+ # And the next iteration the selection will pick up the new first offset
124
+ # The last offset will remain the origin (pivot)
125
+ # self.last ||= current_offset + i
126
+ self.first ||= current_offset + i
127
+ self.last = current_offset + i
128
+ self.srange = (first..last)
129
+ elsif selector.frozen? && selector.up? && selector.selected(sx, y, nw, nh)
130
+ self.first ||= current_offset + i
131
+ end
132
+
133
+ if srange&.include?(current_offset + i)
134
+ wrapper ||= begin
135
+ w = SelectionWrapper.new(sx, y, 0.0, nh)
136
+ w
137
+ end
138
+
139
+ if selector.up?(nh) && first == current_offset + i
140
+ selector.cursor ||= [sx, y - offset_y, 1.0, nh]
141
+ elsif selector.down? && last == current_offset + i
142
+ selector.cursor = [sx + nw, y - offset_y, 1.0, nh]
143
+ end
144
+
145
+ wrapper << nw
146
+ wrapper.buffer << (buffer[ii + i] || "")
147
+ end
148
+
149
+ i = i.succ
150
+ sx += nw
151
+ end
152
+
153
+ on_text_selection_cb.call(wrapper) if wrapper
154
+ wrapper = nil
155
+ end
156
+
157
+ ii += size
158
+ self.current_offset += size
159
+
160
+ nw = widths[range].sum
161
+ # hard break on the buffer, split on character
162
+ wrap_and_call(buffer[range], extra)
163
+ self.x += nw
164
+ end
165
+
166
+ self.current_width = 0.0
167
+ self.buffer = ""
168
+ stack.clear
169
+ widths.clear
170
+ self.x = origin_x
171
+ end
172
+
173
+ NEW_LINE_REGEX = /\n|\r\n/
174
+
175
+ def wrap(text, extra)
176
+ offset = 0
177
+ size = text.size
178
+
179
+ stack << [((buffer.size)..(text.size + buffer.size - 1)), extra]
180
+
181
+ while offset < size
182
+ w, h = measure(text[offset], extra)
183
+ self.current_height = h
184
+
185
+ # if it's a newline we want to break
186
+ if text[offset] =~ NEW_LINE_REGEX
187
+ self.widths << 0
188
+ self.buffer << text[offset]
189
+ flush
190
+
191
+ stack << [(0...(text.size - offset - 1)), extra]
192
+ self.y += h
193
+ self.x = origin_x
194
+ offset += 1
195
+
196
+ next
197
+ end
198
+
199
+ # if adding this char extends beyond the boundary
200
+ if current_width + w > width
201
+ # find the last space
202
+ idx = buffer.rindex(" ")
203
+
204
+ # if there is a break in this line split the buffer
205
+ # and render the current line
206
+ unless idx.nil? || idx < (buffer.size / 2)
207
+ cur = []
208
+ nex = []
209
+
210
+ found = false
211
+
212
+ # We need to split up both the buffer, and the ranges
213
+ while payload = stack.shift
214
+ range, xtra = payload
215
+ if range.include?(idx)
216
+ # putting the space on this line
217
+ cur << [(range.begin..idx), xtra]
218
+ nex << [(0..(range.end - idx - 1)), xtra] unless idx == range.end
219
+
220
+ found = true
221
+ elsif !found
222
+ cur << payload
223
+ else
224
+ nex << [((range.begin - idx - 1)..(range.end - idx - 1)), xtra]
225
+ end
226
+ end
227
+
228
+ scur = buffer[0..idx]
229
+ snex = buffer[(idx + 1)..-1]
230
+
231
+ wcur = widths[0..idx]
232
+ wnex = widths[(idx + 1)..-1]
233
+
234
+ self.buffer = scur
235
+ self.widths = wcur
236
+ self.stack = cur
237
+ flush
238
+
239
+ self.buffer = snex + text[offset]
240
+ self.widths = wnex.concat([w])
241
+ self.stack = nex
242
+ self.y += h
243
+ self.current_width = widths.sum
244
+ self.x = origin_x
245
+ else
246
+ # break on this word
247
+ flush
248
+
249
+ self.y += h
250
+ self.current_width = w
251
+ self.buffer = text[offset]
252
+ self.widths = [w]
253
+ stack << [(0...(text.size - offset)), extra]
254
+ end
255
+ else
256
+ self.current_width += w
257
+ widths << w
258
+ buffer << text[offset]
259
+ end
260
+
261
+ offset += 1
262
+ end
263
+ end
264
+
265
+ private def wrap_and_call(text, extra)
266
+ on_text_cb.call Wrapped.new(text, x, y, extra)
267
+ end
268
+ end