cyberarm_engine 0.25.0 → 0.30.0

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -8
  3. data/.rubocop.yml +7 -7
  4. data/.travis.yml +5 -5
  5. data/Gemfile +6 -6
  6. data/Gemfile.lock +37 -25
  7. data/LICENSE.txt +21 -21
  8. data/README.md +74 -74
  9. data/Rakefile +10 -10
  10. data/assets/shaders/fragment/g_buffer.glsl +30 -30
  11. data/assets/shaders/fragment/lighting.glsl +115 -115
  12. data/assets/shaders/include/light_struct.glsl +11 -11
  13. data/assets/shaders/include/material_struct.glsl +16 -16
  14. data/assets/shaders/vertex/g_buffer.glsl +28 -28
  15. data/assets/shaders/vertex/lighting.glsl +24 -24
  16. data/assets/textures/ui/circle.png +0 -0
  17. data/assets/textures/ui/circle_small.png +0 -0
  18. data/assets/textures/ui/rounded.png +0 -0
  19. data/assets/textures/ui/rounded_bottom.png +0 -0
  20. data/assets/textures/ui/rounded_left.png +0 -0
  21. data/assets/textures/ui/rounded_left_bottom.png +0 -0
  22. data/assets/textures/ui/rounded_left_top.png +0 -0
  23. data/assets/textures/ui/rounded_right.png +0 -0
  24. data/assets/textures/ui/rounded_right_bottom.png +0 -0
  25. data/assets/textures/ui/rounded_right_top.png +0 -0
  26. data/assets/textures/ui/rounded_small.png +0 -0
  27. data/assets/textures/ui/rounded_tiny.png +0 -0
  28. data/assets/textures/ui/rounded_top.png +0 -0
  29. data/assets/textures/ui/square.png +0 -0
  30. data/bin/console +14 -14
  31. data/bin/setup +8 -8
  32. data/cyberarm_engine.gemspec +35 -36
  33. data/lib/cyberarm_engine/animator.rb +219 -219
  34. data/lib/cyberarm_engine/background.rb +156 -158
  35. data/lib/cyberarm_engine/background_image.rb +93 -93
  36. data/lib/cyberarm_engine/background_nine_slice.rb +182 -142
  37. data/lib/cyberarm_engine/bounding_box.rb +150 -150
  38. data/lib/cyberarm_engine/builtin/intro_state.rb +130 -130
  39. data/lib/cyberarm_engine/cache/download_manager.rb +123 -123
  40. data/lib/cyberarm_engine/cache.rb +4 -4
  41. data/lib/cyberarm_engine/common.rb +133 -131
  42. data/lib/cyberarm_engine/config_file.rb +46 -46
  43. data/lib/cyberarm_engine/console/command.rb +157 -157
  44. data/lib/cyberarm_engine/console/commands/help_command.rb +43 -43
  45. data/lib/cyberarm_engine/console/subcommand.rb +99 -99
  46. data/lib/cyberarm_engine/console.rb +248 -248
  47. data/lib/cyberarm_engine/event_bus.rb +39 -0
  48. data/lib/cyberarm_engine/game_object.rb +244 -244
  49. data/lib/cyberarm_engine/game_state.rb +134 -124
  50. data/lib/cyberarm_engine/gosu_ext/draw_arc.rb +111 -111
  51. data/lib/cyberarm_engine/gosu_ext/draw_circle.rb +30 -30
  52. data/lib/cyberarm_engine/gosu_ext/draw_path.rb +17 -17
  53. data/lib/cyberarm_engine/model/material.rb +21 -21
  54. data/lib/cyberarm_engine/model/mesh.rb +131 -131
  55. data/lib/cyberarm_engine/model/parser.rb +74 -74
  56. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -138
  57. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -154
  58. data/lib/cyberarm_engine/model.rb +216 -216
  59. data/lib/cyberarm_engine/model_cache.rb +31 -31
  60. data/lib/cyberarm_engine/notification.rb +82 -82
  61. data/lib/cyberarm_engine/notification_manager.rb +241 -241
  62. data/lib/cyberarm_engine/opengl/light.rb +52 -52
  63. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -46
  64. data/lib/cyberarm_engine/opengl/perspective_camera.rb +41 -41
  65. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -249
  66. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +167 -167
  67. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +307 -307
  68. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +33 -33
  69. data/lib/cyberarm_engine/opengl/shader.rb +408 -408
  70. data/lib/cyberarm_engine/opengl/texture.rb +69 -69
  71. data/lib/cyberarm_engine/opengl.rb +53 -53
  72. data/lib/cyberarm_engine/pathfinding/graph.rb +64 -0
  73. data/lib/cyberarm_engine/pathfinding/graph_search.rb +6 -0
  74. data/lib/cyberarm_engine/pathfinding/graph_searches/astar_search.rb +6 -0
  75. data/lib/cyberarm_engine/pathfinding/graph_searches/breadth_first_search.rb +27 -0
  76. data/lib/cyberarm_engine/pathfinding/graph_searches/dijkstra_search.rb +6 -0
  77. data/lib/cyberarm_engine/pathfinding/graphs/grid.rb +52 -0
  78. data/lib/cyberarm_engine/ray.rb +56 -56
  79. data/lib/cyberarm_engine/result.rb +19 -0
  80. data/lib/cyberarm_engine/stats.rb +200 -200
  81. data/lib/cyberarm_engine/text.rb +288 -260
  82. data/lib/cyberarm_engine/timer.rb +27 -23
  83. data/lib/cyberarm_engine/transform.rb +296 -296
  84. data/lib/cyberarm_engine/trees/aabb_node.rb +126 -126
  85. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  86. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  87. data/lib/cyberarm_engine/ui/border_canvas.rb +102 -102
  88. data/lib/cyberarm_engine/ui/dialog.rb +24 -0
  89. data/lib/cyberarm_engine/ui/dsl.rb +170 -142
  90. data/lib/cyberarm_engine/ui/element.rb +664 -662
  91. data/lib/cyberarm_engine/ui/elements/button.rb +99 -100
  92. data/lib/cyberarm_engine/ui/elements/check_box.rb +58 -54
  93. data/lib/cyberarm_engine/ui/elements/container.rb +413 -407
  94. data/lib/cyberarm_engine/ui/elements/edit_box.rb +200 -179
  95. data/lib/cyberarm_engine/ui/elements/edit_line.rb +299 -297
  96. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  97. data/lib/cyberarm_engine/ui/elements/image.rb +78 -72
  98. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  99. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  100. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  101. data/lib/cyberarm_engine/ui/elements/progress.rb +95 -93
  102. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  103. data/lib/cyberarm_engine/ui/elements/slider.rb +111 -107
  104. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  105. data/lib/cyberarm_engine/ui/elements/text_block.rb +236 -222
  106. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +71 -67
  107. data/lib/cyberarm_engine/ui/elements/widget.rb +45 -0
  108. data/lib/cyberarm_engine/ui/event.rb +54 -54
  109. data/lib/cyberarm_engine/ui/gui_state.rb +402 -326
  110. data/lib/cyberarm_engine/ui/page.rb +39 -0
  111. data/lib/cyberarm_engine/ui/style.rb +94 -61
  112. data/lib/cyberarm_engine/ui/theme.rb +237 -225
  113. data/lib/cyberarm_engine/vector.rb +312 -312
  114. data/lib/cyberarm_engine/version.rb +4 -4
  115. data/lib/cyberarm_engine/window.rb +234 -195
  116. data/lib/cyberarm_engine.rb +88 -76
  117. data/mrbgem.rake +29 -29
  118. metadata +30 -5
