cyberarm_engine 0.24.4 → 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 -404
  71. data/lib/cyberarm_engine/ui/elements/edit_box.rb +179 -179
  72. data/lib/cyberarm_engine/ui/elements/edit_line.rb +297 -297
  73. data/lib/cyberarm_engine/ui/elements/flow.rb +15 -15
  74. data/lib/cyberarm_engine/ui/elements/image.rb +72 -72
  75. data/lib/cyberarm_engine/ui/elements/list_box.rb +79 -79
  76. data/lib/cyberarm_engine/ui/elements/menu.rb +27 -27
  77. data/lib/cyberarm_engine/ui/elements/menu_item.rb +6 -6
  78. data/lib/cyberarm_engine/ui/elements/progress.rb +93 -93
  79. data/lib/cyberarm_engine/ui/elements/radio.rb +6 -6
  80. data/lib/cyberarm_engine/ui/elements/slider.rb +107 -107
  81. data/lib/cyberarm_engine/ui/elements/stack.rb +11 -11
  82. data/lib/cyberarm_engine/ui/elements/text_block.rb +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,128 +1,128 @@
1
- module CyberarmEngine
2
- module Common
3
- def push_state(klass, options = {})
4
- window.push_state(klass, options)
5
- end
6
-
7
- def current_state
8
- window.current_state
9
- end
10
-
11
- def previous_state(state = nil)
12
- raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
13
-
14
- i = window.states.index(state || self)
15
- window.states[i - 1] unless (i - 1).negative?
16
- end
17
-
18
- def pop_state
19
- window.pop_state
20
- end
21
-
22
- def shift_state
23
- window.shift_state
24
- end
25
-
26
- def show_cursor
27
- window.show_cursor
28
- end
29
-
30
- def show_cursor=(boolean)
31
- window.show_cursor = boolean
32
- end
33
-
34
- def find_element_by_tag(container, tag, list = [])
35
- return unless container
36
-
37
- container.children.each do |child|
38
- list << child if child.style.tag == tag
39
-
40
- find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
41
- end
42
-
43
- list.first
44
- end
45
-
46
- def draw_rect(x, y, width, height, color, z = 0, mode = :default)
47
- Gosu.draw_rect(x, y, width, height, color, z, mode)
48
- end
49
-
50
- def fill(color, z = 0)
51
- draw_rect(0, 0, window.width, window.height, color, z)
52
- end
53
-
54
- def lighten(color, amount = 25)
55
- if color.respond_to?(:alpha)
56
- Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
57
- else
58
- Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
59
- end
60
- end
61
-
62
- def darken(color, amount = 25)
63
- if color.respond_to?(:alpha)
64
- Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
65
- else
66
- Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
67
- end
68
- end
69
-
70
- def opacity(color, ratio = 1.0)
71
- alpha = 255 * ratio
72
-
73
- Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
74
- end
75
-
76
- def get_asset(path, hash, klass, retro = false, tileable = false)
77
- asset = nil
78
- hash.detect do |_asset, instance|
79
- if _asset == path
80
- asset = instance
81
- true
82
- end
83
- end
84
-
85
- unless asset
86
- instance = nil
87
- instance = if klass == Gosu::Image
88
- klass.new(path, retro: retro, tileable: tileable)
89
- else
90
- klass.new(path)
91
- end
92
-
93
- hash[path] = instance
94
- asset = instance
95
- end
96
-
97
- asset
98
- end
99
-
100
- def get_image(path, retro: false, tileable: false)
101
- get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
102
- end
103
-
104
- def get_sample(path)
105
- get_asset(path, Window::SAMPLES, Gosu::Sample)
106
- end
107
-
108
- def get_song(path)
109
- get_asset(path, Window::SONGS, Gosu::Song)
110
- end
111
-
112
- def window
113
- CyberarmEngine::Window.instance
114
- end
115
-
116
- def control_down?
117
- Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
118
- end
119
-
120
- def shift_down?
121
- Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
122
- end
123
-
124
- def alt_down?
125
- Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
126
- end
127
- end
128
- end
1
+ module CyberarmEngine
2
+ module Common
3
+ def push_state(klass, options = {})
4
+ window.push_state(klass, options)
5
+ end
6
+
7
+ def current_state
8
+ window.current_state
9
+ end
10
+
11
+ def previous_state(state = nil)
12
+ raise "Only available for CyberarmEngine::GameState and subclasses" unless is_a?(CyberarmEngine::GameState) || state.is_a?(CyberarmEngine::GameState)
13
+
14
+ i = window.states.index(state || self)
15
+ window.states[i - 1] unless (i - 1).negative?
16
+ end
17
+
18
+ def pop_state
19
+ window.pop_state
20
+ end
21
+
22
+ def shift_state
23
+ window.shift_state
24
+ end
25
+
26
+ def show_cursor
27
+ window.show_cursor
28
+ end
29
+
30
+ def show_cursor=(boolean)
31
+ window.show_cursor = boolean
32
+ end
33
+
34
+ def find_element_by_tag(container, tag, list = [])
35
+ return unless container
36
+
37
+ container.children.each do |child|
38
+ list << child if child.style.tag == tag
39
+
40
+ find_element_by_tag(child, tag, list) if child.is_a?(CyberarmEngine::Element::Container)
41
+ end
42
+
43
+ list.first
44
+ end
45
+
46
+ def draw_rect(x, y, width, height, color, z = 0, mode = :default)
47
+ Gosu.draw_rect(x, y, width, height, color, z, mode)
48
+ end
49
+
50
+ def fill(color, z = 0)
51
+ draw_rect(0, 0, window.width, window.height, color, z)
52
+ end
53
+
54
+ def lighten(color, amount = 25)
55
+ if color.respond_to?(:alpha)
56
+ Gosu::Color.rgba(color.red + amount, color.green + amount, color.blue + amount, color.alpha)
57
+ else
58
+ Gosu::Color.rgb(color.red + amount, color.green + amount, color.blue + amount)
59
+ end
60
+ end
61
+
62
+ def darken(color, amount = 25)
63
+ if color.respond_to?(:alpha)
64
+ Gosu::Color.rgba(color.red - amount, color.green - amount, color.blue - amount, color.alpha)
65
+ else
66
+ Gosu::Color.rgb(color.red - amount, color.green - amount, color.blue - amount)
67
+ end
68
+ end
69
+
70
+ def opacity(color, ratio = 1.0)
71
+ alpha = 255 * ratio
72
+
73
+ Gosu::Color.rgba(color.red, color.green, color.blue, alpha)
74
+ end
75
+
76
+ def get_asset(path, hash, klass, retro = false, tileable = false)
77
+ asset = nil
78
+ hash.detect do |_asset, instance|
79
+ if _asset == path
80
+ asset = instance
81
+ true
82
+ end
83
+ end
84
+
85
+ unless asset
86
+ instance = nil
87
+ instance = if klass == Gosu::Image
88
+ klass.new(path, retro: retro, tileable: tileable)
89
+ else
90
+ klass.new(path)
91
+ end
92
+
93
+ hash[path] = instance
94
+ asset = instance
95
+ end
96
+
97
+ asset
98
+ end
99
+
100
+ def get_image(path, retro: false, tileable: false)
101
+ get_asset(path, Window::IMAGES, Gosu::Image, retro, tileable)
102
+ end
103
+
104
+ def get_sample(path)
105
+ get_asset(path, Window::SAMPLES, Gosu::Sample)
106
+ end
107
+
108
+ def get_song(path)
109
+ get_asset(path, Window::SONGS, Gosu::Song)
110
+ end
111
+
112
+ def window
113
+ CyberarmEngine::Window.instance
114
+ end
115
+
116
+ def control_down?
117
+ Gosu.button_down?(Gosu::KB_LEFT_CONTROL) || Gosu.button_down?(Gosu::KB_RIGHT_CONTROL)
118
+ end
119
+
120
+ def shift_down?
121
+ Gosu.button_down?(Gosu::KB_LEFT_SHIFT) || Gosu.button_down?(Gosu::KB_RIGHT_SHIFT)
122
+ end
123
+
124
+ def alt_down?
125
+ Gosu.button_down?(Gosu::KB_LEFT_ALT) || Gosu.button_down?(Gosu::KB_RIGHT_ALT)
126
+ end
127
+ end
128
+ 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