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,662 +1,664 @@
1
- module CyberarmEngine
2
- class Element
3
- include Theme
4
- include Event
5
- include Common
6
-
7
- attr_accessor :x, :y, :z, :tip, :element_visible
8
- attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
9
-
10
- def initialize(options = {}, block = nil)
11
- @parent = options.delete(:parent) # parent Container (i.e. flow/stack)
12
- options = theme_defaults(options)
13
- @options = options
14
- @block = block
15
-
16
- @focus = !@options.key?(:focus) ? false : @options[:focus]
17
- @enabled = !@options.key?(:enabled) ? true : @options[:enabled]
18
- @visible = !@options.key?(:visible) ? true : @options[:visible]
19
- @tip = @options[:tip] || ""
20
-
21
- @debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
22
-
23
- @style = Style.new(options)
24
-
25
- @root ||= nil
26
- @gui_state ||= nil
27
- @element_visible = true
28
-
29
- @x = @style.x
30
- @y = @style.y
31
- @z = @style.z
32
-
33
- @old_width = 0
34
- @old_height = 0
35
- @width = 0
36
- @height = 0
37
-
38
- @style.width = default(:width) || nil
39
- @style.height = default(:height) || nil
40
-
41
- @style.background_canvas = Background.new
42
- @style.background_nine_slice_canvas = BackgroundNineSlice.new
43
- @style.background_image_canvas = BackgroundImage.new
44
- @style.border_canvas = BorderCanvas.new(element: self)
45
-
46
- @style_event = :default
47
-
48
- stylize
49
-
50
- default_events
51
-
52
- root.gui_state.request_focus(self) if @options[:autofocus]
53
- end
54
-
55
- def stylize
56
- set_static_position
57
-
58
- set_color
59
- set_font
60
-
61
- set_padding
62
- set_margin
63
-
64
- set_background
65
- set_background_nine_slice
66
- set_background_image
67
-
68
- set_border_thickness
69
- set_border_color
70
-
71
- root.gui_state.request_repaint
72
- end
73
-
74
- def safe_style_fetch(key, fallback_key = nil)
75
- # Attempt to return value for requested key
76
- v = @style.hash.dig(@style_event, key)
77
- return v if v
78
-
79
- # Attempt to return overriding value
80
- if fallback_key
81
- v = @style.hash.dig(@style_event, fallback_key)
82
- return v if v
83
- end
84
-
85
- # Fallback to default style
86
- @style.hash.dig(:default, key) || default(key)
87
- end
88
-
89
- def set_static_position
90
- @x = @style.x if @style.x != 0
91
- @y = @style.y if @style.y != 0
92
- end
93
-
94
- def set_color
95
- @style.color = safe_style_fetch(:color)
96
- @text&.color = @style.color
97
- end
98
-
99
- def set_font
100
- @text&.swap_font(safe_style_fetch(:text_size), safe_style_fetch(:font))
101
- end
102
-
103
- def set_background
104
- @style.background = safe_style_fetch(:background)
105
-
106
- @style.background_canvas.background = @style.background
107
- end
108
-
109
- def set_background_nine_slice
110
- @style.background_nine_slice = safe_style_fetch(:background_nine_slice)
111
-
112
- @style.background_nine_slice_mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
113
- @style.background_nine_slice_color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
114
- @style.background_nine_slice_canvas.color = @style.background_nine_slice_color
115
-
116
- @style.background_nine_slice_from_edge = safe_style_fetch(:background_nine_slice_from_edge)
117
-
118
- @style.background_nine_slice_left = safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge)
119
- @style.background_nine_slice_top = safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge)
120
- @style.background_nine_slice_right = safe_style_fetch(:background_nine_slice_right, :background_nine_slice_from_edge)
121
- @style.background_nine_slice_bottom = safe_style_fetch(:background_nine_slice_bottom, :background_nine_slice_from_edge)
122
- end
123
-
124
- def set_background_image
125
- @style.background_image = safe_style_fetch(:background_image)
126
- @style.background_image_mode = safe_style_fetch(:background_image_mode) || :stretch
127
- @style.background_image_color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
128
- @style.background_image_canvas.mode = @style.background_image_mode
129
- @style.background_image_canvas.color = @style.background_image_color
130
- end
131
-
132
- def set_border_thickness
133
- @style.border_thickness = safe_style_fetch(:border_thickness)
134
-
135
- @style.border_thickness_left = safe_style_fetch(:border_thickness_left, :border_thickness)
136
- @style.border_thickness_right = safe_style_fetch(:border_thickness_right, :border_thickness)
137
- @style.border_thickness_top = safe_style_fetch(:border_thickness_top, :border_thickness)
138
- @style.border_thickness_bottom = safe_style_fetch(:border_thickness_bottom, :border_thickness)
139
- end
140
-
141
- def set_border_color
142
- @style.border_color = safe_style_fetch(:border_color)
143
-
144
- @style.border_color_left = safe_style_fetch(:border_color_left, :border_color)
145
- @style.border_color_right = safe_style_fetch(:border_color_right, :border_color)
146
- @style.border_color_top = safe_style_fetch(:border_color_top, :border_color)
147
- @style.border_color_bottom = safe_style_fetch(:border_color_bottom, :border_color)
148
-
149
- @style.border_canvas.color = [
150
- @style.border_color_top,
151
- @style.border_color_right,
152
- @style.border_color_bottom,
153
- @style.border_color_left
154
- ]
155
- end
156
-
157
- def set_padding
158
- @style.padding = safe_style_fetch(:padding)
159
-
160
- @style.padding_left = safe_style_fetch(:padding_left, :padding)
161
- @style.padding_right = safe_style_fetch(:padding_right, :padding)
162
- @style.padding_top = safe_style_fetch(:padding_top, :padding)
163
- @style.padding_bottom = safe_style_fetch(:padding_bottom, :padding)
164
- end
165
-
166
- def set_margin
167
- @style.margin = safe_style_fetch(:margin)
168
-
169
- @style.margin_left = safe_style_fetch(:margin_left, :margin)
170
- @style.margin_right = safe_style_fetch(:margin_right, :margin)
171
- @style.margin_top = safe_style_fetch(:margin_top, :margin)
172
- @style.margin_bottom = safe_style_fetch(:margin_bottom, :margin)
173
- end
174
-
175
- def update_styles(event = :default)
176
- old_width = width
177
- old_height = height
178
-
179
- @style_event = event
180
-
181
- return if self.is_a?(ToolTip)
182
-
183
- root.gui_state.request_recalculate if old_width != width || old_height != height
184
-
185
- stylize
186
- end
187
-
188
- def default_events
189
- %i[left middle right].each do |button|
190
- event(:"#{button}_mouse_button")
191
- event(:"released_#{button}_mouse_button")
192
- event(:"clicked_#{button}_mouse_button")
193
- event(:"holding_#{button}_mouse_button")
194
- end
195
-
196
- event(:mouse_wheel_up)
197
- event(:mouse_wheel_down)
198
- event(:scroll_jump_to_top)
199
- event(:scroll_jump_to_end)
200
- event(:scroll_page_up)
201
- event(:scroll_page_down)
202
-
203
- event(:enter)
204
- event(:hover)
205
- event(:leave)
206
-
207
- event(:focus)
208
- event(:blur)
209
-
210
- event(:changed)
211
- end
212
-
213
- def enter(_sender)
214
- @focus = false unless Gosu.button_down?(Gosu::MS_LEFT)
215
-
216
- if !@enabled
217
- update_styles(:disabled)
218
- elsif @focus
219
- update_styles(:active)
220
- else
221
- update_styles(:hover)
222
- end
223
-
224
- :handled
225
- end
226
-
227
- def left_mouse_button(_sender, _x, _y)
228
- @focus = true
229
-
230
- unless @enabled
231
- update_styles(:disabled)
232
- else
233
- update_styles(:active)
234
- end
235
-
236
- window.current_state.focus = self
237
-
238
- :handled
239
- end
240
-
241
- def released_left_mouse_button(sender, _x, _y)
242
- enter(sender)
243
-
244
- :handled
245
- end
246
-
247
- def clicked_left_mouse_button(_sender, _x, _y)
248
- @block&.call(self) if @enabled && !self.is_a?(Container)
249
-
250
- :handled
251
- end
252
-
253
- def leave(_sender)
254
- if @enabled
255
- update_styles
256
- else
257
- update_styles(:disabled)
258
- end
259
-
260
- :handled
261
- end
262
-
263
- def blur(_sender)
264
- @focus = false
265
-
266
- if @enabled
267
- update_styles
268
- else
269
- update_styles(:disabled)
270
- end
271
-
272
- :handled
273
- end
274
-
275
- def enabled=(boolean)
276
- root.gui_state.request_repaint if @enabled != boolean
277
-
278
- @enabled = boolean
279
-
280
- recalculate
281
-
282
- @enabled
283
- end
284
-
285
- def enabled?
286
- @enabled
287
- end
288
-
289
- def focused?
290
- @focus
291
- end
292
-
293
- def visible?
294
- @visible
295
- end
296
-
297
- def element_visible?
298
- @element_visible
299
- end
300
-
301
- def toggle
302
- @visible = !@visible
303
- root.gui_state.request_recalculate
304
- root.gui_state.request_repaint
305
- end
306
-
307
- def show
308
- bool = visible?
309
- @visible = true
310
- root.gui_state.request_recalculate unless bool
311
- root.gui_state.request_repaint unless bool
312
- end
313
-
314
- def hide
315
- bool = visible?
316
- @visible = false
317
- root.gui_state.request_recalculate if bool
318
- root.gui_state.request_repaint if bool
319
- end
320
-
321
- def draw
322
- return unless visible?
323
- return unless element_visible?
324
-
325
- @style.background_canvas.draw
326
- @style.background_nine_slice_canvas.draw
327
- @style.background_image_canvas.draw
328
- @style.border_canvas.draw
329
-
330
- render
331
- end
332
-
333
- def debug_draw
334
- return if CyberarmEngine.const_defined?("GUI_DEBUG_ONLY_ELEMENT") && self.class == GUI_DEBUG_ONLY_ELEMENT
335
-
336
- Gosu.draw_line(
337
- x, y, @debug_color,
338
- x + outer_width, y, @debug_color,
339
- Float::INFINITY
340
- )
341
- Gosu.draw_line(
342
- x + outer_width, y, @debug_color,
343
- x + outer_width, y + outer_height, @debug_color,
344
- Float::INFINITY
345
- )
346
- Gosu.draw_line(
347
- x + outer_width, y + outer_height, @debug_color,
348
- x, y + outer_height, @debug_color,
349
- Float::INFINITY
350
- )
351
- Gosu.draw_line(
352
- x, y + outer_height, @debug_color,
353
- x, y, @debug_color,
354
- Float::INFINITY
355
- )
356
- end
357
-
358
- def update
359
- recalculate_if_size_changed
360
- end
361
-
362
- def button_down(id)
363
- end
364
-
365
- def button_up(id)
366
- end
367
-
368
- def draggable?(_button)
369
- false
370
- end
371
-
372
- def render
373
- end
374
-
375
- def hit?(x, y)
376
- x.between?(@x, @x + width) &&
377
- y.between?(@y, @y + height)
378
- end
379
-
380
- def width
381
- if visible?
382
- inner_width + @width
383
- else
384
- 0
385
- end
386
- end
387
-
388
- def content_width
389
- @width
390
- end
391
-
392
- def noncontent_width
393
- (inner_width + outer_width) - width
394
- end
395
-
396
- def outer_width
397
- @style.margin_left + width + @style.margin_right
398
- end
399
-
400
- def inner_width
401
- (@style.border_thickness_left + @style.padding_left) + (@style.padding_right + @style.border_thickness_right)
402
- end
403
-
404
- def height
405
- if visible?
406
- inner_height + @height
407
- else
408
- 0
409
- end
410
- end
411
-
412
- def content_height
413
- @height
414
- end
415
-
416
- def noncontent_height
417
- (inner_height + outer_height) - height
418
- end
419
-
420
- def outer_height
421
- @style.margin_top + height + @style.margin_bottom
422
- end
423
-
424
- def inner_height
425
- (@style.border_thickness_top + @style.padding_top) + (@style.padding_bottom + @style.border_thickness_bottom)
426
- end
427
-
428
- def scroll_width
429
- return @cached_scroll_width if @cached_scroll_width && is_a?(Container)
430
-
431
- @cached_scroll_width = @children.sum(&:outer_width)
432
- end
433
-
434
- def scroll_height
435
- return @cached_scroll_height if @cached_scroll_height && is_a?(Container)
436
-
437
- if is_a?(CyberarmEngine::Element::Flow)
438
- return 0 if @children.size.zero?
439
-
440
- pairs_ = []
441
- sorted_children_ = @children.sort_by(&:y)
442
- a_ = []
443
- y_position_ = sorted_children_.first.y
444
-
445
- sorted_children_.each do |child|
446
- unless child.y == y_position_
447
- y_position_ = child.y
448
- pairs_ << a_
449
- a_ = []
450
- end
451
-
452
- a_ << child
453
- end
454
-
455
- pairs_ << a_ unless pairs_.last == a_
456
-
457
- @cached_scroll_height = pairs_.sum { |pair| + @style.padding_top + @style.border_thickness_top + pair.map(&:outer_height).max } + @style.padding_bottom + @style.border_thickness_bottom
458
- else
459
- @cached_scroll_height = @style.padding_top + @style.border_thickness_top + @children.sum(&:outer_height) + @style.padding_bottom + @style.border_thickness_bottom
460
- end
461
- end
462
-
463
- def max_scroll_width
464
- (scroll_width - outer_width).positive? ? scroll_width - outer_width : scroll_width
465
- end
466
-
467
- def max_scroll_height
468
- (scroll_height - outer_height).positive? ? scroll_height - outer_height : scroll_height
469
- end
470
-
471
- def dimensional_size(size, dimension)
472
- raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
473
-
474
- new_size = if size.is_a?(Float) && size.between?(0.0, 1.0)
475
- (@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor
476
- else
477
- size
478
- end
479
-
480
- # Handle fill behavior
481
- if @parent && @style.fill &&
482
- (dimension == :width && @parent.is_a?(Flow) ||
483
- dimension == :height && @parent.is_a?(Stack))
484
- new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
485
- new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
486
- end
487
-
488
- return @style.send(:"min_#{dimension}") if @style.send(:"min_#{dimension}") && new_size.to_f < @style.send(:"min_#{dimension}")
489
- return @style.send(:"max_#{dimension}") if @style.send(:"max_#{dimension}") && new_size.to_f > @style.send(:"max_#{dimension}")
490
-
491
- new_size
492
- end
493
-
494
- def space_available_width
495
- # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
496
- fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing
497
-
498
- available_space = ((@parent.content_width - (@parent.children.reject { |c| c.style.fill }).map(&:outer_width).sum) / fill_siblings)
499
- (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
500
- end
501
-
502
- def space_available_height
503
- # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
504
- fill_siblings = @parent.children.select { |c| c.style.fill }.count.to_f # include self since we're dividing
505
-
506
- available_space = ((@parent.content_height - (@parent.children.reject { |c| c.style.fill }).map(&:outer_height).sum) / fill_siblings)
507
- (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
508
- end
509
-
510
- def background=(_background)
511
- root.gui_state.request_repaint
512
-
513
- @style.background_canvas.background = _background
514
- update_background
515
- end
516
-
517
- def update_background
518
- @style.background_canvas.x = @x
519
- @style.background_canvas.y = @y
520
- @style.background_canvas.z = @z
521
- @style.background_canvas.width = width
522
- @style.background_canvas.height = height
523
-
524
- @style.background_canvas.update
525
- update_background_nine_slice
526
- update_background_image
527
- @style.border_canvas.update
528
- end
529
-
530
- def background_nine_slice=(_image_path)
531
- root.gui_state.request_repaint
532
-
533
- @style.background_nine_slice_canvas.image = _image_path
534
- update_background_nine_slice
535
- end
536
-
537
- def update_background_nine_slice
538
- @style.background_nine_slice_canvas.x = @x
539
- @style.background_nine_slice_canvas.y = @y
540
- @style.background_nine_slice_canvas.z = @z
541
- @style.background_nine_slice_canvas.width = width
542
- @style.background_nine_slice_canvas.height = height
543
-
544
- @style.background_nine_slice_canvas.mode = @style.background_nine_slice_mode
545
-
546
- @style.background_nine_slice_canvas.color = @style.background_nine_slice_color
547
-
548
- @style.background_nine_slice_canvas.left = @style.background_nine_slice_left
549
- @style.background_nine_slice_canvas.top = @style.background_nine_slice_top
550
- @style.background_nine_slice_canvas.right = @style.background_nine_slice_right
551
- @style.background_nine_slice_canvas.bottom = @style.background_nine_slice_bottom
552
-
553
- @style.background_nine_slice_canvas.image = @style.background_nine_slice
554
- end
555
-
556
- def background_image=(image_path)
557
- root.gui_state.request_repaint
558
-
559
- @style.background_image = image_path.is_a?(Gosu::Image) ? image_path : get_image(image_path)
560
- update_background_image
561
- end
562
-
563
- def update_background_image
564
- @style.background_image_canvas.x = @x
565
- @style.background_image_canvas.y = @y
566
- @style.background_image_canvas.z = @z
567
- @style.background_image_canvas.width = width
568
- @style.background_image_canvas.height = height
569
-
570
- @style.background_image_canvas.mode = @style.background_image_mode
571
- @style.background_image_canvas.color = @style.background_image_color
572
-
573
- @style.background_image_canvas.image = @style.background_image
574
- end
575
-
576
- def recalculate_if_size_changed
577
- if @parent && !is_a?(ToolTip) && (@old_width != width || @old_height != height)
578
- root.gui_state.request_recalculate
579
-
580
- @old_width = width
581
- @old_height = height
582
- end
583
- end
584
-
585
- def root
586
- return self if is_root?
587
-
588
- unless @root && @root.parent.nil?
589
- @root = parent
590
-
591
- loop do
592
- break unless @root&.parent
593
-
594
- @root = @root.parent
595
- end
596
- end
597
-
598
- @root
599
- end
600
-
601
- def is_root?
602
- @gui_state != nil
603
- end
604
-
605
- def child_of?(element)
606
- return element == self if is_root?
607
- return false unless element.is_a?(Container)
608
- return true if element.children.find { |child| child == self }
609
-
610
- element.children.find { |child| child.child_of?(element) if child.is_a?(Container) }
611
- end
612
-
613
- def parent_of?(element)
614
- return false if element == self
615
- return false unless is_a?(Container)
616
- return true if @children.find { |child| child == element }
617
-
618
- @children.find { |child| child.parent_of?(element) if child.is_a?(Container) }
619
- end
620
-
621
- def focus(_)
622
- warn "#{self.class}#focus was not overridden!"
623
-
624
- :handled
625
- end
626
-
627
- def recalculate
628
- old_width = width
629
- old_height = height
630
-
631
- stylize
632
- layout
633
-
634
- root.gui_state.request_recalculate if @parent && !is_a?(ToolTip) && (width != old_width || height != old_height)
635
- root.gui_state.request_repaint if width != old_width || height != old_height
636
-
637
- root.gui_state.menu.recalculate if root.gui_state.menu && root.gui_state.menu.parent == self
638
- end
639
-
640
- def layout
641
- end
642
-
643
- def reposition
644
- end
645
-
646
- def value
647
- raise "#{self.class}#value was not overridden!"
648
- end
649
-
650
- def value=(_value)
651
- raise "#{self.class}#value= was not overridden!"
652
- end
653
-
654
- def to_s
655
- "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
656
- end
657
-
658
- def inspect
659
- to_s
660
- end
661
- end
662
- end
1
+ module CyberarmEngine
2
+ class Element
3
+ include Theme
4
+ include Event
5
+ include Common
6
+
7
+ attr_accessor :x, :y, :z, :tip, :element_visible
8
+ attr_reader :parent, :options, :style, :event_handler, :background_canvas, :border_canvas
9
+
10
+ def initialize(options = {}, block = nil)
11
+ @parent = options.delete(:parent) # parent Container (i.e. flow/stack)
12
+ options = theme_defaults(options)
13
+ @options = options
14
+ @block = block
15
+
16
+ @focus = !@options.key?(:focus) ? false : @options[:focus]
17
+ @enabled = !@options.key?(:enabled) ? true : @options[:enabled]
18
+ @visible = !@options.key?(:visible) ? true : @options[:visible]
19
+ @tip = @options[:tip] || ""
20
+
21
+ @debug = @options[:debug].nil? ? nil : @options[:debug]
22
+ @debug_color = @options[:debug_color].nil? ? Gosu::Color::RED : @options[:debug_color]
23
+
24
+ @style = Style.new(options)
25
+
26
+ @root ||= nil
27
+ @gui_state ||= nil
28
+ @element_visible = true
29
+
30
+ @x = @style.x
31
+ @y = @style.y
32
+ @z = @style.z
33
+
34
+ @old_width = 0
35
+ @old_height = 0
36
+ @width = 0
37
+ @height = 0
38
+
39
+ @style.width = default(:width) || nil
40
+ @style.height = default(:height) || nil
41
+
42
+ @background_canvas = nil # Background.new
43
+ @background_nine_slice_canvas = nil # BackgroundNineSlice.new
44
+ @background_image_canvas = nil # BackgroundImage.new
45
+ @border_canvas = nil # BorderCanvas.new(element: self)
46
+
47
+ @style_event = enabled? ? :default : :disabled
48
+
49
+ stylize
50
+
51
+ default_events
52
+
53
+ root.gui_state.request_focus(self) if @options[:autofocus]
54
+ end
55
+
56
+ def resolution_scaled(n)
57
+ return n unless @style.design_width
58
+
59
+ ((Gosu.screen_width(window).to_f / @style.design_width) * n).floor
60
+ end
61
+
62
+ def stylize
63
+ @style_event = :disabled unless enabled?
64
+
65
+ set_static_position
66
+
67
+ set_color
68
+ set_font
69
+
70
+ set_background
71
+ set_background_nine_slice
72
+ set_background_image
73
+
74
+ set_border
75
+
76
+ root.gui_state.request_repaint
77
+ end
78
+
79
+ def styled(key)
80
+ case key
81
+ when :border_color_bottom, :border_color_left, :border_color_right, :border_color_top
82
+ safe_style_fetch(key, :border_color)
83
+ when :border_thickness_bottom, :border_thickness_left, :border_thickness_right, :border_thickness_top
84
+ visible? ? safe_style_fetch(key, :border_thickness) : 0
85
+ when :margin_bottom, :margin_left, :margin_right, :margin_top
86
+ visible? ? safe_style_fetch(key, :margin) : 0
87
+ when :padding_bottom, :padding_left, :padding_right, :padding_top
88
+ visible? ? safe_style_fetch(key, :padding) : 0
89
+ else
90
+ safe_style_fetch(key)
91
+ end
92
+ end
93
+
94
+ def safe_style_fetch(key, fallback_key = nil)
95
+ # Attempt to return value for requested key
96
+ v = @style.send(@style_event)&.send(key) || @style.send(key)
97
+ return v if v
98
+
99
+ # Attempt to return overriding value
100
+ if fallback_key
101
+ v = @style.send(@style_event)&.send(fallback_key) || @style.send(fallback_key)
102
+ return v if v
103
+ end
104
+
105
+ # Fallback to default style
106
+ @style.hash.dig(:default, key) || default(key)
107
+ end
108
+
109
+ def set_static_position
110
+ @x = @style.x if @style.x != 0
111
+ @y = @style.y if @style.y != 0
112
+ end
113
+
114
+ def set_color
115
+ @text&.color = safe_style_fetch(:color)
116
+ end
117
+
118
+ def set_font
119
+ @text&.swap_font(resolution_scaled(safe_style_fetch(:text_size)), safe_style_fetch(:font))
120
+ end
121
+
122
+ def set_background
123
+ return unless bg = safe_style_fetch(:background)
124
+
125
+ @background_canvas ||= Background.new
126
+ @background_canvas.background = bg
127
+ end
128
+
129
+ def set_background_nine_slice
130
+ return unless img = safe_style_fetch(:background_nine_slice)
131
+
132
+ @background_nine_slice_canvas ||= BackgroundNineSlice.new
133
+
134
+ @background_nine_slice_canvas.image = img
135
+
136
+ @background_nine_slice_canvas.x = @x + styled(:margin_left)
137
+ @background_nine_slice_canvas.y = @y + styled(:margin_top)
138
+ @background_nine_slice_canvas.z = @z
139
+ @background_nine_slice_canvas.width = width
140
+ @background_nine_slice_canvas.height = height
141
+
142
+ @background_nine_slice_canvas.mode = safe_style_fetch(:background_nine_slice_mode) || :stretch
143
+
144
+ @background_nine_slice_canvas.color = safe_style_fetch(:background_nine_slice_color) || Gosu::Color::WHITE
145
+
146
+ @background_nine_slice_canvas.set_edges(
147
+ left: safe_style_fetch(:background_nine_slice_left, :background_nine_slice_from_edge) || 1,
148
+ top: safe_style_fetch(:background_nine_slice_top, :background_nine_slice_from_edge) || 1,
149
+ right: safe_style_fetch(:background_nine_slice_right, :background_nine_slice_from_edge) || 1,
150
+ bottom: safe_style_fetch(:background_nine_slice_bottom, :background_nine_slice_from_edge) || 1
151
+ )
152
+ end
153
+
154
+ def set_background_image
155
+ return unless img = safe_style_fetch(:background_image)
156
+
157
+ @background_image_canvas ||= BackgroundImage.new
158
+
159
+ @background_image_canvas.image = img
160
+ @background_image_canvas.mode = safe_style_fetch(:background_image_mode) || :stretch
161
+ @background_image_canvas.color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
162
+ end
163
+
164
+ def set_border
165
+ return unless [
166
+ safe_style_fetch(:border_thickness_bottom, :border_thickness),
167
+ safe_style_fetch(:border_thickness_left, :border_thickness),
168
+ safe_style_fetch(:border_thickness_right, :border_thickness),
169
+ safe_style_fetch(:border_thickness_top, :border_thickness),
170
+ ].any?(&:positive?)
171
+
172
+ @border_canvas ||= BorderCanvas.new(element: self)
173
+
174
+ @border_canvas&.color = [
175
+ styled(:border_color_top),
176
+ styled(:border_color_right),
177
+ styled(:border_color_bottom),
178
+ styled(:border_color_left)
179
+ ]
180
+ end
181
+
182
+ def update_styles(event = :default)
183
+ old_width = width
184
+ old_height = height
185
+
186
+ @style_event = event
187
+
188
+ return if self.is_a?(ToolTip)
189
+
190
+ root.gui_state.request_recalculate if old_width != width || old_height != height
191
+
192
+ stylize
193
+ end
194
+
195
+ def default_events
196
+ %i[left middle right].each do |button|
197
+ event(:"#{button}_mouse_button")
198
+ event(:"released_#{button}_mouse_button")
199
+ event(:"clicked_#{button}_mouse_button")
200
+ event(:"holding_#{button}_mouse_button")
201
+ end
202
+
203
+ event(:mouse_wheel_up)
204
+ event(:mouse_wheel_down)
205
+ event(:scroll_jump_to_top)
206
+ event(:scroll_jump_to_end)
207
+ event(:scroll_page_up)
208
+ event(:scroll_page_down)
209
+
210
+ event(:enter)
211
+ event(:hover)
212
+ event(:leave)
213
+
214
+ event(:focus)
215
+ event(:blur)
216
+
217
+ event(:changed)
218
+ event(:size_changed)
219
+ end
220
+
221
+ def enter(_sender)
222
+ @focus = false unless Gosu.button_down?(Gosu::MS_LEFT)
223
+
224
+ if !@enabled
225
+ update_styles(:disabled)
226
+ elsif @focus && !@style.active.empty?
227
+ update_styles(:active)
228
+ else
229
+ update_styles(:hover)
230
+ end
231
+
232
+ :handled
233
+ end
234
+
235
+ def left_mouse_button(_sender, _x, _y)
236
+ @focus = true
237
+
238
+ unless @enabled
239
+ update_styles(:disabled)
240
+ else
241
+ update_styles(:active) unless @style.active.empty?
242
+ end
243
+
244
+ window.current_state.focus = self
245
+
246
+ :handled
247
+ end
248
+
249
+ def released_left_mouse_button(sender, _x, _y)
250
+ enter(sender)
251
+
252
+ :handled
253
+ end
254
+
255
+ def clicked_left_mouse_button(_sender, _x, _y)
256
+ @block&.call(self) if @enabled && !self.is_a?(Container)
257
+
258
+ :handled
259
+ end
260
+
261
+ def leave(_sender)
262
+ if @enabled
263
+ update_styles
264
+ else
265
+ update_styles(:disabled)
266
+ end
267
+
268
+ :handled
269
+ end
270
+
271
+ def blur(_sender)
272
+ @focus = false
273
+
274
+ if @enabled
275
+ update_styles
276
+ else
277
+ update_styles(:disabled)
278
+ end
279
+
280
+ :handled
281
+ end
282
+
283
+ def enabled=(boolean)
284
+ root.gui_state.request_repaint if @enabled != boolean
285
+
286
+ @enabled = boolean
287
+ @style_event = :default if boolean && @style_event == :disabled
288
+
289
+ recalculate
290
+
291
+ @enabled
292
+ end
293
+
294
+ def enabled?
295
+ @enabled
296
+ end
297
+
298
+ def focused?
299
+ @focus
300
+ end
301
+
302
+ def visible?
303
+ @visible
304
+ end
305
+
306
+ def element_visible?
307
+ @element_visible
308
+ end
309
+
310
+ def toggle
311
+ @visible = !@visible
312
+ root.gui_state.request_recalculate
313
+ root.gui_state.request_repaint
314
+ end
315
+
316
+ def show
317
+ bool = visible?
318
+ @visible = true
319
+ root.gui_state.request_recalculate unless bool
320
+ root.gui_state.request_repaint unless bool
321
+ end
322
+
323
+ def hide
324
+ bool = visible?
325
+ @visible = false
326
+ root.gui_state.request_recalculate if bool
327
+ root.gui_state.request_repaint if bool
328
+ end
329
+
330
+ def draw
331
+ return unless visible?
332
+ return unless element_visible?
333
+
334
+ @background_canvas&.draw
335
+ @background_nine_slice_canvas&.draw
336
+ @background_image_canvas&.draw
337
+ @border_canvas&.draw
338
+
339
+ render
340
+ end
341
+
342
+ def debug_draw
343
+ return if @debug == false || !CyberarmEngine::GUI_DEBUG # allow elements to opt out of debug drawing, makes debugging some things easier.
344
+ return if CyberarmEngine.const_defined?("GUI_DEBUG_ONLY_ELEMENT") && self.class == GUI_DEBUG_ONLY_ELEMENT
345
+
346
+ Gosu.draw_line(
347
+ x, y, @debug_color,
348
+ x + outer_width, y, @debug_color,
349
+ Float::INFINITY
350
+ )
351
+ Gosu.draw_line(
352
+ x + outer_width, y, @debug_color,
353
+ x + outer_width, y + outer_height, @debug_color,
354
+ Float::INFINITY
355
+ )
356
+ Gosu.draw_line(
357
+ x + outer_width, y + outer_height, @debug_color,
358
+ x, y + outer_height, @debug_color,
359
+ Float::INFINITY
360
+ )
361
+ Gosu.draw_line(
362
+ x, y + outer_height, @debug_color,
363
+ x, y, @debug_color,
364
+ Float::INFINITY
365
+ )
366
+ end
367
+
368
+ def update
369
+ recalculate_if_size_changed
370
+
371
+ if @style.dirty?
372
+ @style.mark_clean!
373
+ stylize
374
+ end
375
+ end
376
+
377
+ def button_down(id)
378
+ end
379
+
380
+ def button_up(id)
381
+ end
382
+
383
+ def draggable?(_button)
384
+ false
385
+ end
386
+
387
+ def render
388
+ end
389
+
390
+ def hit?(x, y)
391
+ x.between?(@x + styled(:margin_left), @x + styled(:margin_left) + width) &&
392
+ y.between?(@y + styled(:margin_top), @y + styled(:margin_top) + height)
393
+ end
394
+
395
+ def width
396
+ if visible?
397
+ inner_width + content_width
398
+ else
399
+ 0
400
+ end
401
+ end
402
+
403
+ def content_width
404
+ @width
405
+ end
406
+
407
+ def noncontent_width
408
+ (inner_width + outer_width) - width
409
+ end
410
+
411
+ def outer_width
412
+ styled(:margin_left) + width + styled(:margin_right)
413
+ end
414
+
415
+ def inner_width
416
+ (styled(:border_thickness_left) + styled(:padding_left)) + (styled(:padding_right) + styled(:border_thickness_right))
417
+ end
418
+
419
+ def height
420
+ if visible?
421
+ inner_height + content_height
422
+ else
423
+ 0
424
+ end
425
+ end
426
+
427
+ def content_height
428
+ @height
429
+ end
430
+
431
+ def noncontent_height
432
+ (inner_height + outer_height) - height
433
+ end
434
+
435
+ def outer_height
436
+ styled(:margin_top) + height + styled(:margin_bottom)
437
+ end
438
+
439
+ def inner_height
440
+ (styled(:border_thickness_top) + styled(:padding_top)) + (styled(:padding_bottom) + styled(:border_thickness_bottom))
441
+ end
442
+
443
+ def scroll_width
444
+ return @cached_scroll_width if @cached_scroll_width && is_a?(Container)
445
+
446
+ @cached_scroll_width = @children.sum(&:outer_width)
447
+ end
448
+
449
+ def scroll_height
450
+ return @cached_scroll_height if @cached_scroll_height && is_a?(Container)
451
+
452
+ if is_a?(CyberarmEngine::Element::Flow)
453
+ return 0 if @children.size.zero?
454
+
455
+ pairs_ = []
456
+ sorted_children_ = @children.sort_by(&:y)
457
+ a_ = []
458
+ y_position_ = sorted_children_.first.y
459
+
460
+ sorted_children_.each do |child|
461
+ unless child.y == y_position_
462
+ y_position_ = child.y
463
+ pairs_ << a_
464
+ a_ = []
465
+ end
466
+
467
+ a_ << child
468
+ end
469
+
470
+ pairs_ << a_ unless pairs_.last == a_
471
+
472
+ @cached_scroll_height = pairs_.sum { |pair| pair.map(&:outer_height).max } + noncontent_height
473
+ else
474
+ @cached_scroll_height = noncontent_height + @children.sum(&:outer_height)
475
+ end
476
+ end
477
+
478
+ def max_scroll_width
479
+ (scroll_width - outer_width).positive? ? scroll_width - outer_width : scroll_width
480
+ end
481
+
482
+ def max_scroll_height
483
+ (scroll_height - outer_height).positive? ? scroll_height - outer_height : scroll_height
484
+ end
485
+
486
+ def dimensional_size(size, dimension)
487
+ raise "dimension must be either :width or :height" unless %i[width height].include?(dimension)
488
+
489
+ new_size = if size.is_a?(Float) && size.between?(0.0, 1.0)
490
+ (@parent.send(:"content_#{dimension}") * size).floor - send(:"noncontent_#{dimension}").floor
491
+ elsif size.is_a?(Numeric)
492
+ (size - send(:"noncontent_#{dimension}")).floor
493
+ else
494
+ size
495
+ end
496
+
497
+ # Handle fill behavior
498
+ if @parent && styled(:fill) &&
499
+ (dimension == :width && @parent.is_a?(Flow) ||
500
+ dimension == :height && @parent.is_a?(Stack))
501
+ new_size = space_available_width - noncontent_width if dimension == :width && @parent.is_a?(Flow)
502
+ new_size = space_available_height - noncontent_height if dimension == :height && @parent.is_a?(Stack)
503
+ end
504
+
505
+ return styled(:"min_#{dimension}") if styled(:"min_#{dimension}") && new_size.to_f < styled(:"min_#{dimension}")
506
+ return styled(:"max_#{dimension}") if styled(:"max_#{dimension}") && new_size.to_f > styled(:"max_#{dimension}")
507
+
508
+ new_size
509
+ end
510
+
511
+ def space_available_width
512
+ # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
513
+ fill_siblings = @parent.children.select { |c| c.styled(:fill) }.count.to_f # include self since we're dividing
514
+
515
+ available_space = ((@parent.content_width - (@parent.children.reject { |c| c.styled(:fill) }).map(&:outer_width).sum) / fill_siblings)
516
+ (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
517
+ end
518
+
519
+ def space_available_height
520
+ # TODO: This may get expensive if there are a lot of children, probably should cache it somehow
521
+ fill_siblings = @parent.children.select { |c| c.styled(:fill) && c.visible? }.count.to_f # include self since we're dividing
522
+
523
+ available_space = ((@parent.content_height - (@parent.children.reject { |c| c.styled(:fill) }).map(&:outer_height).sum) / fill_siblings)
524
+ (available_space.nan? || available_space.infinite?) ? 0 : available_space.floor # The parent element might not have its dimensions, yet.
525
+ end
526
+
527
+ def background=(_background)
528
+ root.gui_state.request_repaint
529
+
530
+ @background_canvas.background = _background
531
+ update_background
532
+ end
533
+
534
+ def update_background
535
+ @background_canvas&.x = @x + styled(:margin_left)
536
+ @background_canvas&.y = @y + styled(:margin_top)
537
+ @background_canvas&.z = @z
538
+ @background_canvas&.width = width
539
+ @background_canvas&.height = height
540
+
541
+ @background_canvas&.update
542
+ set_background_nine_slice
543
+ update_background_image
544
+ @border_canvas&.update
545
+ end
546
+
547
+ def background_nine_slice=(_image_path)
548
+ root.gui_state.request_repaint
549
+
550
+ @background_nine_slice_canvas.image = _image_path
551
+ set_background_nine_slice
552
+ end
553
+
554
+ def background_image=(image_path)
555
+ root.gui_state.request_repaint
556
+
557
+ @style.background_image = image_path.is_a?(Gosu::Image) ? image_path : get_image(image_path)
558
+ update_background_image
559
+ end
560
+
561
+ def update_background_image
562
+ return unless @background_image_canvas
563
+
564
+ @background_image_canvas.x = @x + styled(:margin_left)
565
+ @background_image_canvas.y = @y + styled(:margin_top)
566
+ @background_image_canvas.z = @z
567
+ @background_image_canvas.width = width
568
+ @background_image_canvas.height = height
569
+
570
+ @background_image_canvas.mode = safe_style_fetch(:background_image_mode) || :stretch
571
+ @background_image_canvas.color = safe_style_fetch(:background_image_color) || Gosu::Color::WHITE
572
+
573
+ @background_image_canvas.image = safe_style_fetch(:background_image)
574
+ end
575
+
576
+ def recalculate_if_size_changed
577
+ if @parent && !is_a?(ToolTip) && (@old_width != width || @old_height != height)
578
+ root.gui_state.request_recalculate
579
+
580
+ @old_width = width
581
+ @old_height = height
582
+
583
+ publish(:size_changed)
584
+ end
585
+ end
586
+
587
+ def root
588
+ return self if is_root?
589
+
590
+ unless @root && @root.parent.nil?
591
+ @root = parent
592
+
593
+ loop do
594
+ break unless @root&.parent
595
+
596
+ @root = @root.parent
597
+ end
598
+ end
599
+
600
+ @root
601
+ end
602
+
603
+ def is_root?
604
+ @gui_state != nil
605
+ end
606
+
607
+ def child_of?(element)
608
+ return element == self if is_root?
609
+ return false unless element.is_a?(Container)
610
+ return true if element.children.find { |child| child == self }
611
+
612
+ element.children.find { |child| child.child_of?(element) if child.is_a?(Container) }
613
+ end
614
+
615
+ def parent_of?(element)
616
+ return false if element == self
617
+ return false unless is_a?(Container)
618
+ return true if @children.find { |child| child == element }
619
+
620
+ @children.find { |child| child.parent_of?(element) if child.is_a?(Container) }
621
+ end
622
+
623
+ def focus(_)
624
+ warn "#{self.class}#focus was not overridden!"
625
+
626
+ :handled
627
+ end
628
+
629
+ def recalculate
630
+ old_width = width
631
+ old_height = height
632
+
633
+ stylize
634
+ layout
635
+
636
+ root.gui_state.request_recalculate if @parent && !is_a?(ToolTip) && (width != old_width || height != old_height)
637
+ root.gui_state.request_repaint if width != old_width || height != old_height
638
+
639
+ root.gui_state.active_menu.recalculate if root.gui_state.active_menu && root.gui_state.active_menu.parent == self
640
+ end
641
+
642
+ def layout
643
+ end
644
+
645
+ def reposition
646
+ end
647
+
648
+ def value
649
+ raise "#{self.class}#value was not overridden!"
650
+ end
651
+
652
+ def value=(_value)
653
+ raise "#{self.class}#value= was not overridden!"
654
+ end
655
+
656
+ def to_s
657
+ "#{self.class} x=#{x} y=#{y} width=#{width} height=#{height} value=#{value.is_a?(String) ? "\"#{value}\"" : value}"
658
+ end
659
+
660
+ def inspect
661
+ to_s
662
+ end
663
+ end
664
+ end