cyberarm_engine 0.12.0 → 0.15.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/Gemfile +1 -1
  4. data/README.md +33 -3
  5. data/Rakefile +1 -1
  6. data/assets/textures/default.png +0 -0
  7. data/cyberarm_engine.gemspec +11 -8
  8. data/lib/cyberarm_engine.rb +24 -4
  9. data/lib/cyberarm_engine/animator.rb +56 -0
  10. data/lib/cyberarm_engine/background.rb +19 -15
  11. data/lib/cyberarm_engine/background_nine_slice.rb +125 -0
  12. data/lib/cyberarm_engine/bounding_box.rb +18 -18
  13. data/lib/cyberarm_engine/cache.rb +4 -0
  14. data/lib/cyberarm_engine/cache/download_manager.rb +121 -0
  15. data/lib/cyberarm_engine/common.rb +16 -16
  16. data/lib/cyberarm_engine/config_file.rb +46 -0
  17. data/lib/cyberarm_engine/game_object.rb +63 -72
  18. data/lib/cyberarm_engine/game_state.rb +7 -4
  19. data/lib/cyberarm_engine/model.rb +207 -0
  20. data/lib/cyberarm_engine/model/material.rb +21 -0
  21. data/lib/cyberarm_engine/model/model_object.rb +131 -0
  22. data/lib/cyberarm_engine/model/parser.rb +74 -0
  23. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -0
  24. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -0
  25. data/lib/cyberarm_engine/model_cache.rb +31 -0
  26. data/lib/cyberarm_engine/opengl.rb +28 -0
  27. data/lib/cyberarm_engine/opengl/light.rb +50 -0
  28. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -0
  29. data/lib/cyberarm_engine/opengl/perspective_camera.rb +38 -0
  30. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -0
  31. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +164 -0
  32. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +289 -0
  33. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +22 -0
  34. data/lib/cyberarm_engine/opengl/shader.rb +406 -0
  35. data/lib/cyberarm_engine/opengl/texture.rb +69 -0
  36. data/lib/cyberarm_engine/ray.rb +5 -5
  37. data/lib/cyberarm_engine/stats.rb +21 -0
  38. data/lib/cyberarm_engine/text.rb +45 -31
  39. data/lib/cyberarm_engine/timer.rb +1 -1
  40. data/lib/cyberarm_engine/transform.rb +221 -9
  41. data/lib/cyberarm_engine/ui/border_canvas.rb +4 -3
  42. data/lib/cyberarm_engine/ui/dsl.rb +68 -50
  43. data/lib/cyberarm_engine/ui/element.rb +57 -18
  44. data/lib/cyberarm_engine/ui/elements/button.rb +86 -16
  45. data/lib/cyberarm_engine/ui/elements/check_box.rb +24 -32
  46. data/lib/cyberarm_engine/ui/elements/container.rb +88 -24
  47. data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -0
  48. data/lib/cyberarm_engine/ui/elements/edit_line.rb +180 -27
  49. data/lib/cyberarm_engine/ui/elements/flow.rb +1 -3
  50. data/lib/cyberarm_engine/ui/elements/image.rb +12 -9
  51. data/lib/cyberarm_engine/ui/elements/label.rb +96 -15
  52. data/lib/cyberarm_engine/ui/elements/list_box.rb +68 -0
  53. data/lib/cyberarm_engine/ui/elements/progress.rb +6 -5
  54. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -0
  55. data/lib/cyberarm_engine/ui/elements/slider.rb +104 -0
  56. data/lib/cyberarm_engine/ui/elements/stack.rb +1 -3
  57. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +40 -31
  58. data/lib/cyberarm_engine/ui/event.rb +8 -7
  59. data/lib/cyberarm_engine/ui/gui_state.rb +89 -6
  60. data/lib/cyberarm_engine/ui/style.rb +10 -9
  61. data/lib/cyberarm_engine/ui/theme.rb +39 -21
  62. data/lib/cyberarm_engine/vector.rb +132 -38
  63. data/lib/cyberarm_engine/version.rb +2 -2
  64. data/lib/cyberarm_engine/{engine.rb → window.rb} +30 -19
  65. metadata +87 -16
  66. data/lib/cyberarm_engine/shader.rb +0 -205
@@ -2,35 +2,73 @@ module CyberarmEngine
2
2
  class Element
