cyberarm_engine 0.25.0 → 0.25.1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -8
  3. data/.rubocop.yml +7 -7
  4. data/.travis.yml +5 -5
  5. data/Gemfile +6 -6
  6. data/Gemfile.lock +25 -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/bin/console +14 -14
  17. data/bin/setup +8 -8
  18. data/cyberarm_engine.gemspec +36 -36
  19. data/lib/cyberarm_engine/animator.rb +219 -219
  20. data/lib/cyberarm_engine/background.rb +158 -158
  21. data/lib/cyberarm_engine/background_image.rb +93 -93
  22. data/lib/cyberarm_engine/background_nine_slice.rb +142 -142
  23. data/lib/cyberarm_engine/bounding_box.rb +150 -150
  24. data/lib/cyberarm_engine/builtin/intro_state.rb +130 -130
  25. data/lib/cyberarm_engine/cache/download_manager.rb +123 -123
  26. data/lib/cyberarm_engine/cache.rb +4 -4
  27. data/lib/cyberarm_engine/common.rb +131 -131
  28. data/lib/cyberarm_engine/config_file.rb +46 -46
  29. data/lib/cyberarm_engine/console/command.rb +157 -157
  30. data/lib/cyberarm_engine/console/commands/help_command.rb +43 -43
  31. data/lib/cyberarm_engine/console/subcommand.rb +99 -99
  32. data/lib/cyberarm_engine/console.rb +248 -248
  33. data/lib/cyberarm_engine/game_object.rb +244 -244
  34. data/lib/cyberarm_engine/game_state.rb +124 -124
  35. data/lib/cyberarm_engine/gosu_ext/draw_arc.rb +111 -111
  36. data/lib/cyberarm_engine/gosu_ext/draw_circle.rb +30 -30
  37. data/lib/cyberarm_engine/gosu_ext/draw_path.rb +17 -17
  38. data/lib/cyberarm_engine/model/material.rb +21 -21
  39. data/lib/cyberarm_engine/model/mesh.rb +131 -131
  40. data/lib/cyberarm_engine/model/parser.rb +74 -74
  41. data/lib/cyberarm_engine/model/parsers/collada_parser.rb +138 -138
  42. data/lib/cyberarm_engine/model/parsers/wavefront_parser.rb +154 -154
  43. data/lib/cyberarm_engine/model.rb +216 -216
  44. data/lib/cyberarm_engine/model_cache.rb +31 -31
  45. data/lib/cyberarm_engine/notification.rb +82 -82
  46. data/lib/cyberarm_engine/notification_manager.rb +241 -241
  47. data/lib/cyberarm_engine/opengl/light.rb +52 -52
  48. data/lib/cyberarm_engine/opengl/orthographic_camera.rb +46 -46
  49. data/lib/cyberarm_engine/opengl/perspective_camera.rb +41 -41
  50. data/lib/cyberarm_engine/opengl/renderer/bounding_box_renderer.rb +249 -249
  51. data/lib/cyberarm_engine/opengl/renderer/g_buffer.rb +167 -167
  52. data/lib/cyberarm_engine/opengl/renderer/opengl_renderer.rb +307 -307
  53. data/lib/cyberarm_engine/opengl/renderer/renderer.rb +33 -33
  54. data/lib/cyberarm_engine/opengl/shader.rb +408 -408
  55. data/lib/cyberarm_engine/opengl/texture.rb +69 -69
  56. data/lib/cyberarm_engine/opengl.rb +53 -53
  57. data/lib/cyberarm_engine/ray.rb +56 -56
  58. data/lib/cyberarm_engine/result.rb +20 -0
  59. data/lib/cyberarm_engine/stats.rb +200 -200
  60. data/lib/cyberarm_engine/text.rb +260 -260
  61. data/lib/cyberarm_engine/timer.rb +23 -23
  62. data/lib/cyberarm_engine/transform.rb +296 -296
  63. data/lib/cyberarm_engine/trees/aabb_node.rb +126 -126
  64. data/lib/cyberarm_engine/trees/aabb_tree.rb +55 -55
  65. data/lib/cyberarm_engine/trees/aabb_tree_debug.rb +29 -29
  66. data/lib/cyberarm_engine/ui/border_canvas.rb +102 -102
  67. data/lib/cyberarm_engine/ui/dsl.rb +142 -142
  68. data/lib/cyberarm_engine/ui/element.rb +662 -662
  69. data/lib/cyberarm_engine/ui/elements/button.rb +100 -100
  70. data/lib/cyberarm_engine/ui/elements/check_box.rb +54 -54
  71. data/lib/cyberarm_engine/ui/elements/container.rb +407 -407
  72. data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -179
  73. data/lib/cyberarm_engine/ui/elements/edit_line.rb +297 -297
  74. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  75. data/lib/cyberarm_engine/ui/elements/image.rb +72 -72
  76. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  77. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  78. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  79. data/lib/cyberarm_engine/ui/elements/progress.rb +93 -93
  80. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  81. data/lib/cyberarm_engine/ui/elements/slider.rb +107 -107
  82. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  83. data/lib/cyberarm_engine/ui/elements/text_block.rb +222 -222
  84. data/lib/cyberarm_engine/ui/elements/toggle_button.rb +67 -67
  85. data/lib/cyberarm_engine/ui/event.rb +54 -54
  86. data/lib/cyberarm_engine/ui/gui_state.rb +326 -326
  87. data/lib/cyberarm_engine/ui/style.rb +61 -61
  88. data/lib/cyberarm_engine/ui/theme.rb +225 -225
  89. data/lib/cyberarm_engine/vector.rb +312 -312
  90. data/lib/cyberarm_engine/version.rb +4 -4
  91. data/lib/cyberarm_engine/window.rb +195 -195
  92. data/lib/cyberarm_engine.rb +77 -76
  93. data/mrbgem.rake +29 -29
  94. metadata +4 -3
