cyberarm_engine 0.24.5 → 0.25.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 (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 +25 -24
  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 -180
  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 -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 +111 -98
  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/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 -126
  63. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  64. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  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 +407 -404
  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 +222 -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 +326 -321
  86. data/lib/cyberarm_engine/ui/style.rb +61 -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 +76 -70
  92. data/mrbgem.rake +29 -29
  93. metadata +3 -3
@@ -1,404 +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
- @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
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