3
3
  class EditLine < Button
4
4
  def initialize(text, options = {}, block = nil)
5
+ @filter = options.delete(:filter)
5
6
  super(text, options, block)
6
7
 
7
8
  @type = default(:type)
8
9
 
9
10
  @caret_width = default(:caret_width)
10
- @caret_height= @text.height
11
+ @caret_height = @text.textobject.height
11
12
  @caret_color = default(:caret_color)
12
13
  @caret_interval = default(:caret_interval)
13
14
  @caret_last_interval = Gosu.milliseconds
14
- @show_caret = true
15
+ @show_caret = true
15
16
 
16
17
  @text_input = Gosu::TextInput.new
17
18
  @text_input.text = text
19
+ @last_text_value = text
20
+
21
+ if @filter && @filter.respond_to?(:call)
22
+ @text_input.instance_variable_set(:@filter, @filter)
23
+
24
+ def @text_input.filter(text_in)
25
+ @filter.call(text_in)
26
+ end
27
+ end
28
+
29
+ @offset_x = 0
30
+ @offset_y = 0
18
31
 
19
- return self
32
+ event(:begin_drag)
33
+ event(:drag_update)
34
+ event(:end_drag)
20
35
  end
21
36
 
22
37
  def render
23
- Gosu.clip_to(@text.x, @text.y, @style.width, @text.height) do
24
- draw_text
25
- Gosu.draw_rect(caret_position, @text.y, @caret_width, @caret_height, @caret_color, @z + 40) if @focus && @show_caret
38
+ Gosu.clip_to(@text.x, @text.y, @width, @height) do
39
+ Gosu.translate(-@offset_x, -@offset_y) do
40
+ draw_selection
41
+ draw_caret if @focus && @show_caret
42
+ draw_text
43
+ end
26
44
  end
27
45
  end
28
46
 
47
+ def draw_text
48
+ @text.draw(:draw_text)
49
+ end
50
+
51
+ def draw_caret
52
+ Gosu.draw_rect(caret_position, @text.y, @caret_width, @caret_height, @caret_color, @z)
53
+ end
54
+
55
+ def draw_selection
56
+ selection_width = caret_position - selection_start_position
57
+
58
+ Gosu.draw_rect(selection_start_position, @text.y, selection_width, @text.height, default(:selection_color), @z)
59
+ end
60
+
29
61
  def update
30
- if @type == :password
31
- @text.text = default(:password_character) * @text_input.text.length
32
- else
33
- @text.text = @text_input.text
62
+ @text.text = if @type == :password
63
+ default(:password_character) * @text_input.text.length
64
+ else
65
+ @text_input.text
66
+ end
67
+
68
+ if @last_text_value != value
69
+ @last_text_value = value
70
+
71
+ publish(:changed, value)
34
72
  end
35
73
 
36
74
  if Gosu.milliseconds >= @caret_last_interval + @caret_interval
@@ -38,6 +76,108 @@ module CyberarmEngine
38
76
 
39
77
  @show_caret = !@show_caret
40
78
  end
