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,662 +1,662 @@
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_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