grmenu 0.1.6 → 2.0.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.
- checksums.yaml +4 -4
- data/GRmenu.rb +381 -91
- data/README.md +121 -77
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9321761865c19b47e92b7fb2ed1808a23184d65a65cfef033b6f2129537512f9
|
|
4
|
+
data.tar.gz: f6ff6e9057c2dee31a7af652b74b9dd88eac8b8b13b05638956516ecafdfb2af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27cbe736aba023bf6f5cda2063c5dae971127a485b0b441d6600aa211f2d6734a04917d638e9795bb4106e8a52acdf1f9a3f0f1eebea155949e1999f4ad06b2d
|
|
7
|
+
data.tar.gz: 86c3b3d6de6c6fff5dc4959743c87089173701d52edaaf6613de8381574c954c02ae6a8caab517d24a19f8e78e1882ba35189020b67cd32dca96eefa933ede60
|
data/GRmenu.rb
CHANGED
|
@@ -39,55 +39,55 @@ class GRmenu
|
|
|
39
39
|
"#{code}#{text}#{RESET}"
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def red(s)
|
|
43
|
-
def bright_red(s)
|
|
44
|
-
def dark_red(s)
|
|
42
|
+
def red(s); paint(s, :red, 1); end
|
|
43
|
+
def bright_red(s); paint(s, :red, 2); end
|
|
44
|
+
def dark_red(s); paint(s, :red, 1); end
|
|
45
45
|
|
|
46
|
-
def green(s)
|
|
47
|
-
def bright_green(s)
|
|
48
|
-
def dark_green(s)
|
|
46
|
+
def green(s); paint(s, :green, 1); end
|
|
47
|
+
def bright_green(s); paint(s, :green, 2); end
|
|
48
|
+
def dark_green(s); paint(s, :green, 1); end
|
|
49
49
|
|
|
50
|
-
def yellow(s)
|
|
51
|
-
def bright_yellow(s)
|
|
50
|
+
def yellow(s); paint(s, :yellow, 1); end
|
|
51
|
+
def bright_yellow(s); paint(s, :yellow, 2); end
|
|
52
52
|
|
|
53
|
-
def blue(s)
|
|
54
|
-
def bright_blue(s)
|
|
53
|
+
def blue(s); paint(s, :blue, 1); end
|
|
54
|
+
def bright_blue(s); paint(s, :blue, 2); end
|
|
55
55
|
|
|
56
|
-
def magenta(s)
|
|
57
|
-
def bright_magenta(s)
|
|
56
|
+
def magenta(s); paint(s, :magenta, 1); end
|
|
57
|
+
def bright_magenta(s); paint(s, :magenta, 2); end
|
|
58
58
|
|
|
59
|
-
def purple(s)
|
|
60
|
-
def bright_purple(s)
|
|
59
|
+
def purple(s); paint(s, :purple, 1); end
|
|
60
|
+
def bright_purple(s); paint(s, :purple, 2); end
|
|
61
61
|
|
|
62
|
-
def pink(s)
|
|
63
|
-
def bright_pink(s)
|
|
62
|
+
def pink(s); paint(s, :pink, 1); end
|
|
63
|
+
def bright_pink(s); paint(s, :pink, 2); end
|
|
64
64
|
|
|
65
|
-
def cyan(s)
|
|
66
|
-
def bright_cyan(s)
|
|
65
|
+
def cyan(s); paint(s, :cyan, 1); end
|
|
66
|
+
def bright_cyan(s); paint(s, :cyan, 2); end
|
|
67
67
|
|
|
68
|
-
def aqua(s)
|
|
69
|
-
def bright_aqua(s)
|
|
68
|
+
def aqua(s); paint(s, :aqua, 1); end
|
|
69
|
+
def bright_aqua(s); paint(s, :aqua, 2); end
|
|
70
70
|
|
|
71
|
-
def orange(s)
|
|
72
|
-
def bright_orange(s)
|
|
71
|
+
def orange(s); paint(s, :orange, 1); end
|
|
72
|
+
def bright_orange(s); paint(s, :orange, 2); end
|
|
73
73
|
|
|
74
|
-
def white(s)
|
|
75
|
-
def bright_white(s)
|
|
74
|
+
def white(s); paint(s, :white, 1); end
|
|
75
|
+
def bright_white(s); paint(s, :white, 2); end
|
|
76
76
|
|
|
77
|
-
def black(s)
|
|
78
|
-
def gray(s)
|
|
79
|
-
def bright_gray(s)
|
|
80
|
-
def grey(s)
|
|
77
|
+
def black(s); paint(s, :black, 1); end
|
|
78
|
+
def gray(s); paint(s, :gray, 1); end
|
|
79
|
+
def bright_gray(s); paint(s, :gray, 2); end
|
|
80
|
+
def grey(s); gray(s); end
|
|
81
81
|
|
|
82
|
-
def r(s)
|
|
83
|
-
def dr(s)
|
|
84
|
-
def g(s)
|
|
85
|
-
def y(s)
|
|
86
|
-
def w(s)
|
|
87
|
-
def gr(s)
|
|
88
|
-
def cy(s)
|
|
89
|
-
def mg(s)
|
|
90
|
-
def bl(s)
|
|
82
|
+
def r(s); bright_red(s); end
|
|
83
|
+
def dr(s); dark_red(s); end
|
|
84
|
+
def g(s); bright_green(s); end
|
|
85
|
+
def y(s); bright_yellow(s); end
|
|
86
|
+
def w(s); bright_white(s); end
|
|
87
|
+
def gr(s); gray(s); end
|
|
88
|
+
def cy(s); bright_cyan(s); end
|
|
89
|
+
def mg(s); bright_magenta(s); end
|
|
90
|
+
def bl(s); bright_blue(s); end
|
|
91
91
|
end
|
|
92
92
|
C = Color
|
|
93
93
|
|
|
@@ -162,6 +162,148 @@ class GRmenu
|
|
|
162
162
|
FONTS.each { |id, data| const_set("FONT_#{id}", data) }
|
|
163
163
|
FONT = FONTS[1] || {}.freeze
|
|
164
164
|
|
|
165
|
+
class ProgressBar
|
|
166
|
+
attr_reader :total, :current, :title, :status
|
|
167
|
+
|
|
168
|
+
def initialize(total = 100, title: nil, color: "cyan", level: 2, style: 3, width: nil)
|
|
169
|
+
@total = [total.to_i, 1].max
|
|
170
|
+
@current = 0
|
|
171
|
+
@title = title
|
|
172
|
+
@status = ""
|
|
173
|
+
@color = color.to_s.downcase
|
|
174
|
+
@level = level.to_i
|
|
175
|
+
@style = style.to_i
|
|
176
|
+
@width = width
|
|
177
|
+
@closed = false
|
|
178
|
+
@drawn_lines_count = 0
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def advance(step = 1, status: nil)
|
|
182
|
+
return if @closed
|
|
183
|
+
@current = [(@current + step), @total].min
|
|
184
|
+
@status = status.to_s if status
|
|
185
|
+
render
|
|
186
|
+
end
|
|
187
|
+
alias_method :increment, :advance
|
|
188
|
+
alias_method :step, :advance
|
|
189
|
+
|
|
190
|
+
def set(value, status: nil)
|
|
191
|
+
return if @closed
|
|
192
|
+
@current = [[value.to_i, 0].max, @total].min
|
|
193
|
+
@status = status.to_s if status
|
|
194
|
+
render
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def render
|
|
198
|
+
term_w = GRmenu.terminal_width
|
|
199
|
+
box_w = @width || [term_w - 4, 60].min
|
|
200
|
+
box_w = [box_w, 36].max
|
|
201
|
+
|
|
202
|
+
color_code = GRmenu::COLORS.dig(@color, @level) || "\e[1;96m"
|
|
203
|
+
reset_code = GRmenu::COLORS["reset"]
|
|
204
|
+
border_cfg = GRmenu::BORDERS[@style] || GRmenu::BORDERS[3]
|
|
205
|
+
|
|
206
|
+
v_l = border_cfg[:vl] || border_cfg[:v]
|
|
207
|
+
v_r = border_cfg[:vr] || border_cfg[:v]
|
|
208
|
+
h_t = border_cfg[:ht] || border_cfg[:h]
|
|
209
|
+
h_b = border_cfg[:hb] || border_cfg[:h]
|
|
210
|
+
|
|
211
|
+
top_fill = (h_t * ((box_w - 2).to_f / h_t.length).ceil)[0...(box_w - 2)]
|
|
212
|
+
bot_fill = (h_b * ((box_w - 2).to_f / h_b.length).ceil)[0...(box_w - 2)]
|
|
213
|
+
|
|
214
|
+
pct = ((@current.to_f / @total) * 100).round
|
|
215
|
+
pct_str = "#{pct}% (#{@current}/#{@total})"
|
|
216
|
+
|
|
217
|
+
inner_w = box_w - 4
|
|
218
|
+
bar_w = [inner_w - pct_str.length - 3, 10].max
|
|
219
|
+
filled_len = ((@current.to_f / @total) * bar_w).round
|
|
220
|
+
empty_len = bar_w - filled_len
|
|
221
|
+
|
|
222
|
+
bar_str = "[#{"█" * filled_len}#{"░" * empty_len}] #{pct_str}"
|
|
223
|
+
bar_line = bar_str.ljust(inner_w)[0...inner_w]
|
|
224
|
+
|
|
225
|
+
lines = []
|
|
226
|
+
lines << "#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}"
|
|
227
|
+
if @title && !@title.empty?
|
|
228
|
+
lines << "#{color_code}#{v_l}#{reset_code} #{@title.center(inner_w)} #{color_code}#{v_r}#{reset_code}"
|
|
229
|
+
lines << "#{color_code}#{v_l}#{top_fill}#{v_r}#{reset_code}"
|
|
230
|
+
end
|
|
231
|
+
lines << "#{color_code}#{v_l}#{reset_code} #{color_code}#{bar_line}#{reset_code} #{color_code}#{v_r}#{reset_code}"
|
|
232
|
+
if @status && !@status.empty?
|
|
233
|
+
stat_line = @status.ljust(inner_w)[0...inner_w]
|
|
234
|
+
lines << "#{color_code}#{v_l}#{reset_code} #{Color.gray(stat_line)} #{color_code}#{v_r}#{reset_code}"
|
|
235
|
+
end
|
|
236
|
+
lines << "#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}"
|
|
237
|
+
|
|
238
|
+
frame = lines.join("\r\n") + "\r\n"
|
|
239
|
+
|
|
240
|
+
if @drawn_lines_count && @drawn_lines_count > 0
|
|
241
|
+
Kernel.print("\e[#{@drawn_lines_count}A\e[J")
|
|
242
|
+
end
|
|
243
|
+
Kernel.print(frame)
|
|
244
|
+
$stdout.flush
|
|
245
|
+
@drawn_lines_count = lines.length
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def finish(status: "¡Completado!")
|
|
249
|
+
return if @closed
|
|
250
|
+
set(@total, status: status)
|
|
251
|
+
@closed = true
|
|
252
|
+
Kernel.print(GRmenu::SHOW_CURSOR)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def self.spinner(message = "Cargando...", color: "cyan", level: 2, delay: 0.08, &block)
|
|
257
|
+
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
258
|
+
color_name = color.to_s.downcase
|
|
259
|
+
color_code = COLORS.dig(color_name, level) || "\e[1;96m"
|
|
260
|
+
reset_code = COLORS["reset"]
|
|
261
|
+
|
|
262
|
+
stop_spinner = false
|
|
263
|
+
spinner_thread = Thread.new do
|
|
264
|
+
frame_idx = 0
|
|
265
|
+
while !stop_spinner
|
|
266
|
+
f = frames[frame_idx % frames.length]
|
|
267
|
+
Kernel.print("\r\e[K#{color_code}#{f}#{reset_code} #{message}")
|
|
268
|
+
$stdout.flush
|
|
269
|
+
frame_idx += 1
|
|
270
|
+
sleep(delay)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
begin
|
|
275
|
+
Kernel.print(HIDE_CURSOR)
|
|
276
|
+
result = block ? block.call : nil
|
|
277
|
+
stop_spinner = true
|
|
278
|
+
spinner_thread.join
|
|
279
|
+
success_color = COLORS.dig("green", 2) || "\e[1;92m"
|
|
280
|
+
Kernel.print("\r\e[K#{success_color}✔#{reset_code} #{message} #{Color.gray("¡Listo!")}\r\n")
|
|
281
|
+
result
|
|
282
|
+
rescue Exception => e
|
|
283
|
+
stop_spinner = true
|
|
284
|
+
spinner_thread.join rescue nil
|
|
285
|
+
error_color = COLORS.dig("red", 2) || "\e[1;91m"
|
|
286
|
+
Kernel.print("\r\e[K#{error_color}✖#{reset_code} #{message} #{Color.bright_red("(Error: #{e.message})")}\r\n")
|
|
287
|
+
raise e
|
|
288
|
+
ensure
|
|
289
|
+
stop_spinner = true
|
|
290
|
+
Kernel.print(SHOW_CURSOR)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def self.progress(total = 100, title: nil, color: "cyan", level: 2, style: 3, width: nil, &block)
|
|
295
|
+
bar = ProgressBar.new(total, title: title, color: color, level: level, style: style, width: width)
|
|
296
|
+
Kernel.print(HIDE_CURSOR)
|
|
297
|
+
bar.render
|
|
298
|
+
begin
|
|
299
|
+
result = block ? block.call(bar) : bar
|
|
300
|
+
bar.finish
|
|
301
|
+
result
|
|
302
|
+
ensure
|
|
303
|
+
Kernel.print(SHOW_CURSOR)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
165
307
|
class SetStyle
|
|
166
308
|
def initialize(
|
|
167
309
|
border: { color: "cyan", level: 1 },
|
|
@@ -333,7 +475,7 @@ class GRmenu
|
|
|
333
475
|
end
|
|
334
476
|
end
|
|
335
477
|
|
|
336
|
-
attr_accessor :functions, :title, :subtitle, :banner, :banner_style, :divider, :style, :index, :style_config, :center
|
|
478
|
+
attr_accessor :functions, :title, :subtitle, :banner, :banner_style, :divider, :style, :index, :style_config, :center, :page_size
|
|
337
479
|
|
|
338
480
|
alias_method :options, :functions
|
|
339
481
|
alias_method :options=, :functions=
|
|
@@ -344,11 +486,11 @@ class GRmenu
|
|
|
344
486
|
alias_method :description, :subtitle
|
|
345
487
|
alias_method :description=, :subtitle=
|
|
346
488
|
|
|
347
|
-
def self.STYLES
|
|
348
|
-
def self.COLORS
|
|
349
|
-
def self.BORDERS
|
|
350
|
-
def self.FONTS
|
|
351
|
-
def self.FONT
|
|
489
|
+
def self.STYLES; STYLES; end
|
|
490
|
+
def self.COLORS; COLORS; end
|
|
491
|
+
def self.BORDERS; BORDERS; end
|
|
492
|
+
def self.FONTS; FONTS; end
|
|
493
|
+
def self.FONT; FONT_1; end
|
|
352
494
|
|
|
353
495
|
def self.terminal_width
|
|
354
496
|
cols = $stdout.winsize[1] rescue nil
|
|
@@ -358,6 +500,14 @@ class GRmenu
|
|
|
358
500
|
80
|
|
359
501
|
end
|
|
360
502
|
|
|
503
|
+
def self.terminal_height
|
|
504
|
+
rows = $stdout.winsize[0] rescue nil
|
|
505
|
+
rows = $stdin.winsize[0] rescue nil if rows.nil? || rows <= 0
|
|
506
|
+
(rows && rows > 0) ? rows : (ENV['LINES'] ? ENV['LINES'].to_i : 24)
|
|
507
|
+
rescue StandardError
|
|
508
|
+
24
|
|
509
|
+
end
|
|
510
|
+
|
|
361
511
|
def self.clear_screen
|
|
362
512
|
Kernel.print(CLEAR_SCREEN_SEQUENCE)
|
|
363
513
|
end
|
|
@@ -371,20 +521,20 @@ class GRmenu
|
|
|
371
521
|
Kernel.print("#{color_code}#{char * width}#{COLORS['reset']}\r\n")
|
|
372
522
|
end
|
|
373
523
|
|
|
374
|
-
def self.help
|
|
375
|
-
w = [terminal_width - 4,
|
|
376
|
-
w = [w,
|
|
524
|
+
def self.help(section = :all)
|
|
525
|
+
w = [terminal_width - 4, 70].min
|
|
526
|
+
w = [w, 46].max
|
|
377
527
|
inner_w = w - 2
|
|
378
528
|
h_line = "═" * inner_w
|
|
379
529
|
s_line = "─" * w
|
|
380
530
|
|
|
381
531
|
Kernel.print "\r\n"
|
|
382
532
|
Kernel.print "#{Color.bright_cyan("╔" + h_line + "╗")}\r\n"
|
|
383
|
-
Kernel.print "#{Color.bright_cyan("║")}#{Color.bright_yellow("GRmenu - Guia y Referencia
|
|
384
|
-
Kernel.print "#{Color.bright_cyan("║")}#{Color.gray("
|
|
533
|
+
Kernel.print "#{Color.bright_cyan("║")}#{Color.bright_yellow("GRmenu - Guia y Referencia Completa (v2.0)".center(inner_w))}#{Color.bright_cyan("║")}\r\n"
|
|
534
|
+
Kernel.print "#{Color.bright_cyan("║")}#{Color.gray("Menus interactivos, Banners 3D, Barras de Progreso y TTY".center(inner_w))}#{Color.bright_cyan("║")}\r\n"
|
|
385
535
|
Kernel.print "#{Color.bright_cyan("╚" + h_line + "╝")}\r\n\r\n"
|
|
386
536
|
|
|
387
|
-
Kernel.print "#{Color.bright_magenta("[1] HELPERS NATIVOS
|
|
537
|
+
Kernel.print "#{Color.bright_magenta("[1] HELPERS NATIVOS")}\r\n"
|
|
388
538
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
389
539
|
Kernel.print " #{Color.bright_green("GRmenu.clear_screen")} #{Color.gray("(o GRmenu.clr)")}\r\n"
|
|
390
540
|
Kernel.print " * Limpia la terminal al instante con secuencias ANSI.\r\n"
|
|
@@ -392,18 +542,55 @@ class GRmenu
|
|
|
392
542
|
Kernel.print " * Pausa interactiva: espera una sola tecla en modo TTY crudo.\r\n"
|
|
393
543
|
Kernel.print " #{Color.bright_green("GRmenu.banner(texto, delay, color:, level:, style:, font:)")}\r\n"
|
|
394
544
|
Kernel.print " * Renderiza banner ASCII 3D con marco y animacion opcional.\r\n"
|
|
545
|
+
Kernel.print " #{Color.bright_green("GRmenu.spinner(mensaje, color:, level:, delay:, &bloque)")}\r\n"
|
|
546
|
+
Kernel.print " * Animacion giratoria fluida para tareas de tiempo desconocido.\r\n"
|
|
547
|
+
Kernel.print " * Ejemplo: #{Color.bright_white("GRmenu.spinner(\"Conectando...\") { conectar_db }")}\r\n"
|
|
548
|
+
Kernel.print " #{Color.bright_green("GRmenu.progress(total, title:, color:, level:, style:, width:, &bloque)")}\r\n"
|
|
549
|
+
Kernel.print " * Barra de progreso porcentual dentro de un recuadro estilizado.\r\n"
|
|
550
|
+
Kernel.print " * El bloque recibe 'bar'. Metodos disponibles:\r\n"
|
|
551
|
+
Kernel.print " - #{Color.cyan("bar.advance(n, status: \"...\")")} -> Avanza n pasos (alias: increment, step).\r\n"
|
|
552
|
+
Kernel.print " - #{Color.cyan("bar.set(valor, status: \"...\")")} -> Fija el valor exacto actual.\r\n"
|
|
553
|
+
Kernel.print " - #{Color.cyan("bar.finish(status: \"...\")")} -> Finaliza la barra al 100%.\r\n"
|
|
554
|
+
Kernel.print " * Ejemplo: #{Color.bright_white("GRmenu.progress(10, title: \"Copia\") { |b| 10.times { b.advance(1) } }")}\r\n"
|
|
395
555
|
Kernel.print " #{Color.bright_green("GRmenu.div(longitud, color, level, char)")}\r\n"
|
|
396
|
-
Kernel.print " * Dibuja linea divisoria horizontal adaptable a la consola.\r\n"
|
|
397
|
-
Kernel.print " #{Color.bright_green("GRmenu.help")}\r\n"
|
|
398
|
-
Kernel.print " * Imprime esta guia visual interactiva en consola.\r\n\r\n"
|
|
556
|
+
Kernel.print " * Dibuja linea divisoria horizontal adaptable a la consola.\r\n\r\n"
|
|
399
557
|
|
|
400
|
-
Kernel.print "#{Color.bright_magenta("[2]
|
|
558
|
+
Kernel.print "#{Color.bright_magenta("[2] FORMATOS DE OPCIONES Y TOOLTIPS DINAMICOS")}\r\n"
|
|
559
|
+
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
560
|
+
Kernel.print " #{Color.cyan("1. Metodo directo:")} #{Color.bright_white("method(:iniciar)")} #{Color.gray("(auto-capitaliza nombre)")}\r\n"
|
|
561
|
+
Kernel.print " #{Color.cyan("2. Simbolo:")} #{Color.bright_white(":iniciar")}\r\n"
|
|
562
|
+
Kernel.print " #{Color.cyan("3. Nombre propio:")} #{Color.bright_white("[\"Mi Accion\", method(:iniciar)]")}\r\n"
|
|
563
|
+
Kernel.print " #{Color.cyan("4. Con Tooltip/Info:")} #{Color.bright_white("[\"Mi Accion\", method(:iniciar), \"Descripcion que sale abajo\"]")}\r\n"
|
|
564
|
+
Kernel.print " #{Color.cyan("5. Lambda / Proc:")} #{Color.bright_white("[\"Test\", -> { puts \"Hola\" }, \"Tooltip opcional\"]")}\r\n"
|
|
565
|
+
Kernel.print " #{Color.cyan("6. Hash:")} #{Color.bright_white("{ name: \"Test\", action: method(:iniciar), desc: \"Info\" }")}\r\n\r\n"
|
|
566
|
+
|
|
567
|
+
Kernel.print "#{Color.bright_magenta("[3] PARAMETROS DE GRmenu.new(functions, ...)")}\r\n"
|
|
568
|
+
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
569
|
+
Kernel.print " #{Color.bright_green("functions:")} #{Color.white("Array")} -> Lista de opciones (metodos, simbolos, arreglos, lambdas).\r\n"
|
|
570
|
+
Kernel.print " #{Color.bright_green("banner:")} #{Color.white("String")} -> Texto grande a renderizar en arte ASCII 3D.\r\n"
|
|
571
|
+
Kernel.print " #{Color.bright_green("title:")} #{Color.white("String")} -> Titulo en el encabezado del recuadro.\r\n"
|
|
572
|
+
Kernel.print " #{Color.bright_green("subtitle:")} #{Color.white("String")} -> Subtitulo / descripcion (soporta \\n).\r\n"
|
|
573
|
+
Kernel.print " #{Color.bright_green("page_size:")} #{Color.white("Integer")} -> Limite visible para scroll y paginacion automatica.\r\n"
|
|
574
|
+
Kernel.print " #{Color.bright_green("font:")} #{Color.white("Integer")} -> Fuente ASCII del banner (1 al 10, default 1).\r\n"
|
|
575
|
+
Kernel.print " #{Color.bright_green("style:")} #{Color.white("Integer")} -> Estilo de marco de opciones (1 al 20, default 19).\r\n"
|
|
576
|
+
Kernel.print " #{Color.bright_green("banner_style:")} #{Color.white("Integer")} -> Estilo de marco del banner (1 al 20, default 3).\r\n"
|
|
577
|
+
Kernel.print " #{Color.bright_green("divider:")} #{Color.white("Boolean")} -> Divisores alineados al banner (true/false).\r\n"
|
|
578
|
+
Kernel.print " #{Color.bright_green("center:")} #{Color.white("Boolean")} -> Centrado simetrico de subtitulo y menu (default true).\r\n\r\n"
|
|
579
|
+
|
|
580
|
+
Kernel.print "#{Color.bright_magenta("[4] AUTO-PAGINACION Y SCROLL")}\r\n"
|
|
581
|
+
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
582
|
+
Kernel.print " * #{Color.white("100% Automatica:")} Si la lista tiene muchas opciones o la pantalla es pequena,\r\n"
|
|
583
|
+
Kernel.print " GRmenu calcula el espacio disponible y genera una ventana deslizante suave.\r\n"
|
|
584
|
+
Kernel.print " * Indicadores visuales: #{Color.bright_yellow("▲ (+N arriba)")} y #{Color.bright_yellow("▼ (+M abajo)")}.\r\n"
|
|
585
|
+
Kernel.print " * Opcional: fija el limite con #{Color.bright_white("page_size: 8")} al instanciar #{Color.bright_green("GRmenu.new")}.\r\n\r\n"
|
|
586
|
+
|
|
587
|
+
Kernel.print "#{Color.bright_magenta("[5] MODULO DE COLORES (Color / C)")}\r\n"
|
|
401
588
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
402
589
|
Kernel.print " #{Color.cyan("Uso directo: ")}#{Color.bright_white("puts Color.green(\"Texto\")")} | #{Color.bright_white("puts Color.bright_cyan(\"Texto\")")}\r\n"
|
|
403
590
|
Kernel.print " #{Color.cyan("Paleta: ")}#{Color.red("red")}, #{Color.green("green")}, #{Color.yellow("yellow")}, #{Color.blue("blue")}, #{Color.magenta("magenta")}, #{Color.purple("purple")}, #{Color.pink("pink")}, #{Color.cyan("cyan")}, #{Color.aqua("aqua")}, #{Color.orange("orange")}, #{Color.white("white")}, #{Color.gray("gray")}, #{Color.black("black")}.\r\n"
|
|
404
591
|
Kernel.print " #{Color.cyan("Brillo: ")}#{Color.white("1")} = Normal, #{Color.bright_white("2")} = Brillante / Bold.\r\n\r\n"
|
|
405
592
|
|
|
406
|
-
Kernel.print "#{Color.bright_magenta("[
|
|
593
|
+
Kernel.print "#{Color.bright_magenta("[6] FUENTES ASCII 3D DEL BANNER (font: 1 al 10)")}\r\n"
|
|
407
594
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
408
595
|
Kernel.print " #{Color.yellow("1")} -> #{Color.bright_white("ANSI Shadow 3D (Default)")} #{Color.cyan("[██████╗ ██╗ ██╗]")}\r\n"
|
|
409
596
|
Kernel.print " #{Color.yellow("2")} -> #{Color.bright_white("Slant 3D (FIGlet)")} #{Color.cyan("[ ____ __ __]")}\r\n"
|
|
@@ -416,28 +603,17 @@ class GRmenu
|
|
|
416
603
|
Kernel.print " #{Color.yellow("9")} -> #{Color.bright_white("Solid Fat 3D Block")} #{Color.cyan("[ ██████▄ ██ ██]")}\r\n"
|
|
417
604
|
Kernel.print " #{Color.yellow("10")}-> #{Color.bright_white("Arcade Stars Matrix")} #{Color.cyan("[ ★★★★ ★ ★]")}\r\n\r\n"
|
|
418
605
|
|
|
419
|
-
Kernel.print "#{Color.bright_magenta("[
|
|
606
|
+
Kernel.print "#{Color.bright_magenta("[7] ESTILOS DE MARCO (style / banner_style: 1 al 20)")}\r\n"
|
|
420
607
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
421
608
|
Kernel.print " #{Color.yellow("3")} -> #{Color.bright_white("Doble linea")} #{Color.cyan("╔═══╗ ║ ║ ╚═══╝")} (Default en Banner)\r\n"
|
|
422
609
|
Kernel.print " #{Color.yellow("7")} -> #{Color.bright_white("Curvas redondeadas")} #{Color.cyan("╭───╮ │ │ ╰───╯")}\r\n"
|
|
423
610
|
Kernel.print " #{Color.yellow("4")} -> #{Color.bright_white("Linea gruesa")} #{Color.cyan("┏━━━┓ ┃ ┃ ┗━━━┛")}\r\n"
|
|
424
611
|
Kernel.print " #{Color.yellow("2")} -> #{Color.bright_white("Linea simple")} #{Color.cyan("┌───┐ │ │ └───┘")}\r\n"
|
|
425
|
-
Kernel.print " #{Color.yellow("8")} -> #{Color.bright_white("Bloques outline")} #{Color.cyan("▛▀▀▀▜ ▌
|
|
612
|
+
Kernel.print " #{Color.yellow("8")} -> #{Color.bright_white("Bloques outline")} #{Color.cyan("▛▀▀▀▜ ▌ ▐ ▙▄▄▄▟")}\r\n"
|
|
426
613
|
Kernel.print " #{Color.yellow("19")} -> #{Color.bright_white("Circulos")} #{Color.cyan("●○○○● ● ● ●○○○●")} (Default en Opciones)\r\n"
|
|
427
614
|
Kernel.print " #{Color.yellow("20")} -> #{Color.bright_white("Estrellas")} #{Color.cyan("★☆☆☆★ ★ ★ ★☆☆☆★")}\r\n\r\n"
|
|
428
615
|
|
|
429
|
-
Kernel.print "#{Color.bright_magenta("[
|
|
430
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
431
|
-
Kernel.print " #{Color.bright_green("banner:")} #{Color.white("String")} -> Texto grande a renderizar en arte ASCII 3D.\r\n"
|
|
432
|
-
Kernel.print " #{Color.bright_green("title:")} #{Color.white("String")} -> Titulo en el encabezado del recuadro.\r\n"
|
|
433
|
-
Kernel.print " #{Color.bright_green("subtitle:")} #{Color.white("String")} -> Subtitulo / descripcion (soporta \\n).\r\n"
|
|
434
|
-
Kernel.print " #{Color.bright_green("font:")} #{Color.white("Integer")} -> Fuente ASCII del banner (1 al 10, default 1).\r\n"
|
|
435
|
-
Kernel.print " #{Color.bright_green("style:")} #{Color.white("Integer")} -> Estilo de marco de opciones (1 al 20, default 19).\r\n"
|
|
436
|
-
Kernel.print " #{Color.bright_green("banner_style:")} #{Color.white("Integer")} -> Estilo de marco del banner (1 al 20, default 3).\r\n"
|
|
437
|
-
Kernel.print " #{Color.bright_green("divider:")} #{Color.white("Boolean")} -> Divisores alineados al banner (true/false).\r\n"
|
|
438
|
-
Kernel.print " #{Color.bright_green("center:")} #{Color.white("Boolean")} -> Centrado simetrico de subtitulo y menu (default true).\r\n\r\n"
|
|
439
|
-
|
|
440
|
-
Kernel.print "#{Color.bright_magenta("[6] METODOS DE CONFIGURACION (menu.set_style)")}\r\n"
|
|
616
|
+
Kernel.print "#{Color.bright_magenta("[8] METODOS DE CONFIGURACION (menu.set_style)")}\r\n"
|
|
441
617
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
442
618
|
Kernel.print " #{Color.cyan("menu.set_style.font(id)")} -> Cambia fuente ASCII (1..10)\r\n"
|
|
443
619
|
Kernel.print " #{Color.cyan("menu.set_style.banner(color, level)")} -> Color y brillo del banner ASCII\r\n"
|
|
@@ -448,7 +624,7 @@ class GRmenu
|
|
|
448
624
|
Kernel.print " #{Color.cyan("menu.set_style.options(color, level)")} -> Color y brillo de opciones no activas\r\n"
|
|
449
625
|
Kernel.print " #{Color.cyan("menu.set_style.focus(color, level)")} -> Color y brillo de la opcion resaltada\r\n\r\n"
|
|
450
626
|
|
|
451
|
-
Kernel.print "#{Color.bright_magenta("[
|
|
627
|
+
Kernel.print "#{Color.bright_magenta("[9] EJECUCION (menu.draw)")}\r\n"
|
|
452
628
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
453
629
|
Kernel.print " #{Color.bright_white("menu.draw(size_max: 38)")} -> Inicia el menu interactivo con ancho minimo.\r\n"
|
|
454
630
|
Kernel.print "#{Color.bright_blue(s_line)}\r\n\r\n"
|
|
@@ -499,25 +675,32 @@ class GRmenu
|
|
|
499
675
|
|
|
500
676
|
ascii_rows = build_ascii_lines(text, cols, font)
|
|
501
677
|
border_cfg = BORDERS[style] || BORDERS[3]
|
|
678
|
+
h_top = border_cfg[:ht] || border_cfg[:h]
|
|
679
|
+
h_bot = border_cfg[:hb] || border_cfg[:h]
|
|
680
|
+
v_l = border_cfg[:vl] || border_cfg[:v]
|
|
681
|
+
v_r = border_cfg[:vr] || border_cfg[:v]
|
|
502
682
|
|
|
503
683
|
if ascii_rows
|
|
504
684
|
max_len = ascii_rows.map(&:length).max
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
685
|
+
top_fill = (h_top * ((max_len + 4).to_f / h_top.length).ceil)[0...(max_len + 4)]
|
|
686
|
+
bot_fill = (h_bot * ((max_len + 4).to_f / h_bot.length).ceil)[0...(max_len + 4)]
|
|
687
|
+
|
|
688
|
+
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
508
689
|
ascii_rows.each do |line|
|
|
509
690
|
pad = " " * (max_len - line.length)
|
|
510
|
-
Kernel.print("#{color_code}#{
|
|
691
|
+
Kernel.print("#{color_code}#{v_l} #{line}#{pad} #{v_r}#{reset_code}\r\n")
|
|
511
692
|
sleep(delay) if delay > 0
|
|
512
693
|
end
|
|
513
|
-
Kernel.print("#{color_code}#{border_cfg[:bl]}#{
|
|
694
|
+
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
514
695
|
else
|
|
515
696
|
clean_t = text.to_s.strip
|
|
516
697
|
box_w = [clean_t.length + 6, cols - 2].min
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
Kernel.print("#{color_code}#{border_cfg[:
|
|
698
|
+
top_fill = (h_top * ((box_w - 2).to_f / h_top.length).ceil)[0...(box_w - 2)]
|
|
699
|
+
bot_fill = (h_bot * ((box_w - 2).to_f / h_b.length).ceil)[0...(box_w - 2)]
|
|
700
|
+
|
|
701
|
+
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
702
|
+
Kernel.print("#{color_code}#{v_l} #{clean_t.center(box_w - 4)} #{v_r}#{reset_code}\r\n")
|
|
703
|
+
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
521
704
|
end
|
|
522
705
|
end
|
|
523
706
|
|
|
@@ -526,7 +709,7 @@ class GRmenu
|
|
|
526
709
|
alias_method :logo, :banner
|
|
527
710
|
end
|
|
528
711
|
|
|
529
|
-
def initialize(functions, *positional_arguments, title: nil, banner: nil, subtitle: nil, description: nil, divider: nil, style: nil, banner_style: nil, center: true, font: nil, **keyword_arguments)
|
|
712
|
+
def initialize(functions, *positional_arguments, title: nil, banner: nil, subtitle: nil, description: nil, divider: nil, style: nil, banner_style: nil, center: true, font: nil, page_size: nil, **keyword_arguments)
|
|
530
713
|
@functions = functions.is_a?(Array) ? functions : Array(functions)
|
|
531
714
|
|
|
532
715
|
pos_title = positional_arguments[0]
|
|
@@ -539,8 +722,8 @@ class GRmenu
|
|
|
539
722
|
@style = (style || pos_style || keyword_arguments[:style] || 19).to_i
|
|
540
723
|
@banner_style = (banner_style || keyword_arguments[:banner_style] || 3).to_i
|
|
541
724
|
@center = center.nil? ? true : center
|
|
725
|
+
@page_size = (page_size || keyword_arguments[:page_size])&.to_i
|
|
542
726
|
@index = 0
|
|
543
|
-
@clear_seq = CLEAR_SCREEN_SEQUENCE
|
|
544
727
|
|
|
545
728
|
init_font = font || keyword_arguments[:font_style] || SetStyle.font || 1
|
|
546
729
|
|
|
@@ -598,13 +781,13 @@ class GRmenu
|
|
|
598
781
|
banner_border = BORDERS[@banner_style] || BORDERS[3]
|
|
599
782
|
banner_color_cfg = @style_config.banner
|
|
600
783
|
|
|
601
|
-
lines = []
|
|
602
|
-
box_w = 0
|
|
603
784
|
h_top = banner_border[:ht] || banner_border[:h]
|
|
604
785
|
h_bot = banner_border[:hb] || banner_border[:h]
|
|
605
786
|
v_l = banner_border[:vl] || banner_border[:v]
|
|
606
787
|
v_r = banner_border[:vr] || banner_border[:v]
|
|
607
788
|
|
|
789
|
+
lines = []
|
|
790
|
+
box_w = 0
|
|
608
791
|
if ascii_rows
|
|
609
792
|
content_w = ascii_rows.map(&:length).max
|
|
610
793
|
box_w = content_w + 6
|
|
@@ -632,40 +815,53 @@ class GRmenu
|
|
|
632
815
|
|
|
633
816
|
def render_lines(size_max = 20)
|
|
634
817
|
term_cols = self.class.terminal_width
|
|
818
|
+
term_rows = self.class.terminal_height
|
|
635
819
|
rendered_lines = []
|
|
636
820
|
|
|
637
821
|
banner_box_width = 0
|
|
822
|
+
banner_lines_count = 0
|
|
638
823
|
if @banner && !@banner.empty?
|
|
639
824
|
banner_lines, banner_box_width = render_banner_lines(term_cols)
|
|
640
825
|
rendered_lines.concat(banner_lines)
|
|
641
826
|
rendered_lines << ""
|
|
827
|
+
banner_lines_count = banner_lines.length + 1
|
|
642
828
|
end
|
|
643
829
|
|
|
644
|
-
|
|
830
|
+
all_names = @functions.map { |func| extract_name_from_action(func) }
|
|
831
|
+
all_descriptions = @functions.map { |func| extract_description_from_action(func) }
|
|
832
|
+
|
|
833
|
+
active_desc = all_descriptions[@index] || ""
|
|
834
|
+
|
|
645
835
|
calculated_width = [size_max, @title.length + 4].max
|
|
646
|
-
calculated_width = ([calculated_width] +
|
|
836
|
+
calculated_width = ([calculated_width] + all_names.map { |name| name.length + 6 }).max
|
|
837
|
+
calculated_width = ([calculated_width, active_desc.length + 8].max) unless active_desc.empty?
|
|
647
838
|
total_width = [calculated_width, term_cols - 2].min
|
|
648
839
|
|
|
649
840
|
reference_width = banner_box_width > 0 ? banner_box_width : total_width
|
|
650
841
|
margin_left = (@center && reference_width > total_width) ? " " * ((reference_width - total_width) / 2) : ""
|
|
651
842
|
|
|
843
|
+
subtitle_lines_count = 0
|
|
652
844
|
if @subtitle && !@subtitle.empty?
|
|
653
845
|
subtitle_lines = @subtitle.lines.map(&:chomp)
|
|
654
846
|
div_w = @divider.is_a?(Numeric) ? @divider.to_i : [reference_width, term_cols - 2].min
|
|
655
847
|
|
|
656
848
|
if @divider
|
|
657
849
|
rendered_lines << colorize("─" * div_w, @style_config.divider)
|
|
850
|
+
subtitle_lines_count += 1
|
|
658
851
|
end
|
|
659
852
|
|
|
660
853
|
subtitle_lines.each do |sub_line|
|
|
661
854
|
formatted_sub = @center ? sub_line.center(div_w) : sub_line
|
|
662
855
|
rendered_lines << colorize(formatted_sub, @style_config.subtitle)
|
|
856
|
+
subtitle_lines_count += 1
|
|
663
857
|
end
|
|
664
858
|
|
|
665
859
|
if @divider
|
|
666
860
|
rendered_lines << colorize("─" * div_w, @style_config.divider)
|
|
861
|
+
subtitle_lines_count += 1
|
|
667
862
|
end
|
|
668
863
|
rendered_lines << ""
|
|
864
|
+
subtitle_lines_count += 1
|
|
669
865
|
end
|
|
670
866
|
|
|
671
867
|
border_color_cfg = @style_config.border
|
|
@@ -675,6 +871,33 @@ class GRmenu
|
|
|
675
871
|
|
|
676
872
|
box_border = BORDERS[@style]
|
|
677
873
|
|
|
874
|
+
total_items = @functions.length
|
|
875
|
+
overhead = banner_lines_count + subtitle_lines_count + 6
|
|
876
|
+
overhead += 2 unless active_desc.empty?
|
|
877
|
+
available_rows = [term_rows - overhead - 2, 3].max
|
|
878
|
+
|
|
879
|
+
effective_page_size = if @page_size && @page_size > 0
|
|
880
|
+
[@page_size, total_items].min
|
|
881
|
+
elsif total_items > available_rows
|
|
882
|
+
available_rows
|
|
883
|
+
else
|
|
884
|
+
total_items
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
start_idx = 0
|
|
888
|
+
end_idx = total_items - 1
|
|
889
|
+
if total_items > effective_page_size
|
|
890
|
+
half = effective_page_size / 2
|
|
891
|
+
start_idx = [[@index - half, 0].max, total_items - effective_page_size].min
|
|
892
|
+
end_idx = start_idx + effective_page_size - 1
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
visible_indices = (start_idx..end_idx).to_a
|
|
896
|
+
has_more_above = start_idx > 0
|
|
897
|
+
has_more_below = end_idx < (total_items - 1)
|
|
898
|
+
|
|
899
|
+
avail_w = [total_width - 6, 1].max
|
|
900
|
+
|
|
678
901
|
if box_border
|
|
679
902
|
h_top = box_border[:ht] || box_border[:h]
|
|
680
903
|
h_bot = box_border[:hb] || box_border[:h]
|
|
@@ -699,8 +922,13 @@ class GRmenu
|
|
|
699
922
|
rendered_lines << "#{margin_left}#{colorize(separator_line, border_color_cfg)}"
|
|
700
923
|
end
|
|
701
924
|
|
|
702
|
-
|
|
703
|
-
|
|
925
|
+
if has_more_above
|
|
926
|
+
up_indicator = colorize("▲ (+#{start_idx} arriba)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
927
|
+
rendered_lines << "#{margin_left}#{v_left} #{up_indicator} #{v_right}"
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
visible_indices.each do |current_index|
|
|
931
|
+
option_name = all_names[current_index]
|
|
704
932
|
if @index == current_index
|
|
705
933
|
highlighted_text = colorize("> #{option_name.ljust(avail_w)}", focus_color_cfg)
|
|
706
934
|
rendered_lines << "#{margin_left}#{v_left} #{highlighted_text} #{v_right}"
|
|
@@ -710,6 +938,19 @@ class GRmenu
|
|
|
710
938
|
end
|
|
711
939
|
end
|
|
712
940
|
|
|
941
|
+
if has_more_below
|
|
942
|
+
remaining_below = total_items - 1 - end_idx
|
|
943
|
+
down_indicator = colorize("▼ (+#{remaining_below} abajo)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
944
|
+
rendered_lines << "#{margin_left}#{v_left} #{down_indicator} #{v_right}"
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
unless active_desc.empty?
|
|
948
|
+
separator_line = v_l_raw + mid_fill + v_r_raw
|
|
949
|
+
rendered_lines << "#{margin_left}#{colorize(separator_line, border_color_cfg)}"
|
|
950
|
+
desc_text = colorize("ℹ #{active_desc.ljust(avail_w)}", { color: "cyan", level: 1 })
|
|
951
|
+
rendered_lines << "#{margin_left}#{v_left} #{desc_text} #{v_right}"
|
|
952
|
+
end
|
|
953
|
+
|
|
713
954
|
bottom_border_line = box_border[:bl] + bot_fill + box_border[:br]
|
|
714
955
|
rendered_lines << "#{margin_left}#{colorize(bottom_border_line, border_color_cfg)}"
|
|
715
956
|
else
|
|
@@ -725,8 +966,13 @@ class GRmenu
|
|
|
725
966
|
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
726
967
|
end
|
|
727
968
|
|
|
728
|
-
|
|
729
|
-
|
|
969
|
+
if has_more_above
|
|
970
|
+
up_indicator = colorize("▲ (+#{start_idx} arriba)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
971
|
+
rendered_lines << "#{margin_left}#{solid_border} #{up_indicator} #{solid_border}"
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
visible_indices.each do |current_index|
|
|
975
|
+
option_name = all_names[current_index]
|
|
730
976
|
if @index == current_index
|
|
731
977
|
highlighted_text = colorize("> #{option_name.ljust(avail_w)}", focus_color_cfg)
|
|
732
978
|
rendered_lines << "#{margin_left}#{solid_border} #{highlighted_text} #{solid_border}"
|
|
@@ -736,6 +982,18 @@ class GRmenu
|
|
|
736
982
|
end
|
|
737
983
|
end
|
|
738
984
|
|
|
985
|
+
if has_more_below
|
|
986
|
+
remaining_below = total_items - 1 - end_idx
|
|
987
|
+
down_indicator = colorize("▼ (+#{remaining_below} abajo)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
988
|
+
rendered_lines << "#{margin_left}#{solid_border} #{down_indicator} #{solid_border}"
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
unless active_desc.empty?
|
|
992
|
+
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
993
|
+
desc_text = colorize("ℹ #{active_desc.ljust(avail_w)}", { color: "cyan", level: 1 })
|
|
994
|
+
rendered_lines << "#{margin_left}#{solid_border} #{desc_text} #{solid_border}"
|
|
995
|
+
end
|
|
996
|
+
|
|
739
997
|
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
740
998
|
end
|
|
741
999
|
|
|
@@ -778,8 +1036,9 @@ class GRmenu
|
|
|
778
1036
|
def draw_frame(target_width)
|
|
779
1037
|
lines = render_lines(target_width)
|
|
780
1038
|
buffer = String.new(CURSOR_HOME)
|
|
781
|
-
lines.
|
|
782
|
-
buffer << line << CLEAR_TO_EOL
|
|
1039
|
+
lines.each_with_index do |line, idx|
|
|
1040
|
+
buffer << line << CLEAR_TO_EOL
|
|
1041
|
+
buffer << "\r\n" if idx < lines.length - 1
|
|
783
1042
|
end
|
|
784
1043
|
buffer << CLEAR_TO_EOS
|
|
785
1044
|
Kernel.print(buffer)
|
|
@@ -848,6 +1107,8 @@ class GRmenu
|
|
|
848
1107
|
case action
|
|
849
1108
|
when Array
|
|
850
1109
|
action[0].to_s
|
|
1110
|
+
when Hash
|
|
1111
|
+
(action[:name] || action[:title] || action["name"] || action["title"] || "Opcion").to_s
|
|
851
1112
|
when Method
|
|
852
1113
|
format_auto_name(action.name)
|
|
853
1114
|
when Symbol
|
|
@@ -869,6 +1130,16 @@ class GRmenu
|
|
|
869
1130
|
end
|
|
870
1131
|
end
|
|
871
1132
|
|
|
1133
|
+
def extract_description_from_action(action)
|
|
1134
|
+
if action.is_a?(Array) && action.length >= 3
|
|
1135
|
+
action[2].to_s
|
|
1136
|
+
elsif action.is_a?(Hash)
|
|
1137
|
+
(action[:desc] || action[:description] || action["desc"] || action["description"]).to_s
|
|
1138
|
+
else
|
|
1139
|
+
""
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
|
|
872
1143
|
def execute_action(action)
|
|
873
1144
|
case action
|
|
874
1145
|
when Method, Proc
|
|
@@ -881,7 +1152,26 @@ class GRmenu
|
|
|
881
1152
|
end
|
|
882
1153
|
when Array
|
|
883
1154
|
callable = action[1]
|
|
884
|
-
|
|
1155
|
+
if callable.is_a?(Symbol)
|
|
1156
|
+
if Object.respond_to?(callable, true)
|
|
1157
|
+
Object.send(callable)
|
|
1158
|
+
elsif Kernel.respond_to?(callable, true)
|
|
1159
|
+
Kernel.send(callable)
|
|
1160
|
+
end
|
|
1161
|
+
elsif callable.respond_to?(:call)
|
|
1162
|
+
callable.call
|
|
1163
|
+
end
|
|
1164
|
+
when Hash
|
|
1165
|
+
callable = action[:action] || action[:call] || action["action"] || action["call"]
|
|
1166
|
+
if callable.is_a?(Symbol)
|
|
1167
|
+
if Object.respond_to?(callable, true)
|
|
1168
|
+
Object.send(callable)
|
|
1169
|
+
elsif Kernel.respond_to?(callable, true)
|
|
1170
|
+
Kernel.send(callable)
|
|
1171
|
+
end
|
|
1172
|
+
elsif callable.respond_to?(:call)
|
|
1173
|
+
callable.call
|
|
1174
|
+
end
|
|
885
1175
|
else
|
|
886
1176
|
action.call if action.respond_to?(:call)
|
|
887
1177
|
end
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# GRmenu (Ruby)
|
|
4
4
|
|
|
5
|
-
**Menús interactivos por teclado para terminal en modo TTY crudo, con soporte para Banners ASCII 3D, estilos y colores personalizados**
|
|
5
|
+
**Menús interactivos por teclado para terminal en modo TTY crudo, con soporte para Banners ASCII 3D, barras de progreso, spinners animados, tooltips dinámicos, estilos y colores personalizados**
|
|
6
6
|
|
|
7
7
|
Flechas arriba/abajo para moverte · `Enter` para elegir · `q` para salir
|
|
8
8
|
|
|
@@ -18,12 +18,15 @@ Flechas arriba/abajo para moverte · `Enter` para elegir · `q` para salir
|
|
|
18
18
|
|
|
19
19
|
- 🎮 **Navegación con flechas** — arriba/abajo para moverte, `Enter` para ejecutar, `q` para salir.
|
|
20
20
|
- 🔤 **10 Fuentes ASCII 3D para Banners** — fuentes tridimensionales (ANSI Shadow, Slant, Doom, Graffiti, Modular, Wire, Block, Stars, etc.).
|
|
21
|
-
-
|
|
21
|
+
- 📜 **Auto-Paginación y Scroll Fluido** — calcula la altura de la terminal y crea una ventana deslizante con indicadores automáticos (`▲ (+N arriba)` / `▼ (+M abajo)`).
|
|
22
|
+
- 💡 **Descripciones y Tooltips Dinámicos** — muestra información explicativa en la parte inferior del recuadro al enfocar cada opción.
|
|
23
|
+
- ⏳ **Spinners y Barras de Progreso** — helpers nativos `GRmenu.spinner` y `GRmenu.progress` dentro de recuadros con la misma estética visual.
|
|
24
|
+
- 🎨 **20 estilos de borde** — desde ASCII clásico hasta caracteres Unicode dobles, curvas redondeadas y bloques outline.
|
|
22
25
|
- 🌈 **Paleta de colores completa** — personalización individual de marco, título, banner, subtítulo, divisores, opciones y foco activo con 2 niveles de brillo.
|
|
23
26
|
- 📐 **Centrado simétrico automático** — alinea y centra automáticamente subtítulos y menús de opciones respecto al ancho de banners grandes.
|
|
24
|
-
- 🛠️ **Helpers nativos en modo crudo** — `clear_screen`, `continue`, `banner`, `div` y `help
|
|
27
|
+
- 🛠️ **Helpers nativos en modo crudo** — `clear_screen`, `continue`, `banner`, `spinner`, `progress`, `div` y `help`.
|
|
25
28
|
- 💻 **100% Multiplataforma** — compatible con Linux, macOS y Windows (PowerShell, CMD, Windows Terminal, VS Code).
|
|
26
|
-
- 📦 **Cero dependencias externas** — utiliza únicamente la librería estándar `io/console`.
|
|
29
|
+
- 📦 **Cero dependencias externas** — utiliza únicamente la librería estándar `io/console` y `json`.
|
|
27
30
|
|
|
28
31
|
---
|
|
29
32
|
|
|
@@ -36,7 +39,7 @@ gem install grmenu
|
|
|
36
39
|
O si trabajás con el archivo directamente en tu proyecto:
|
|
37
40
|
|
|
38
41
|
```ruby
|
|
39
|
-
|
|
42
|
+
require "GRmenu"
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
<sub>Requiere Ruby ≥ 2.6.</sub>
|
|
@@ -45,7 +48,7 @@ require_relative "GRmenu"
|
|
|
45
48
|
|
|
46
49
|
## 💡 Cómo se pasan las opciones y todos los parámetros
|
|
47
50
|
|
|
48
|
-
`GRmenu` permite pasar métodos directos, símbolos, arreglos con nombres personalizados, bloques lambda/procs y
|
|
51
|
+
`GRmenu` permite pasar métodos directos, símbolos, arreglos con nombres personalizados, bloques lambda/procs y **descripciones explicativas (tooltips)**:
|
|
49
52
|
|
|
50
53
|
```ruby
|
|
51
54
|
menu = GRmenu.new(
|
|
@@ -53,7 +56,8 @@ menu = GRmenu.new(
|
|
|
53
56
|
method(:iniciar_servidor), # 1. Method (auto-capitaliza: "Iniciar Servidor")
|
|
54
57
|
:crear_respaldo, # 2. Symbol (auto-capitaliza: "Crear Respaldo")
|
|
55
58
|
["Métricas del Sistema", method(:ver_metricas)], # 3. Array ["Nombre Personalizado", acción]
|
|
56
|
-
["
|
|
59
|
+
["Crear Backup", method(:respaldo), "Genera dump SQL de la BD"], # 4. Con Tooltip descriptivo al pie del marco
|
|
60
|
+
["Ejecutar Lambda", -> { puts Color.pink("Lambda!"); GRmenu.continue }, "Bloque anónimo Proc/Lambda"],
|
|
57
61
|
["Probar Banner Helper", method(:prueba_banner_rapido)], # 5. Helper GRmenu.banner
|
|
58
62
|
["Ver Ayuda y Referencia", method(:ver_ayuda_completa)], # 6. Helper GRmenu.help
|
|
59
63
|
method(:salir) # 7. Salir
|
|
@@ -61,11 +65,12 @@ menu = GRmenu.new(
|
|
|
61
65
|
banner: "DEV OPS", # Texto gigante en arte ASCII 3D
|
|
62
66
|
title: "Panel de Control", # Título en el marco de opciones
|
|
63
67
|
subtitle: "Consola de Administración\nUsa las flechas y Enter", # Subtítulo (soporta saltos de línea \n)
|
|
64
|
-
font: 1, # Fuente del banner (1 al 10,
|
|
68
|
+
font: 1, # Fuente del banner (1 al 10, default 1: ANSI Shadow 3D)
|
|
65
69
|
style: 7, # Estilo de marco de opciones (1 al 20, ej: 7=redondeado, 3=doble)
|
|
66
70
|
banner_style: 3, # Estilo de marco del banner (1 al 20, ej: 3=doble línea)
|
|
67
71
|
divider: true, # Líneas divisorias a la par del banner (true, false o número)
|
|
68
|
-
center: true
|
|
72
|
+
center: true, # Centrado automático del menú y subtítulo respecto al banner
|
|
73
|
+
page_size: 8 # (Opcional) Límite visible para auto-scroll y paginación
|
|
69
74
|
)
|
|
70
75
|
```
|
|
71
76
|
|
|
@@ -73,14 +78,14 @@ menu = GRmenu.new(
|
|
|
73
78
|
|
|
74
79
|
## 🌟 Ejemplo Completo de Uso (`e.rb`)
|
|
75
80
|
|
|
76
|
-
A continuación se muestra el archivo de ejemplo completo [`e.rb`](e.rb) con acciones, helpers, configuración de estilos, fuentes y colores:
|
|
81
|
+
A continuación se muestra el archivo de ejemplo completo [`e.rb`](e.rb) con acciones originales, helpers de carga (`spinner` / `progress`), paginación, configuración de estilos, fuentes y colores:
|
|
77
82
|
|
|
78
83
|
```ruby
|
|
79
84
|
# frozen_string_literal: true
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
require "GRmenu"
|
|
82
87
|
|
|
83
|
-
# 1. Definición de acciones/métodos
|
|
88
|
+
# 1. Definición de acciones/métodos originales
|
|
84
89
|
def iniciar_servidor
|
|
85
90
|
GRmenu.clear_screen
|
|
86
91
|
puts Color.bright_green("-> Servidor iniciado correctamente en el puerto 3000.")
|
|
@@ -101,7 +106,6 @@ end
|
|
|
101
106
|
|
|
102
107
|
def prueba_banner_rapido
|
|
103
108
|
GRmenu.clear_screen
|
|
104
|
-
# Helper para mostrar un banner estático o animado en cualquier momento
|
|
105
109
|
GRmenu.banner("OK", 0, color: "green", level: 2, style: 3, font: 1)
|
|
106
110
|
GRmenu.div(40, "green", 1, "═")
|
|
107
111
|
puts Color.green(" Prueba completada con éxito.")
|
|
@@ -111,7 +115,6 @@ end
|
|
|
111
115
|
|
|
112
116
|
def ver_ayuda_completa
|
|
113
117
|
GRmenu.clear_screen
|
|
114
|
-
# Helper interactivo que imprime toda la guía y referencia de GRmenu
|
|
115
118
|
GRmenu.help
|
|
116
119
|
GRmenu.continue
|
|
117
120
|
end
|
|
@@ -122,44 +125,88 @@ def salir
|
|
|
122
125
|
exit(0)
|
|
123
126
|
end
|
|
124
127
|
|
|
125
|
-
# 2.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
subtitle: "Consola de Administración\nUsa las flechas y Enter", # Subtítulo (soporta saltos de línea \n)
|
|
139
|
-
font: 1, # Fuente del banner (1 al 10, por defecto 1: ANSI Shadow 3D)
|
|
140
|
-
style: 7, # Estilo de marco de opciones (1 al 20, ej: 7=redondeado, 3=doble)
|
|
141
|
-
banner_style: 3, # Estilo de marco del banner (1 al 20, ej: 3=doble línea)
|
|
142
|
-
divider: true, # Líneas divisorias a la par del banner (true, false o número)
|
|
143
|
-
center: true # Centrado automático del menú y subtítulo respecto al banner
|
|
144
|
-
)
|
|
128
|
+
# 2. Nuevos ejemplos: Barra de Progreso, Spinner y Paginación
|
|
129
|
+
def demo_barra_progreso
|
|
130
|
+
GRmenu.clear_screen
|
|
131
|
+
# Fácil y rápido: pasas el total y bar.advance(1) en tu bucle
|
|
132
|
+
GRmenu.progress(10, title: "Descargando Paquetes", color: "cyan", style: 3) do |bar|
|
|
133
|
+
10.times do |i|
|
|
134
|
+
sleep 0.1
|
|
135
|
+
bar.advance(1, status: "Paso #{i + 1} de 10 completado")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
puts Color.bright_green("\n-> ¡Barra completada al 100%!")
|
|
139
|
+
GRmenu.continue
|
|
140
|
+
end
|
|
145
141
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
142
|
+
def demo_spinner
|
|
143
|
+
GRmenu.clear_screen
|
|
144
|
+
GRmenu.spinner("Conectando a la base de datos...", color: "green") do
|
|
145
|
+
sleep 1.2
|
|
146
|
+
end
|
|
147
|
+
puts Color.bright_green("\n-> Conexión establecida con éxito.")
|
|
148
|
+
GRmenu.continue
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def demo_paginacion
|
|
152
|
+
GRmenu.clear_screen
|
|
153
|
+
opciones_largas = (1..20).map do |n|
|
|
154
|
+
["Elemento ##{n}", -> {
|
|
155
|
+
GRmenu.clear_screen
|
|
156
|
+
puts Color.bright_cyan("-> Has seleccionado el Elemento ##{n}")
|
|
157
|
+
GRmenu.continue
|
|
158
|
+
}, "Descripción opcional del elemento ##{n}"]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
sub = GRmenu.new(
|
|
162
|
+
opciones_largas,
|
|
163
|
+
title: "Submenú Paginado",
|
|
164
|
+
subtitle: "Usa ↑ / ↓ para ver el auto-scroll",
|
|
165
|
+
style: 7,
|
|
166
|
+
page_size: 6
|
|
167
|
+
)
|
|
168
|
+
sub.draw(size_max: 38)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# 3. Instanciación del menú con TODOS los parámetros disponibles
|
|
172
|
+
def main
|
|
173
|
+
menu = GRmenu.new(
|
|
174
|
+
[
|
|
175
|
+
method(:iniciar_servidor), # 1. Method (sin info)
|
|
176
|
+
:crear_respaldo, # 2. Symbol (sin info)
|
|
177
|
+
["Métricas del Sistema", method(:ver_metricas)], # 3. Array (sin info)
|
|
178
|
+
["Probar Barra de Progreso", method(:demo_barra_progreso), "Ejemplo fácil de GRmenu.progress al 100%"], # 4. CON Tooltip
|
|
179
|
+
["Probar Spinner de Carga", method(:demo_spinner), "Animación en tiempo real para funciones pesadas"],
|
|
180
|
+
["Probar Paginación (20 ítems)", method(:demo_paginacion), "Desplazamiento suave de opciones"],
|
|
181
|
+
["Ejecutar Lambda", -> { puts Color.pink("¡Lambda!"); GRmenu.continue }], # 5. Lambda/Proc
|
|
182
|
+
["Probar Banner Helper", method(:prueba_banner_rapido)], # 6. Helper GRmenu.banner
|
|
183
|
+
["Ver Ayuda y Referencia", method(:ver_ayuda_completa), "Abre la guía de ayuda interactiva"], # 7. Helper GRmenu.help
|
|
184
|
+
method(:salir) # 8. Salir
|
|
185
|
+
],
|
|
186
|
+
banner: " gr codE ",
|
|
187
|
+
title: "Panel de Control",
|
|
188
|
+
subtitle: "Consola de Administración TTY\nUsa ↑ / ↓ para navegar y Enter para seleccionar",
|
|
189
|
+
style: 3,
|
|
190
|
+
banner_style: 3,
|
|
191
|
+
divider: true,
|
|
192
|
+
center: true
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# 4. Configuración completa de colores y estilos
|
|
196
|
+
menu.set_style.font(1) # 1 = ANSI Shadow 3D
|
|
197
|
+
menu.set_style.banner("cyan", 2) # Color del banner ASCII
|
|
198
|
+
menu.set_style.title("yellow", 2) # Color del título del recuadro
|
|
199
|
+
menu.set_style.subtitle("white", 1)# Color del subtítulo
|
|
200
|
+
menu.set_style.divider("blue", 1) # Color de las líneas divisorias
|
|
201
|
+
menu.set_style.border("yellow", 1) # Color del borde del marco de opciones
|
|
202
|
+
menu.set_style.options("white", 1) # Color de opciones no seleccionadas
|
|
203
|
+
menu.set_style.focus("green", 2) # Color y brillo de la opción resaltada
|
|
204
|
+
|
|
205
|
+
# 5. Dibujar y lanzar el menú interactivo
|
|
206
|
+
menu.draw(size_max: 38)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
main
|
|
163
210
|
```
|
|
164
211
|
|
|
165
212
|
---
|
|
@@ -178,28 +225,6 @@ O desde una instancia:
|
|
|
178
225
|
menu.help
|
|
179
226
|
```
|
|
180
227
|
|
|
181
|
-
Salida limpia y estructurada en consola:
|
|
182
|
-
|
|
183
|
-
```text
|
|
184
|
-
╔══════════════════════════════════════════════════════════════╗
|
|
185
|
-
║ GRmenu - Guia y Referencia Rapida ║
|
|
186
|
-
║ Navegacion interactiva en terminal TTY ║
|
|
187
|
-
╚══════════════════════════════════════════════════════════════╝
|
|
188
|
-
|
|
189
|
-
[1] HELPERS NATIVOS EN MODO CRUDO
|
|
190
|
-
────────────────────────────────────────────────────────────────
|
|
191
|
-
GRmenu.clear_screen (o GRmenu.clr)
|
|
192
|
-
* Limpia la terminal al instante con secuencias ANSI.
|
|
193
|
-
GRmenu.continue(mensaje)
|
|
194
|
-
* Pausa interactiva: espera una sola tecla en modo TTY crudo.
|
|
195
|
-
GRmenu.banner(texto, delay, color:, level:, style:, font:)
|
|
196
|
-
* Renderiza banner ASCII 3D con marco y animacion opcional.
|
|
197
|
-
GRmenu.div(longitud, color, level, char)
|
|
198
|
-
* Dibuja linea divisoria horizontal adaptable a la consola.
|
|
199
|
-
GRmenu.help
|
|
200
|
-
* Imprime esta guia visual interactiva en consola.
|
|
201
|
-
```
|
|
202
|
-
|
|
203
228
|
---
|
|
204
229
|
|
|
205
230
|
## 🕹️ Controles
|
|
@@ -273,13 +298,23 @@ GRmenu.clear_screen # o GRmenu.clr
|
|
|
273
298
|
# 2. Imprime un banner o logo gigante responsivo
|
|
274
299
|
GRmenu.banner("SECURE", 0, color: "magenta", style: 3, font: 1)
|
|
275
300
|
|
|
276
|
-
# 3.
|
|
301
|
+
# 3. Spinner animado en tiempo real mientras corre un bloque
|
|
302
|
+
GRmenu.spinner("Conectando al clúster...", color: "green") do
|
|
303
|
+
hacer_tarea_pesada()
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# 4. Barra de progreso porcentual dentro de un recuadro estilizado
|
|
307
|
+
GRmenu.progress(10, title: "Exportando Datos", color: "cyan") do |bar|
|
|
308
|
+
10.times { |i| bar.advance(1, status: "Paso #{i + 1}/10") }
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# 5. Línea divisoria horizontal adaptable
|
|
277
312
|
GRmenu.div(60, "blue")
|
|
278
313
|
|
|
279
|
-
#
|
|
314
|
+
# 6. Pausa de consola que espera una sola tecla en modo TTY crudo
|
|
280
315
|
GRmenu.continue("Presiona cualquier tecla para continuar...")
|
|
281
316
|
|
|
282
|
-
#
|
|
317
|
+
# 7. Guía interactiva en consola
|
|
283
318
|
GRmenu.help
|
|
284
319
|
```
|
|
285
320
|
|
|
@@ -291,7 +326,7 @@ GRmenu.help
|
|
|
291
326
|
|
|
292
327
|
| Parámetro | Tipo | Descripción |
|
|
293
328
|
|------------------|------------|-------------|
|
|
294
|
-
| `functions` | `Array` | Opciones a mostrar (`Method`, `Symbol`, `Array ["Nombre", acción]`, `Proc`/`lambda`). |
|
|
329
|
+
| `functions` | `Array` | Opciones a mostrar (`Method`, `Symbol`, `Array ["Nombre", acción, "Descripción"]`, `Proc`/`lambda`). |
|
|
295
330
|
| `banner:` | `String` | Texto gigante a renderizar en arte ASCII 3D arriba del menú. |
|
|
296
331
|
| `title:` | `String` | Título en la cabecera del marco de opciones. |
|
|
297
332
|
| `subtitle:` | `String` | Subtítulo o descripción (soporta múltiples líneas con `\n`). |
|
|
@@ -300,6 +335,15 @@ GRmenu.help
|
|
|
300
335
|
| `banner_style:` | `Integer` | Estilo de marco para el banner (1 al 20, default 3). |
|
|
301
336
|
| `divider:` | `Boolean` | Dibuja líneas divisorias a la par del ancho del banner. |
|
|
302
337
|
| `center:` | `Boolean` | Centra simétricamente el subtítulo y menú de opciones (default `true`). |
|
|
338
|
+
| `page_size:` | `Integer` | (Opcional) Número máximo de opciones visibles en pantalla para auto-paginación. |
|
|
339
|
+
|
|
340
|
+
### `GRmenu.spinner(message, color: "cyan", &block)`
|
|
341
|
+
|
|
342
|
+
Ejecuta un bloque en segundo plano mientras dibuja una animación giratoria y muestra `✔ Mensaje ¡Listo!` al terminar.
|
|
343
|
+
|
|
344
|
+
### `GRmenu.progress(total, title:, color:, style:, &block)`
|
|
345
|
+
|
|
346
|
+
Crea una barra de progreso porcentual dentro de un recuadro estilizado. El bloque recibe el objeto `bar` con los métodos `bar.advance(n, status:)` y `bar.set(valor, status:)`.
|
|
303
347
|
|
|
304
348
|
### `menu.set_style`
|
|
305
349
|
|