79
+
80
+ keep_caret_visible
81
+ end
82
+
83
+ def button_down(id)
84
+ handle_keyboard_shortcuts(id)
85
+ end
86
+
87
+ def handle_keyboard_shortcuts(id)
88
+ return unless @focus && @enabled
89
+
90
+ if Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
91
+ case id
92
+ when Gosu::KB_A
93
+ @text_input.selection_start = 0
94
+ @text_input.caret_pos = @text_input.text.length
95
+
96
+ when Gosu::KB_C
97
+ if @text_input.selection_start < @text_input.caret_pos
98
+ Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
99
+ else
100
+ Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
101
+ end
102
+
103
+ when Gosu::KB_X
104
+ chars = @text_input.text.chars
105
+
106
+ if @text_input.selection_start < @text_input.caret_pos
107
+ Clipboard.copy(@text_input.text[@text_input.selection_start...@text_input.caret_pos])
108
+ chars.slice!(@text_input.selection_start, @text_input.caret_pos)
109
+ else
110
+ Clipboard.copy(@text_input.text[@text_input.caret_pos...@text_input.selection_start])
111
+ chars.slice!(@text_input.caret_pos, @text_input.selection_start)
112
+ end
113
+
114
+ @text_input.text = chars.join
115
+
116
+ when Gosu::KB_V
117
+ if instance_of?(EditLine) # EditLine assumes a single line of text
118
+ @text_input.text = @text_input.text.insert(@text_input.caret_pos,
119
+ Clipboard.paste.encode("UTF-8").gsub("\n", ""))
120
+ else
121
+ @text_input.text = @text_input.text.insert(@text_input.caret_pos, Clipboard.paste.encode("UTF-8"))
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ def caret_position_under_mouse(mouse_x)
128
+ 1.upto(@text.text.length) do |i|
129
+ return i - 1 if mouse_x < @text.x - @offset_x + @text.width(@text.text[0...i])
130
+ end
131
+
132
+ @text_input.text.length
133
+ end
134
+
135
+ def move_caret_to_mouse(mouse_x, _mouse_y)
136
+ @text_input.caret_pos = @text_input.selection_start = caret_position_under_mouse(mouse_x)
137
+ end
138
+
139
+ def keep_caret_visible
140
+ caret_pos = (caret_position - @text.x) + @caret_width
141
+
142
+ @last_text ||= "/\\"
143
+ @last_pos ||= -1
144
+
145
+ @last_text = @text.text
146
+ @last_pos = caret_pos
147
+
148
+ if caret_pos.between?(@offset_x, @width + @offset_x)
149
+ # Do nothing
150
+
151
+ elsif caret_pos < @offset_x
152
+ @offset_x = if caret_pos > @width
153
+ caret_pos + @width
154
+ else
155
+ 0
156
+ end
157
+
158
+ elsif caret_pos > @width
159
+ @offset_x = caret_pos - @width
160
+
161
+ else
162
+ # Reset to Zero
163
+ @offset_x = 0
164
+ end
165
+ end
166
+
167
+ def caret_position
168
+ text_input_position_for(:caret_pos)
169
+ end
170
+
171
+ def selection_start_position
172
+ text_input_position_for(:selection_start)
173
+ end
174
+
175
+ def text_input_position_for(method)
176
+ if @type == :password
177
+ @text.x + @text.width(default(:password_character) * @text_input.text[0...@text_input.send(method)].length)
178
+ else
179
+ @text.x + @text.width(@text_input.text[0...@text_input.send(method)])
180
+ end
41
181
  end
42
182
 
43
183
  def left_mouse_button(sender, x, y)
@@ -47,10 +187,12 @@ module CyberarmEngine
47
187
  @caret_last_interval = Gosu.milliseconds
48
188
  @show_caret = true
49
189
 
50
- return :handled
190
+ move_caret_to_mouse(x, y)
191
+
192
+ :handled
51
193
  end
52
194
 
53
- def enter(sender)
195
+ def enter(_sender)
54
196
  if @focus
55
197
  @style.background_canvas.background = default(:active, :background)
56
198
  @text.color = default(:active, :color)
@@ -59,33 +201,44 @@ module CyberarmEngine
59
201
  @text.color = default(:hover, :color)
60
202
  end
61
203
 
62
- return :handled
204
+ :handled
63
205
  end
64
206
 
65
207
  def leave(sender)
66
- unless @focus
67
- super
68
- end
208
+ super unless @focus
69
209
 
70
- return :handled
210
+ :handled
71
211
  end
72
212
 
73
- def blur(sender)
213
+ def blur(_sender)
74
214
  @focus = false
75
215
  @style.background_canvas.background = default(:background)
76
216
  @text.color = default(:color)
77
217
  window.text_input = nil
78
218
 
79
- return :handled
219
+ :handled
80
220
  end
81
221
 
82
- # TODO: Fix caret rendering in wrong position unless caret_pos is at end of text
83
- def caret_position
84
- if @type == :password
85
- @text.x + @text.textobject.text_width(default(:password_character) * @text_input.text[0..@text_input.caret_pos-1].length)
86
- else
87
- @text.x + @text.textobject.text_width(@text_input.text[0..@text_input.caret_pos-1])
88
- end
222
+ def draggable?(button)
223
+ button == :left
224
+ end
225
+
226
+ def begin_drag(_sender, x, _y, _button)
227
+ @drag_start = x
228
+ @offset_drag_start = @offset_x
229
+ @drag_caret_position = @text_input.caret_pos
230
+
231
+ :handled
232
+ end
233
+
234
+ def drag_update(_sender, x, _y, _button)
235
+ @text_input.caret_pos = caret_position_under_mouse(x)
236
+
237
+ :handled
238
+ end
239
+
240
+ def end_drag(_sender, _x, _y, _button)
241
+ :handled
89
242
  end
