cyberarm_engine 0.24.3 → 0.24.5

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