@@ -1,407 +1,413 @@
1
- module CyberarmEngine
2
- class Element
3
- class Container < Element
4
- include Common
5
-
6
- attr_accessor :stroke_color, :fill_color
7
- attr_reader :children, :gui_state, :scroll_position, :scroll_target_position
8
-
9
- def self.current_container
10
- @current_container
11
- end
12
-
13
- def self.current_container=(container)
14
- raise ArgumentError, "Expected container to an an instance of CyberarmEngine::Element::Container, got #{container.class}" unless container.is_a?(CyberarmEngine::Element::Container)
15
-
16
- @current_container = container
17
- end
18
-
19
- def initialize(options = {}, block = nil)
20
- @gui_state = options.delete(:gui_state)
21
- super
22
-
23
- @last_scroll_position = Vector.new(0, 0)
24
- @scroll_position = Vector.new(0, 0)
25
- @scroll_target_position = Vector.new(0, 0)
26
- @scroll_chunk = 120
27
- @scroll_speed = 40
28
-
29
- if @gui_state
30
- @width = window.width
31
- @height = window.height
32
- end
33
-
34
- @text_color = options[:color]
35
-
36
- @children = []
37
-
38
- event(:window_size_changed)
39
- end
40
-
41
- def build
42
- @block&.call(self)
43
-
44
- root.gui_state.request_recalculate_for(self)
45
- end
46
-
47
- def add(element)
48
- @children << element
49
-
50
- root.gui_state.request_recalculate_for(self)
51
- end
52
-
53
- def remove(element)
54
- root.gui_state.request_recalculate_for(self) if @children.delete(element)
55
- end
56
-
57
- def append(&block)
58
- old_container = CyberarmEngine::Element::Container.current_container
59
-
60
- CyberarmEngine::Element::Container.current_container = self
61
- block&.call(self)
62
-
63
- CyberarmEngine::Element::Container.current_container = old_container
64
-
65
- root.gui_state.request_recalculate_for(self)
66
- end
67
-
68
- def clear(&block)
69
- @children.clear
70
-
71
- old_container = CyberarmEngine::Element::Container.current_container
72
-
73
- CyberarmEngine::Element::Container.current_container = self
74
- block&.call(self)
75
-
76
- CyberarmEngine::Element::Container.current_container = old_container
77
-
78
- root.gui_state.request_recalculate_for(self)
79
- end
80
-
81
- def render
82
- Gosu.clip_to(
83
- @x + @style.border_thickness_left + @style.padding_left,
84
- @y + @style.border_thickness_top + @style.padding_top,
85
- content_width + 1,
86
- content_height + 1
87
- ) do
88
- Gosu.translate(@scroll_position.x, @scroll_position.y) do
89
- @children.each(&:draw)
90
- end
91
- end
92
- end
93
-
94
- def debug_draw
95
- super
96
-
97
- @children.each(&:debug_draw)
98
- end
99
-
100
- def update
101
- update_scroll if @style.scroll
102
- @children.each(&:update)
103
- end
104
-
105
- def hit_element?(x, y)
106
- return unless hit?(x, y)
107
-
108
- # Offset child hit point by scroll position/offset
109
- child_x = x - @scroll_position.x
110
- child_y = y - @scroll_position.y
111
-
112
- @children.reverse_each do |child|
113
- next unless child.visible?
114
-
115
- case child
116
- when Container
117
- if (element = child.hit_element?(child_x, child_y))
118
- return element
119
- end
120
- else
121
- return child if child.hit?(child_x, child_y)
122
- end
123
- end
124
-
125
- self if hit?(x, y)
126
- end
127
-
128
- def update_child_element_visibity(child)
129
- child.element_visible = child.x >= (@x - @scroll_position.x) - child.width && child.x <= (@x - @scroll_position.x) + width &&
130
- child.y >= (@y - @scroll_position.y) - child.height && child.y <= (@y - @scroll_position.y) + height
131
- end
132
-
133
- def update_scroll
134
- dt = window.dt.clamp(0.000001, 0.025)
135
- @scroll_position.x += (((@scroll_target_position.x - @scroll_position.x) * (@scroll_speed / 4.0) * 0.98) * dt).round
136
- @scroll_position.y += (((@scroll_target_position.y - @scroll_position.y) * (@scroll_speed / 4.0) * 0.98) * dt).round
137
-
138
- # Scrolled PAST top
139
- if @scroll_position.y > 0
140
- @scroll_target_position.y = 0
141
-
142
- # Scrolled PAST bottom
143
- elsif @scroll_position.y < -max_scroll_height
144
- @scroll_target_position.y = -max_scroll_height
145
- end
146
-
147
- if @last_scroll_position != @scroll_position
148
- @children.each { |child| update_child_element_visibity(child) }
149
- root.gui_state.request_repaint
150
- end
151
-
152
- @last_scroll_position.x = @scroll_position.x
153
- @last_scroll_position.y = @scroll_position.y
154
- end
155
-
156
- def recalculate
157
- @current_position = Vector.new(@style.margin_left + @style.padding_left, @style.margin_top + @style.padding_top)
158
-
159
- return unless visible?
160
-
161
- Stats.frame&.increment(:gui_recalculations)
162
-
163
- # s = Gosu.milliseconds
164
-
165
- stylize
166
- layout
167
-
168
- # Old sizes MUST be determined AFTER call to layout
169
- old_width = width
170
- old_height = height
171
-
172
- @cached_scroll_width = nil
173
- @cached_scroll_height = nil
174
-
175
- if is_root?
176
- @width = @style.width = window.width
177
- @height = @style.height = window.height
178
- else
179
- @width = 0
180
- @height = 0
181
-
182
- _width = dimensional_size(@style.width, :width)
183
- _height = dimensional_size(@style.height, :height)
184
-
185
- @width = _width || (@children.map { |c| c.x + c.outer_width }.max || 0).floor
186
- @height = _height || (@children.map { |c| c.y + c.outer_height }.max || 0).floor
187
- end
188
-
189
- # FIXME: Correctly handle alignment when element has siblings
190
- # FIXME: Enable alignment for any element, not just containers
191
- if @style.v_align
192
- space = space_available_height
193
-
194
- case @style.v_align
195
- when :center
196
- @y = parent.height / 2 - height / 2
197
- when :bottom
198
- @y = parent.height - height
199
- end
200
- end
201
-
202
- if @style.h_align
203
- space = space_available_width
204
-
205
- case @style.h_align
206
- when :center
207
- @x = parent.width / 2 - width / 2
208
- when :right
209
- @x = parent.width - width
210
- end
211
- end
212
-
213
- # t = Gosu.milliseconds
214
- # Move children to parent after positioning
215
- @children.each do |child|
216
- child.x += (@x + @style.border_thickness_left) - style.margin_left
217
- child.y += (@y + @style.border_thickness_top) - style.margin_top
218
-
219
- child.stylize
220
- child.recalculate
221
- child.reposition # TODO: Implement top,bottom,left,center, and right positioning
222
-
223
- Stats.frame&.increment(:gui_recalculations)
224
-
225
- update_child_element_visibity(child)
226
- end
227
- # puts "TOOK: #{Gosu.milliseconds - t}ms to recalculate #{self.class}:0x#{object_id.to_s(16)}'s #{@children.count} children" if is_root?
228
-
229
- update_background
230
-
231
- # Fixes resized container scrolled past bottom
232
- if old_height != @height
233
- self.scroll_top = -@scroll_position.y
234
- @scroll_target_position.y = @scroll_position.y
235
- end
236
-
237
- # Fixes resized container that is scrolled down from being stuck overscrolled when resized
238
- if scroll_height < height
239
- @scroll_target_position.y = 0
240
- end
241
-
242
- recalculate_if_size_changed
243
-
244
- # puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}"
245
- end
246
-
247
- def layout
248
- raise "Not overridden"
249
- end
250
-
251
- def max_width
252
- # _width = dimensional_size(@style.width, :width)
253
- # if _width
254
- # outer_width
255
- # else
256
- # window.width - (@parent ? @parent.style.margin_right + @style.margin_right : @style.margin_right)
257
- # end
258
-
259
- outer_width
260
- end
261
-
262
- def fits_on_line?(element) # Flow
263
- @current_position.x + element.outer_width <= max_width &&
264
- @current_position.x + element.outer_width <= window.width
265
- end
266
-
267
- def position_on_current_line(element) # Flow
268
- element.x = element.style.margin_left + @current_position.x
269
- element.y = element.style.margin_top + @current_position.y
270
-
271
- @current_position.x += element.outer_width
272
- end
273
-
274
- def tallest_neighbor(querier, _y_position) # Flow
275
- response = querier
276
- @children.each do |child|
277
- response = child if child.outer_height > response.outer_height
278
- break if child == querier
279
- end
280
-
281
- response
282
- end
283
-
284
- def position_on_next_line(element) # Flow
285
- @current_position.x = @style.margin_left + @style.padding_left
286
- @current_position.y += tallest_neighbor(element, @current_position.y).outer_height
287
-
288
- element.x = element.style.margin_left + @current_position.x
289
- element.y = element.style.margin_top + @current_position.y
290
-
291
- @current_position.x += element.outer_width
292
- end
293
-
294
- def move_to_next_line(element) # Stack
295
- element.x = element.style.margin_left + @current_position.x
296
- element.y = element.style.margin_top + @current_position.y
297
-
298
- @current_position.y += element.outer_height
299
- end
300
-
301
- def mouse_wheel_up(sender, x, y)
302
- return unless @style.scroll
303
-
304
- # Allow overscrolling UP, only if one can scroll DOWN
305
- return unless height < scroll_height
306
-
307
- if @scroll_target_position.y.positive?
308
- @scroll_target_position.y = @scroll_chunk
309
- else
310
- @scroll_target_position.y += @scroll_chunk
311
- end
312
-
313
- :handled
314
- end
315
-
316
- def mouse_wheel_down(sender, x, y)
317
- return unless @style.scroll
318
-
319
- return unless height < scroll_height
320
-
321
- if @scroll_target_position.y.positive?
322
- @scroll_target_position.y = -@scroll_chunk
323
- else
324
- @scroll_target_position.y -= @scroll_chunk
325
- end
326
-
327
- :handled
328
- end
329
-
330
- def scroll_jump_to_top(sender, x, y)
331
- return unless @style.scroll
332
-
333
- @scroll_position.y = 0
334
- @scroll_target_position.y = 0
335
-
336
- :handled
337
- end
338
-
339
- def scroll_jump_to_end(sender, x, y)
340
- return unless @style.scroll
341
-
342
- @scroll_position.y = -max_scroll_height
343
- @scroll_target_position.y = -max_scroll_height
344
-
345
- :handled
346
- end
347
-
348
- def scroll_page_up(sender, x, y)
349
- return unless @style.scroll
350
-
351
- @scroll_position.y += height
352
- @scroll_position.y = 0 if @scroll_position.y > 0
353
- @scroll_target_position.y = @scroll_position.y
354
-
355
- :handled
356
- end
357
-
358
- def scroll_page_down(sender, x, y)
359
- return unless @style.scroll
360
-
361
- @scroll_position.y -= height
362
- @scroll_position.y = -max_scroll_height if @scroll_position.y < -max_scroll_height
363
- @scroll_target_position.y = @scroll_position.y
364
-
365
- :handled
366
- end
367
-
368
- def scroll_top
369
- @scroll_position.y
370
- end
371
-
372
- def scroll_top=(n)
373
- n = 0 if n <= 0
374
- @scroll_position.y = -n
375
-
376
- if max_scroll_height.positive?
377
- @scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
378
- else
379
- @scroll_position.y = 0
380
- end
381
- end
382
-
383
- def value
384
- @children.map(&:class).join(", ")
385
- end
386
-
387
- def to_s
388
- "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} children=#{@children.size}"
389
- end
390
-
391
- def write_tree(indent = "", _index = 0)
392
- puts self
393
-
394
- indent += " "
395
- @children.each_with_index do |child, i|
396
- print "#{indent}#{i}: "
397
-
398
- if child.is_a?(Container)
399
- child.write_tree(indent)
400
- else
401
- puts child
402
- end
403
- end
404
- end
405
- end
406
- end
407
- end
1
+ module CyberarmEngine
2
+ class Element
3
+ class Container < Element
4
+ include Common
5
+
6
+ attr_accessor :stroke_color, :fill_color
7
+ attr_reader :children, :gui_state, :scroll_position, :scroll_target_position
8
+
9
+ def self.current_container
10
+ @current_container
11
+ end
12
+
13
+ def self.current_container=(container)
14
+ raise ArgumentError, "Expected container to an an instance of CyberarmEngine::Element::Container, got #{container.class}" unless container.is_a?(CyberarmEngine::Element::Container)
15
+
16
+ @current_container = container
17
+ end
18
+
19
+ def initialize(options = {}, block = nil)
20
+ @gui_state = options.delete(:gui_state)
21
+ super
22
+
23
+ @last_scroll_position = Vector.new(0, 0)
24
+ @scroll_position = Vector.new(0, 0)
25
+ @scroll_target_position = Vector.new(0, 0)
26
+ @scroll_chunk = 120
27
+ @scroll_speed = 40
28
+
29
+ if @gui_state
30
+ @width = window.width
31
+ @height = window.height
32
+ end
33
+
34
+ @text_color = options[:color]
35
+
36
+ @children = []
37
+
38
+ event(:window_size_changed)
39
+ end
40
+
41
+ def build
42
+ @block&.call(self)
43
+
44
+ root.gui_state.request_recalculate_for(self)
45
+ end
46
+
47
+ def add(element)
48
+ @children << element
49
+
50
+ root.gui_state.request_recalculate_for(self)
51
+ end
52
+
53
+ def remove(element)
54
+ root.gui_state.request_recalculate_for(self) if @children.delete(element)
55
+ end
56
+
57
+ def append(&block)
58
+ old_container = CyberarmEngine::Element::Container.current_container
59
+
60
+ CyberarmEngine::Element::Container.current_container = self
61
+ block&.call(self)
62
+
63
+ CyberarmEngine::Element::Container.current_container = old_container
64
+
65
+ root.gui_state.request_recalculate_for(self)
66
+ end
67
+
68
+ def clear(&block)
69
+ @children.clear
70
+
71
+ old_container = CyberarmEngine::Element::Container.current_container
72
+
73
+ CyberarmEngine::Element::Container.current_container = self
74
+ block&.call(self)
75
+
76
+ CyberarmEngine::Element::Container.current_container = old_container
77
+
78
+ root.gui_state.request_recalculate_for(self)
79
+ end
80
+
81
+ def render
82
+ Gosu.clip_to(
83
+ @x - 1 + styled(:margin_left) + styled(:border_thickness_left) + styled(:padding_left),
84
+ @y - 1 + styled(:margin_top) + styled(:border_thickness_top) + styled(:padding_top),
85
+ content_width + 1,
86
+ content_height + 1
87
+ ) do
88
+
89
+ Gosu.translate(@scroll_position.x, @scroll_position.y) do
90
+ @children.each(&:draw)
91
+
92
+ @children.each do |child|
93
+ next unless child.visible?
94
+
95
+ child.debug_draw
96
+ end
97
+ end
98
+ end
99
+
100
+ debug_draw
101
+ end
102
+
103
+ def debug_draw
104
+ super
105
+ end
106
+
107
+ def update
108
+ update_scroll if styled(:scroll)
109
+ @children.each(&:update)
110
+ end
111
+
112
+ # return nil if element was not hit, or array of hit elements if hit, includes self.
113
+ def hit_element?(x, y, elements = [])
114
+ return unless hit?(x, y)
115
+
116
+ elements << self
117
+
118
+ # Offset child hit point by scroll position/offset
119
+ child_x = x - @scroll_position.x
120
+ child_y = y - @scroll_position.y
121
+
122
+ @children.reverse_each do |child|
123
+ next unless child.visible?
124
+
125
+ case child
126
+ when Container
127
+ if (child.hit_element?(child_x, child_y, elements))
128
+ return elements
129
+ end
130
+ else
131
+ if child.hit?(child_x, child_y)
132
+ elements << child
133
+ return elements
134
+ end
135
+ end
136
+ end
137
+
138
+ elements
139
+ end
140
+
141
+ def update_child_element_visibity(child)
142
+ child.element_visible = child.x >= ((styled(:margin_left) + @x) - @scroll_position.x) - child.width && child.x <= ((styled(:margin_left) + @x) - @scroll_position.x) + width &&
143
+ child.y >= ((styled(:margin_top) + @y) - @scroll_position.y) - child.height && child.y <= ((styled(:margin_top) + @y) - @scroll_position.y) + height
144
+ end
145
+
146
+ def update_scroll
147
+ dt = window.dt.clamp(0.000001, 0.025)
148
+ @scroll_position.x += (((@scroll_target_position.x - @scroll_position.x) * (@scroll_speed / 4.0) * 0.98) * dt).round
149
+ @scroll_position.y += (((@scroll_target_position.y - @scroll_position.y) * (@scroll_speed / 4.0) * 0.98) * dt).round
150
+
151
+ # Scrolled PAST top
152
+ if @scroll_position.y > 0
153
+ @scroll_target_position.y = 0
154
+
155
+ # Scrolled PAST bottom
156
+ elsif @scroll_position.y < -max_scroll_height
157
+ @scroll_target_position.y = -max_scroll_height
158
+ end
159
+
160
+ if @last_scroll_position != @scroll_position
161
+ @children.each { |child| update_child_element_visibity(child) }
162
+ root.gui_state.request_repaint
163
+ end
164
+
165
+ @last_scroll_position.x = @scroll_position.x
166
+ @last_scroll_position.y = @scroll_position.y
167
+ end
168
+
169
+ def recalculate
170
+ @current_position = Vector.new(styled(:margin_left) + styled(:padding_left), styled(:margin_top) + styled(:padding_top))
171
+
172
+ return unless visible?
173
+
174
+ Stats.frame&.increment(:gui_recalculations)
175
+
176
+ # s = Gosu.milliseconds
177
+
178
+ stylize
179
+ layout
180
+
181
+ # Old sizes MUST be determined AFTER call to layout
182
+ old_width = width
183
+ old_height = height
184
+
185
+ @cached_scroll_width = nil
186
+ @cached_scroll_height = nil
187
+
188
+ if is_root?
189
+ @width = @style.width = window.width
190
+ @height = @style.height = window.height
191
+ else
192
+ @width = 0
193
+ @height = 0
194
+
195
+ _width = dimensional_size(@style.width, :width)
196
+ _height = dimensional_size(@style.height, :height)
197
+
198
+ @width = _width || (@children.map { |c| c.x + c.outer_width }.max.to_f - (styled(:margin_left) + styled(:padding_left))).floor
199
+ @height = _height || (@children.map { |c| c.y + c.outer_height }.max.to_f - (styled(:margin_top) + styled(:padding_top))).floor
200
+ end
201
+
202
+ # FIXME: Correctly handle alignment when element has siblings
203
+ # FIXME: Enable alignment for any element, not just containers
204
+ if styled(:v_align)
205
+ space = space_available_height
206
+
207
+ case styled(:v_align)
208
+ when :center
209
+ @y = parent.y + parent.outer_height / 2 - outer_height / 2
210
+ when :bottom
211
+ @y = parent.y + parent.outer_height - outer_height
212
+ end
213
+ end
214
+
215
+ if styled(:h_align)
216
+ space = space_available_width
217
+
218
+ case styled(:h_align)
219
+ when :center
220
+ @x = parent.x + parent.outer_width / 2 - outer_width / 2
221
+ when :right
222
+ @x = parent.x + parent.outer_width - outer_width
223
+ end
224
+ end
225
+
226
+ # t = Gosu.milliseconds
227
+ # Move children to parent after positioning
228
+ @children.each do |child|
229
+ child.x += (@x + styled(:border_thickness_left))
230
+ child.y += (@y + styled(:border_thickness_top))
231
+
232
+ child.stylize
233
+ child.recalculate
234
+ child.reposition # TODO: Implement top,bottom,left,center, and right positioning
235
+
236
+ Stats.frame&.increment(:gui_recalculations)
237
+
238
+ update_child_element_visibity(child)
239
+ end
240
+ # puts "TOOK: #{Gosu.milliseconds - t}ms to recalculate #{self.class}:0x#{object_id.to_s(16)}'s #{@children.count} children" if is_root?
241
+
242
+ update_background
243
+
244
+ # Fixes resized container scrolled past bottom
245
+ if old_height != @height
246
+ self.scroll_top = -@scroll_position.y
247
+ @scroll_target_position.y = @scroll_position.y
248
+ end
249
+
250
+ # Fixes resized container that is scrolled down from being stuck overscrolled when resized
251
+ if scroll_height < height
252
+ @scroll_target_position.y = 0
253
+ end
254
+
255
+ recalculate_if_size_changed
256
+
257
+ # puts "TOOK: #{Gosu.milliseconds - s}ms to recalculate #{self.class}:0x#{self.object_id.to_s(16)}"
258
+ end
259
+
260
+ def layout
261
+ raise "Not overridden"
262
+ end
263
+
264
+ def max_width
265
+ outer_width
266
+ end
267
+
268
+ def fits_on_line?(element) # Flow
269
+ @current_position.x + element.outer_width <= max_width &&
270
+ @current_position.x + element.outer_width <= window.width
271
+ end
272
+
273
+ def position_on_current_line(element) # Flow
274
+ element.x = @current_position.x
275
+ element.y = @current_position.y
276
+
277
+ @current_position.x += element.outer_width
278
+ end
279
+
280
+ def tallest_neighbor(querier, _y_position) # Flow
281
+ response = querier
282
+ @children.each do |child|
283
+ response = child if child.outer_height > response.outer_height
284
+ break if child == querier
285
+ end
286
+
287
+ response
288
+ end
289
+
290
+ def position_on_next_line(element) # Flow
291
+ @current_position.x = styled(:margin_left) + styled(:padding_left)
292
+ @current_position.y += tallest_neighbor(element, @current_position.y).outer_height
293
+
294
+ element.x = @current_position.x
295
+ element.y = @current_position.y
296
+
297
+ @current_position.x += element.outer_width
298
+ end
299
+
300
+ def move_to_next_line(element) # Stack
301
+ element.x = @current_position.x
302
+ element.y = @current_position.y
303
+
304
+ @current_position.y += element.outer_height
305
+ end
306
+
307
+ def mouse_wheel_up(sender, x, y)
308
+ return unless styled(:scroll)
309
+
310
+ # Allow overscrolling UP, only if one can scroll DOWN
311
+ return unless height < scroll_height
312
+
313
+ if @scroll_target_position.y.positive?
314
+ @scroll_target_position.y = @scroll_chunk
315
+ else
316
+ @scroll_target_position.y += @scroll_chunk
317
+ end
318
+
319
+ :handled
320
+ end
321
+
322
+ def mouse_wheel_down(sender, x, y)
323
+ return unless styled(:scroll)
324
+
325
+ return unless height < scroll_height
326
+
327
+ if @scroll_target_position.y.positive?
328
+ @scroll_target_position.y = -@scroll_chunk
329
+ else
330
+ @scroll_target_position.y -= @scroll_chunk
331
+ end
332
+
333
+ :handled
334
+ end
335
+
336
+ def scroll_jump_to_top(sender, x, y)
337
+ return unless styled(:scroll)
338
+
339
+ @scroll_position.y = 0
340
+ @scroll_target_position.y = 0
341
+
342
+ :handled
343
+ end
344
+
345
+ def scroll_jump_to_end(sender, x, y)
346
+ return unless styled(:scroll)
347
+
348
+ @scroll_position.y = -max_scroll_height
349
+ @scroll_target_position.y = -max_scroll_height
350
+
351
+ :handled
352
+ end
353
+
354
+ def scroll_page_up(sender, x, y)
355
+ return unless styled(:scroll)
356
+
357
+ @scroll_position.y += height
358
+ @scroll_position.y = 0 if @scroll_position.y > 0
359
+ @scroll_target_position.y = @scroll_position.y
360
+
361
+ :handled
362
+ end
363
+
364
+ def scroll_page_down(sender, x, y)
365
+ return unless styled(:scroll)
366
+
367
+ @scroll_position.y -= height
368
+ @scroll_position.y = -max_scroll_height if @scroll_position.y < -max_scroll_height
369
+ @scroll_target_position.y = @scroll_position.y
370
+
371
+ :handled
372
+ end
373
+
374
+ def scroll_top
375
+ @scroll_position.y
376
+ end
377
+
378
+ def scroll_top=(n)
379
+ n = 0 if n <= 0
380
+ @scroll_position.y = -n
381
+
382
+ if max_scroll_height.positive?
383
+ @scroll_position.y = -max_scroll_height if @scroll_position.y.abs > max_scroll_height
384
+ else
385
+ @scroll_position.y = 0
386
+ end
387
+ end
388
+
389
+ def value
390
+ @children.map(&:class).join(", ")
391
+ end
392
+
393
+ def to_s
394
+ "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} children=#{@children.size}"
395
+ end
396
+
397
+ def write_tree(indent = "", _index = 0)
398
+ puts self
399
+
400
+ indent += " "
401
+ @children.each_with_index do |child, i|
402
+ print "#{indent}#{i}: "
403
+
404
+ if child.is_a?(Container)
405
+ child.write_tree(indent)
406
+ else
407
+ puts child
408
+ end
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end