90
243
 
91
244
  def recalculate
@@ -100,4 +253,4 @@ module CyberarmEngine
100
253
  end
101
254
  end
102
255
  end
103
- end
256
+ end
@@ -1,8 +1,6 @@
1
1
  module CyberarmEngine
2
2
  class Element
3
3
  class Flow < Container
4
- include Common
5
-
6
4
  def layout
7
5
  @children.each do |child|
8
6
  if fits_on_line?(child)
@@ -14,4 +12,4 @@ module CyberarmEngine
14
12
  end
15
13
  end
16
14
  end
17
- end
15
+ end
@@ -6,7 +6,8 @@ module CyberarmEngine
6
6
  @path = path
7
7
 
8
8
  @image = Gosu::Image.new(path, retro: @options[:image_retro])
9
- @scale_x, @scale_y = 1, 1
9
+ @scale_x = 1
10
+ @scale_y = 1
10
11
  end
11
12
 
12
13
  def render
@@ -14,18 +15,19 @@ module CyberarmEngine
14
15
  @style.border_thickness_left + @style.padding_left + @x,
15
16
  @style.border_thickness_top + @style.padding_top + @y,
16
17
  @z + 2,
17
- @scale_x, @scale_y) # TODO: Add color support?
18
+ @scale_x, @scale_y, @style.color
19
+ )
18
20
  end
19
21
 
20
- def clicked_left_mouse_button(sender, x, y)
22
+ def clicked_left_mouse_button(_sender, _x, _y)
21
23
  @block.call(self) if @block
22
24
 
23
- return :handled
25
+ :handled
24
26
  end
25
27
 
26
28
  def recalculate
27
29
  _width = dimensional_size(@style.width, :width)
28
- _height= dimensional_size(@style.height,:height)
30
+ _height = dimensional_size(@style.height, :height)
29
31
 
30
32
  if _width && _height
31
33
  @scale_x = _width.to_f / @image.width
@@ -37,11 +39,12 @@ module CyberarmEngine
37
39
  @scale_y = _height.to_f / @image.height
38
40
  @scale_x = @scale_y
39
41
  else
40
- @scale_x, @scale_y = 1, 1
42
+ @scale_x = 1
43
+ @scale_y = 1
41
44
  end
42
45
 
43
- @width = _width ? _width : @image.width.round * @scale_x
44
- @height= _height ? _height : @image.height.round * @scale_y
46
+ @width = _width || @image.width.round * @scale_x
47
+ @height = _height || @image.height.round * @scale_y
45
48
  end
46
49
 
47
50
  def value
@@ -49,4 +52,4 @@ module CyberarmEngine
49
52
  end
50
53
  end
51
54
  end
52
- end
55
+ end
@@ -4,47 +4,128 @@ module CyberarmEngine
4
4
  def initialize(text, options = {}, block = nil)
5
5
  super(options, block)
6
6
 
7
- @text = Text.new(text, font: @options[:font], z: @z, color: @options[:color], size: @options[:text_size], shadow: @options[:text_shadow])
7
+ @text = Text.new(
8
+ text, font: @options[:font], z: @z, color: @options[:color],
9
+ size: @options[:text_size], shadow: @options[:text_shadow],
10
+ shadow_size: @options[:text_shadow_size],
11
+ shadow_color: @options[:text_shadow_color]
12
+ )
13
+
14
+ @raw_text = text
8
15
  end
9
16
 
10
17
  def render
11
18
  @text.draw
12
19
  end
13
20
 
14
- def clicked_left_mouse_button(sender, x, y)
15
- @block.call(self) if @block
21
+ def clicked_left_mouse_button(_sender, _x, _y)
22
+ @block&.call(self)
16
23
 
17
- return :handled
24
+ # return :handled
18
25
  end
19
26
 
20
27
  def recalculate
21
- @width, @height = 0, 0
28
+ @width = 0
29
+ @height = 0
30
+
31
+ _width = dimensional_size(@style.width, :width)
32
+ _height = dimensional_size(@style.height, :height)
22
33
 
