cyberarm_engine 0.25.0 → 0.25.1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -8
  3. data/.rubocop.yml +7 -7
  4. data/.travis.yml +5 -5
  5. data/Gemfile +6 -6
  6. data/Gemfile.lock +25 -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/bin/console +14 -14
  17. data/bin/setup +8 -8
  18. data/cyberarm_engine.gemspec +36 -36
  19. data/lib/cyberarm_engine/animator.rb +219 -219
  20. data/lib/cyberarm_engine/background.rb +158 -158
  21. data/lib/cyberarm_engine/background_image.rb +93 -93
  22. data/lib/cyberarm_engine/background_nine_slice.rb +142 -142
  23. data/lib/cyberarm_engine/bounding_box.rb +150 -150
  24. data/lib/cyberarm_engine/builtin/intro_state.rb +130 -130
  25. data/lib/cyberarm_engine/cache/download_manager.rb +123 -123
  26. data/lib/cyberarm_engine/cache.rb +4 -4
  27. data/lib/cyberarm_engine/common.rb +131 -131
  28. data/lib/cyberarm_engine/config_file.rb +46 -46
  29. data/lib/cyberarm_engine/console/command.rb +157 -157
  30. data/lib/cyberarm_engine/console/commands/help_command.rb +43 -43
  31. data/lib/cyberarm_engine/console/subcommand.rb +99 -99
  32. data/lib/cyberarm_engine/console.rb +248 -248
  33. data/lib/cyberarm_engine/game_object.rb +244 -244
  34. data/lib/cyberarm_engine/game_state.rb +124 -124
  35. data/lib/cyberarm_engine/gosu_ext/draw_arc.rb +111 -111
  36. data/lib/cyberarm_engine/gosu_ext/draw_circle.rb +30 -30
  37. data/lib/cyberarm_engine/gosu_ext/draw_path.rb +17 -17
  38. data/lib/cyberarm_engine/model/material.rb +21 -21
  39. data/lib/cyberarm_engine/model/mesh.rb +131 -131
  40. data/lib/cyberarm_engine/model/parser.rb +74 -74
  41. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -138
  42. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -154
  43. data/lib/cyberarm_engine/model.rb +216 -216
  44. data/lib/cyberarm_engine/model_cache.rb +31 -31
  45. data/lib/cyberarm_engine/notification.rb +82 -82
  46. data/lib/cyberarm_engine/notification_manager.rb +241 -241
  47. data/lib/cyberarm_engine/opengl/light.rb +52 -52
  48. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -46
  49. data/lib/cyberarm_engine/opengl/perspective_camera.rb +41 -41
  50. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -249
  51. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +167 -167
  52. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +307 -307
  53. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +33 -33
  54. data/lib/cyberarm_engine/opengl/shader.rb +408 -408
  55. data/lib/cyberarm_engine/opengl/texture.rb +69 -69
  56. data/lib/cyberarm_engine/opengl.rb +53 -53
  57. data/lib/cyberarm_engine/ray.rb +56 -56
  58. data/lib/cyberarm_engine/result.rb +20 -0
  59. data/lib/cyberarm_engine/stats.rb +200 -200
  60. data/lib/cyberarm_engine/text.rb +260 -260
  61. data/lib/cyberarm_engine/timer.rb +23 -23
  62. data/lib/cyberarm_engine/transform.rb +296 -296
  63. data/lib/cyberarm_engine/trees/aabb_node.rb +126 -126
  64. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  65. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  66. data/lib/cyberarm_engine/ui/border_canvas.rb +102 -102
  67. data/lib/cyberarm_engine/ui/dsl.rb +142 -142
  68. data/lib/cyberarm_engine/ui/element.rb +662 -662
  69. data/lib/cyberarm_engine/ui/elements/button.rb +100 -100
  70. data/lib/cyberarm_engine/ui/elements/check_box.rb +54 -54
  71. data/lib/cyberarm_engine/ui/elements/container.rb +407 -407
  72. data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -179
  73. data/lib/cyberarm_engine/ui/elements/edit_line.rb +297 -297
  74. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  75. data/lib/cyberarm_engine/ui/elements/image.rb +72 -72
  76. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  77. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  78. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  79. data/lib/cyberarm_engine/ui/elements/progress.rb +93 -93
  80. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  81. data/lib/cyberarm_engine/ui/elements/slider.rb +107 -107
  82. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  83. data/lib/cyberarm_engine/ui/elements/text_block.rb +222 -222
  84. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +67 -67
  85. data/lib/cyberarm_engine/ui/event.rb +54 -54
  86. data/lib/cyberarm_engine/ui/gui_state.rb +326 -326
  87. data/lib/cyberarm_engine/ui/style.rb +61 -61
  88. data/lib/cyberarm_engine/ui/theme.rb +225 -225
  89. data/lib/cyberarm_engine/vector.rb +312 -312
  90. data/lib/cyberarm_engine/version.rb +4 -4
  91. data/lib/cyberarm_engine/window.rb +195 -195
  92. data/lib/cyberarm_engine.rb +77 -76
  93. data/mrbgem.rake +29 -29
  94. metadata +4 -3
@@ -1,407 +1,407 @@
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 + @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