@@ -1,131 +1,131 @@
1
- module CyberarmEngine
2
- module Common
3
- ImageBlob = Data.define(:to_blob, :columns, :rows)
4
-
5
- def push_state(klass, options = {})
6
- window.push_state(klass, options)
7
- end
8
-
9
- def current_state
10
- window.current_state
11
- end
12
-
13
- def previous_state(state = nil)
14
- raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
15
-
16
- i = window.states.index(state || self)
17
- window.states[i - 1] unless (i - 1).negative?
18
- end
19
-
20
- def pop_state
21
- window.pop_state
22
- end
23
-
24
- def shift_state
25
- window.shift_state
26
- end
27
-
28
- def show_cursor
29
- window.show_cursor
30
- end
31
-
32
- def show_cursor=(boolean)
33
- window.show_cursor = boolean
34
- end
35
-
36
- def find_element_by_tag(container, tag, list = [])
37
- return unless container
38
-
39
- container.children.each do |child|
40
- list << child if child.style.tag == tag
41
-
42
- find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
43
- end
44
-
45
- list.first
46
- end
47
-
48
- def draw_rect(x, y, width, height, color, z = 0, mode = :default)
49
- Gosu.draw_rect(x, y, width, height, color, z, mode)
50
- end
51
-
52
- def fill(color, z = 0)
53
- draw_rect(0, 0, window.width, window.height, color, z)
54
- end
55
-
56
- def lighten(color, amount = 25)
57
- if color.respond_to?(:alpha)
58
- Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
59
- else
60
- Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
61
- end
62
- end
63
-
64
- def darken(color, amount = 25)
65
- if color.respond_to?(:alpha)
66
- Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
67
- else
68
- Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
69
- end
70
- end
71
-
72
- def opacity(color, ratio = 1.0)
73
- alpha = 255 * ratio
74
-
75
- Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
76
- end
77
-
78
- def get_asset(path, hash, klass, retro = false, tileable = false)
79
- asset = nil
80
- hash.detect do |_asset, instance|
81
- if _asset == path
82
- asset = instance
83
- true
84
- end
85
- end
86
-
87
- unless asset
88
- instance = nil
89
- instance = if klass == Gosu::Image
90
- path_or_blob = path.is_a?(String) ? path : ImageBlob.new(path.to_blob, path.width, path.height)
91
- klass.new(path_or_blob, retro: retro, tileable: tileable)
92
- else
93
- klass.new(path)
94
- end
95
-
96
- hash[path] = instance
97
- asset = instance
98
- end
99
-
100
- asset
101
- end
102
-
103
- def get_image(path, retro: false, tileable: false)
104
- get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
105
- end
106
-
107
- def get_sample(path)
108
- get_asset(path, Window::SAMPLES, Gosu::Sample)
109
- end
110
-
111
- def get_song(path)
112
- get_asset(path, Window::SONGS, Gosu::Song)
113
- end
114
-
115
- def window
116
- CyberarmEngine::Window.instance
117
- end
118
-
119
- def control_down?
120
- Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
121
- end
122
-
123
- def shift_down?
124
- Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
125
- end
126
-
127
- def alt_down?
128
- Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
129
- end
130
- end
131
- end
1
+ module CyberarmEngine
2
+ module Common
3
+ ImageBlob = Data.define(:to_blob, :columns, :rows)
4
+
5
+ def push_state(klass, options = {})
6
+ window.push_state(klass, options)
7
+ end
8
+
9
+ def current_state
10
+ window.current_state
11
+ end
12
+
13
+ def previous_state(state = nil)
14
+ raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
15
+
16
+ i = window.states.index(state || self)
17
+ window.states[i - 1] unless (i - 1).negative?
18
+ end
19
+
20
+ def pop_state
21
+ window.pop_state
22
+ end
23
+
24
+ def shift_state
25
+ window.shift_state
26
+ end
27
+
28
+ def show_cursor
29
+ window.show_cursor
30
+ end
31
+
32
+ def show_cursor=(boolean)
33
+ window.show_cursor = boolean
34
+ end
35
+
36
+ def find_element_by_tag(container, tag, list = [])
37
+ return unless container
38
+
39
+ container.children.each do |child|
40
+ list << child if child.style.tag == tag
41
+
42
+ find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
43
+ end
44
+
45
+ list.first
46
+ end
47
+
48
+ def draw_rect(x, y, width, height, color, z = 0, mode = :default)
49
+ Gosu.draw_rect(x, y, width, height, color, z, mode)
50
+ end
51
+
52
+ def fill(color, z = 0)
53
+ draw_rect(0, 0, window.width, window.height, color, z)
54
+ end
55
+
56
+ def lighten(color, amount = 25)
57
+ if color.respond_to?(:alpha)
58
+ Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
59
+ else
60
+ Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
61
+ end
62
+ end
63
+
64
+ def darken(color, amount = 25)
65
+ if color.respond_to?(:alpha)
66
+ Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
67
+ else
68
+ Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
69
+ end
70
+ end
71
+
72
+ def opacity(color, ratio = 1.0)
73
+ alpha = 255 * ratio
74
+
75
+ Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
76
+ end
77
+
78
+ def get_asset(path, hash, klass, retro = false, tileable = false)
79
+ asset = nil
80
+ hash.detect do |_asset, instance|
81
+ if _asset == path
82
+ asset = instance
83
+ true
84
+ end
85
+ end
86
+
87
+ unless asset
88
+ instance = nil
89
+ instance = if klass == Gosu::Image
90
+ path_or_blob = path.is_a?(String) ? path : ImageBlob.new(path.to_blob, path.width, path.height)
91
+ klass.new(path_or_blob, retro: retro, tileable: tileable)
92
+ else
93
+ klass.new(path)
94
+ end
95
+
96
+ hash[path] = instance
97
+ asset = instance
98
+ end
99
+
100
+ asset
101
+ end
102
+
103
+ def get_image(path, retro: false, tileable: false)
104
+ get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
105
+ end
106
+
107
+ def get_sample(path)
108
+ get_asset(path, Window::SAMPLES, Gosu::Sample)
109
+ end
110
+
111
+ def get_song(path)
112
+ get_asset(path, Window::SONGS, Gosu::Song)
113
+ end
114
+
115
+ def window
116
+ CyberarmEngine::Window.instance
117
+ end
118
+
119
+ def control_down?
120
+ Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
121
+ end
122
+
123
+ def shift_down?
124
+ Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
125
+ end
126
+
127
+ def alt_down?
128
+ Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
129
+ end
130
+ end
131
+ end
@@ -1,46 +1,46 @@
1
- module CyberarmEngine
2
- class ConfigFile
3
- def initialize(file:)
4
- @file = file
5
-
6
- if File.exist?(@file)
7
- deserialize
8
- else
9
- @data = {}
10
- end
11
- end
12
-
13
- def []=(*keys, value)
14
- last_key = keys.last
15
-
16
- if keys.size == 1
17
- hash = @data
18
- else
19
- keys.pop
20
- hash = @data[keys.shift] ||= {}
21
-
22
- keys.each do |key|
23
- hash = hash[key] ||= {}
24
- end
25
- end
26
-
27
- hash[last_key] = value
28
- end
29
-
30
- def get(*keys)
31
- @data.dig(*keys)
32
- end
33
-
34
- def serialize
35
- JSON.dump(@data)
36
- end
37
-
38
- def deserialize
39
- @data = JSON.parse(File.read(@file), symbolize_names: true)
40
- end
41
-
42
- def save!
43
- File.open(@file, "w") { |f| f.write(serialize) }
44
- end
45
- end
46
- end
1
+ module CyberarmEngine
2
+ class ConfigFile
3
+ def initialize(file:)
4
+ @file = file
5
+
6
+ if File.exist?(@file)
7
+ deserialize
8
+ else
9
+ @data = {}
10
+ end
11
+ end
12
+
13
+ def []=(*keys, value)
14
+ last_key = keys.last
15
+
16
+ if keys.size == 1
17
+ hash = @data
18
+ else
19
+ keys.pop
20
+ hash = @data[keys.shift] ||= {}
21
+
22
+ keys.each do |key|
23
+ hash = hash[key] ||= {}
24
+ end
25
+ end
26
+
27
+ hash[last_key] = value
28
+ end
29
+
30
+ def get(*keys)
31
+ @data.dig(*keys)
32
+ end
33
+
34
+ def serialize
35
+ JSON.dump(@data)
36
+ end
37
+
38
+ def deserialize
39
+ @data = JSON.parse(File.read(@file), symbolize_names: true)
40
+ end
41
+
42
+ def save!
43
+ File.open(@file, "w") { |f| f.write(serialize) }
44
+ end
45
+ end
46
+ end
@@ -1,158 +1,158 @@
1
- # frozen_string_literal: true
2
-
3
- module CyberarmEngine
4
- class Console
5
- module Style
6
- def self.error(string)
7
- "<c=ff5555>#{string}</c>"
8
- end
9
-
10
- def self.warn(string)
11
- "<c=ff7700>#{string}</c>"
12
- end
13
-
14
- def self.notice(string)
15
- "<c=55ff55>#{string}</c>"
16
- end
17
-
18
- def self.highlight(string, color = "5555ff")
19
- "<c=#{color}>#{string}</c>"
20
- end
21
- end
22
-
23
- class Command
24
- def self.inherited(subclass)
25
- @list ||= []
26
- @commands ||= []
27
- @list << subclass
28
- end
29
-
30
- def self.setup
31
- @list ||= []
32
- @commands = []
33
- @list.each do |subclass|
34
- cmd = subclass.new
35
- if @commands.detect { |c| c.command == cmd.command }
36
- raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
37
- end
38
-
39
- @commands << cmd
40
- end
41
- end
42
-
43
- def self.use(command, arguments, console)
44
- found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
45
-
46
- if found_command
47
- found_command.handle(arguments, console)
48
- else
49
- console.stdin("Command #{Style.error(command)} not found.")
50
- end
51
- end
52
-
53
- def self.find(command)
54
- @commands.detect { |cmd| cmd.command == command.to_sym }
55
- end
56
-
57
- def self.list_commands
58
- @commands
59
- end
60
-
61
- def initialize
62
- @store = {}
63
- @subcommands = []
64
-
65
- setup
66
- end
67
-
68
- def setup
69
- end
70
-
71
- def subcommand(command, type)
72
- if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
73
- raise "Subcommand '#{command}' for '#{self.command}' already exists!"
74
- end
75
-
76
- @subcommands << SubCommand.new(self, command, type)
77
- end
78
-
79
- def get(key)
80
- @store[key]
81
- end
82
-
83
- def set(key, value)
84
- @store[key] = value
85
- end
86
-
87
- def group
88
- raise NotImplementedError
89
- end
90
-
91
- def command
92
- raise NotImplementedError
93
- end
94
-
95
- def handle(arguments, console)
96
- raise NotImplementedError
97
- end
98
-
99
- def autocomplete(console)
100
- split = console.text_input.text.split(" ")
101
-
102
- if @subcommands.size.positive?
103
- if !console.text_input.text.end_with?(" ") && split.size == 2
104
- list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
105
-
106
- if list.size == 1
107
- console.text_input.text = "#{split.first} #{list.first} "
108
- else
109
- return unless list.size.positive?
110
-
111
- console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
112
- end
113
-
114
- # List available options on subcommand
115
- elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
116
- subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
117
-
118
- if subcommand
119
- if split.size == 2
120
- console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
121
- else
122
- list = console.abbrev_search(subcommand.values, split.last)
123
- if list.size == 1
124
- console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
125
- elsif list.size.positive?
126
- console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
127
- end
128
- end
129
- end
130
-
131
- # List available subcommands if command was entered and has only a space after it
132
- elsif console.text_input.text.end_with?(" ") && split.size == 1
133
- console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
134
- end
135
- end
136
- end
137
-
138
- def handle_subcommand(arguments, console)
139
- if arguments.size.zero?
140
- console.stdin(usage)
141
- return
142
- end
143
- subcommand = arguments.delete_at(0)
144
-
145
- found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
146
- if found_command
147
- found_command.handle(arguments, console)
148
- else
149
- console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
150
- end
151
- end
152
-
153
- def usage
154
- raise NotImplementedError
155
- end
156
- end
157
- end
1
+ # frozen_string_literal: true
2
+
3
+ module CyberarmEngine
4
+ class Console
5
+ module Style
6
+ def self.error(string)
7
+ "<c=ff5555>#{string}</c>"
8
+ end
9
+
10
+ def self.warn(string)
11
+ "<c=ff7700>#{string}</c>"
12
+ end
13
+
14
+ def self.notice(string)
15
+ "<c=55ff55>#{string}</c>"
16
+ end
17
+
18
+ def self.highlight(string, color = "5555ff")
19
+ "<c=#{color}>#{string}</c>"
20
+ end
21
+ end
22
+
23
+ class Command
24
+ def self.inherited(subclass)
25
+ @list ||= []
26
+ @commands ||= []
27
+ @list << subclass
28
+ end
29
+
30
+ def self.setup
31
+ @list ||= []
32
+ @commands = []
33
+ @list.each do |subclass|
34
+ cmd = subclass.new
35
+ if @commands.detect { |c| c.command == cmd.command }
36
+ raise "Command '#{cmd.command}' from '#{cmd.class}' already exists!"
37
+ end
38
+
39
+ @commands << cmd
40
+ end
41
+ end
42
+
43
+ def self.use(command, arguments, console)
44
+ found_command = @commands.detect { |cmd| cmd.command == command.to_sym }
45
+
46
+ if found_command
47
+ found_command.handle(arguments, console)
48
+ else
49
+ console.stdin("Command #{Style.error(command)} not found.")
50
+ end
51
+ end
52
+
53
+ def self.find(command)
54
+ @commands.detect { |cmd| cmd.command == command.to_sym }
55
+ end
56
+
57
+ def self.list_commands
58
+ @commands
59
+ end
60
+
61
+ def initialize
62
+ @store = {}
63
+ @subcommands = []
64
+
65
+ setup
66
+ end
67
+
68
+ def setup
69
+ end
70
+
71
+ def subcommand(command, type)
72
+ if @subcommands.detect { |subcmd| subcmd.command == command.to_sym }
73
+ raise "Subcommand '#{command}' for '#{self.command}' already exists!"
74
+ end
75
+
76
+ @subcommands << SubCommand.new(self, command, type)
77
+ end
78
+
79
+ def get(key)
80
+ @store[key]
81
+ end
82
+
83
+ def set(key, value)
84
+ @store[key] = value
85
+ end
86
+
87
+ def group
88
+ raise NotImplementedError
89
+ end
90
+
91
+ def command
92
+ raise NotImplementedError
93
+ end
94
+
95
+ def handle(arguments, console)
96
+ raise NotImplementedError
97
+ end
98
+
99
+ def autocomplete(console)
100
+ split = console.text_input.text.split(" ")
101
+
102
+ if @subcommands.size.positive?
103
+ if !console.text_input.text.end_with?(" ") && split.size == 2
104
+ list = console.abbrev_search(@subcommands.map { |cmd| cmd.command.to_s }, split.last)
105
+
106
+ if list.size == 1
107
+ console.text_input.text = "#{split.first} #{list.first} "
108
+ else
109
+ return unless list.size.positive?
110
+
111
+ console.stdin(list.map { |cmd| Console::Style.highlight(cmd) }.join(", ").to_s)
112
+ end
113
+
114
+ # List available options on subcommand
115
+ elsif (console.text_input.text.end_with?(" ") && split.size == 2) || !console.text_input.text.end_with?(" ") && split.size == 3
116
+ subcommand = @subcommands.detect { |cmd| cmd.command.to_s == (split[1]) }
117
+
118
+ if subcommand
119
+ if split.size == 2
120
+ console.stdin("Available options: #{subcommand.values.map { |value| Console::Style.highlight(value) }.join(',')}")
121
+ else
122
+ list = console.abbrev_search(subcommand.values, split.last)
123
+ if list.size == 1
124
+ console.text_input.text = "#{split.first} #{split[1]} #{list.first} "
125
+ elsif list.size.positive?
126
+ console.stdin("Available options: #{list.map { |value| Console::Style.highlight(value) }.join(',')}")
127
+ end
128
+ end
129
+ end
130
+
131
+ # List available subcommands if command was entered and has only a space after it
132
+ elsif console.text_input.text.end_with?(" ") && split.size == 1
133
+ console.stdin("Available subcommands: #{@subcommands.map { |cmd| Console::Style.highlight(cmd.command) }.join(', ')}")
134
+ end
135
+ end
136
+ end
137
+
138
+ def handle_subcommand(arguments, console)
139
+ if arguments.size.zero?
140
+ console.stdin(usage)
141
+ return
142
+ end
143
+ subcommand = arguments.delete_at(0)
144
+
145
+ found_command = @subcommands.detect { |cmd| cmd.command == subcommand.to_sym }
146
+ if found_command
147
+ found_command.handle(arguments, console)
148
+ else
149
+ console.stdin("Unknown subcommand #{Style.error(subcommand)} for #{Style.highlight(command)}")
150
+ end
151
+ end
152
+
153
+ def usage
154
+ raise NotImplementedError
155
+ end
156
+ end
157
+ end
158
158
  end