23
- _width = dimensional_size(@style.width, :width)
24
- _height= dimensional_size(@style.height,:height)
34
+ handle_text_wrapping(_width)
25
35
 
26
- @width = _width ? _width : @text.width.round
27
- @height= _height ? _height : @text.height.round
36
+ @width = _width || @text.width.round
37
+ @height = _height || @text.height.round
28
38
 
29
- @text.x = @style.border_thickness_left + @style.padding_left + @x
30
- @text.y = @style.border_thickness_top + @style.padding_top + @y
39
+ @text.y = @style.border_thickness_top + @style.padding_top + @y
31
40
  @text.z = @z + 3
32
41
 
42
+ if (text_alignment = @options[:text_align])
43
+ case text_alignment
44
+ when :left
45
+ @text.x = @style.border_thickness_left + @style.padding_left + @x
46
+ when :center
47
+ @text.x = if @text.width <= outer_width
48
+ @x + outer_width / 2 - @text.width / 2
49
+ else # Act as left aligned
50
+ @style.border_thickness_left + @style.padding_left + @x
51
+ end
52
+ when :right
53
+ @text.x = @x + outer_width - (@text.width + @style.border_thickness_right + @style.padding_right)
54
+ end
55
+ end
56
+
33
57
  update_background
34
58
  end
35
59
 
60
+ def handle_text_wrapping(max_width)
61
+ max_width ||= @parent&.width
62
+ max_width ||= @x - (window.width + noncontent_width)
63
+ wrap_behavior = style.text_wrap
64
+ copy = @raw_text.to_s.dup
65
+
66
+ if max_width >= line_width(copy[0]) && line_width(copy) > max_width && wrap_behavior != :none
67
+ breaks = []
68
+ line_start = 0
69
+ line_end = copy.length
70
+
71
+ while line_start != copy.length
72
+ if line_width(copy[line_start...line_end]) > max_width
73
+ line_end = ((line_end - line_start) / 2.0)
74
+ elsif line_end < copy.length && line_width(copy[line_start...line_end + 1]) < max_width
75
+ # To small, grow!
76
+ # TODO: find a more efficient way
77
+ line_end += 1
78
+
79
+ else # FOUND IT!
80
+ entering_line_end = line_end.floor
81
+ max_reach = line_end.floor - line_start < 63 ? line_end.floor - line_start : 63
82
+ reach = 0
83
+
84
+ if wrap_behavior == :word_wrap
85
+ max_reach.times do |i|
86
+ reach = i
87
+ break if copy[line_end.floor - i].to_s.match(/[[:punct:]]| /)
88
+ end
89
+
90
+ puts "Max width: #{max_width}/#{line_width(@raw_text)} Reach: {#{reach}/#{max_reach}} Line Start: #{line_start}/#{line_end.floor} (#{copy.length}|#{@raw_text.length}) [#{entering_line_end}] '#{copy}' {#{copy[line_start...line_end]}}"
91
+ line_end = line_end.floor - reach + 1 if reach != max_reach # Add +1 to walk in front of punctuation
92
+ end
93
+
94
+ breaks << line_end.floor
95
+ line_start = line_end.floor
96
+ line_end = copy.length
97
+
98
+ break if entering_line_end == copy.length || reach == max_reach
99
+ end
100
+ end
101
+
102
+ breaks.each_with_index do |pos, index|
103
+ copy.insert(pos + index, "\n") if pos + index >= 0 && pos + index < copy.length
104
+ end
105
+ end
106
+
107
+ @text.text = copy
108
+ end
109
+
110
+ def line_width(text)
111
+ (@x + @text.textobject.markup_width(text) + noncontent_width)
112
+ end
113
+
36
114
  def value
37
- @text.text
115
+ @raw_text
38
116
  end
39
117
 
40
118
  def value=(value)
41
- @text.text = value
119
+ @raw_text = value.to_s.chomp
42
120
 
43
- old_width, old_height = width, height
121
+ old_width = width
122
+ old_height = height
44
123
  recalculate
45
124
 
46
125
  root.gui_state.request_recalculate if old_width != width || old_height != height
126
+
127
+ publish(:changed, self.value)
47
128
  end
48
129
  end
49
130
  end
50
- end
131
+ end