grmenu 4.1.0 → 5.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/README.md +186 -49
- data/data/colors.json +224 -44
- data/data/help.txt +223 -90
- data/data/themes/chromatic_red.gr +74 -0
- data/data/themes/cyberpunk.gr +18 -2
- data/data/themes/dracula.gr +18 -2
- data/data/themes/matrix.gr +18 -2
- data/data/themes/monokai.gr +18 -2
- data/data/themes/nord.gr +18 -2
- data/data/themes/sunset.gr +18 -2
- data/grmenu/animation.rb +139 -0
- data/grmenu/banner.rb +99 -0
- data/grmenu/color.rb +265 -0
- data/grmenu/image.rb +339 -0
- data/grmenu/interactive.rb +1002 -0
- data/grmenu/renderer.rb +865 -0
- data/grmenu/set_style.rb +312 -0
- data/grmenu/terminal.rb +189 -0
- data/grmenu/theme.rb +378 -0
- data/grmenu/version.rb +6 -0
- data/grmenu/widgets/checkbox.rb +291 -0
- data/grmenu/widgets/dialogs.rb +251 -0
- data/grmenu/widgets/form.rb +533 -0
- data/grmenu/widgets/input.rb +171 -0
- data/grmenu/widgets/modal.rb +209 -0
- data/grmenu/widgets/progress.rb +210 -0
- data/grmenu/widgets/slider.rb +178 -0
- data/grmenu/widgets/table.rb +291 -0
- data/grmenu/window.rb +187 -0
- data/grmenu.rb +88 -4339
- metadata +23 -3
- data/data/themes/neon_red.gr +0 -51
data/data/themes/monokai.gr
CHANGED
|
@@ -52,9 +52,25 @@ GRmenu::config<-1->
|
|
|
52
52
|
unchecked_mark:: [ ]
|
|
53
53
|
>>
|
|
54
54
|
|
|
55
|
+
<<input
|
|
56
|
+
style:: 4
|
|
57
|
+
border_color:: yellow:2
|
|
58
|
+
title_color:: green:2
|
|
59
|
+
label_color:: orange:2
|
|
60
|
+
>>
|
|
61
|
+
|
|
55
62
|
<<modal
|
|
56
63
|
style:: 4
|
|
57
64
|
border_color:: yellow:2
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
title_color:: green:2
|
|
66
|
+
content_color:: white:1
|
|
67
|
+
shadow:: true
|
|
68
|
+
>>
|
|
69
|
+
|
|
70
|
+
<<form
|
|
71
|
+
style:: 4
|
|
72
|
+
border_color:: yellow:2
|
|
73
|
+
title_color:: green:2
|
|
74
|
+
focus_color:: magenta:2
|
|
75
|
+
shadow:: true
|
|
60
76
|
>>
|
data/data/themes/nord.gr
CHANGED
|
@@ -52,9 +52,25 @@ GRmenu::config<-1->
|
|
|
52
52
|
unchecked_mark:: [ ]
|
|
53
53
|
>>
|
|
54
54
|
|
|
55
|
+
<<input
|
|
56
|
+
style:: 6
|
|
57
|
+
border_color:: blue:2
|
|
58
|
+
title_color:: cyan:2
|
|
59
|
+
label_color:: aqua:2
|
|
60
|
+
>>
|
|
61
|
+
|
|
55
62
|
<<modal
|
|
56
63
|
style:: 6
|
|
57
64
|
border_color:: blue:2
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
title_color:: cyan:2
|
|
66
|
+
content_color:: white:1
|
|
67
|
+
shadow:: true
|
|
68
|
+
>>
|
|
69
|
+
|
|
70
|
+
<<form
|
|
71
|
+
style:: 6
|
|
72
|
+
border_color:: blue:2
|
|
73
|
+
title_color:: cyan:2
|
|
74
|
+
focus_color:: aqua:2
|
|
75
|
+
shadow:: true
|
|
60
76
|
>>
|
data/data/themes/sunset.gr
CHANGED
|
@@ -52,9 +52,25 @@ GRmenu::config<-1->
|
|
|
52
52
|
unchecked_mark:: [ ]
|
|
53
53
|
>>
|
|
54
54
|
|
|
55
|
+
<<input
|
|
56
|
+
style:: 19
|
|
57
|
+
border_color:: orange:2
|
|
58
|
+
title_color:: yellow:2
|
|
59
|
+
label_color:: pink:2
|
|
60
|
+
>>
|
|
61
|
+
|
|
55
62
|
<<modal
|
|
56
63
|
style:: 19
|
|
57
64
|
border_color:: orange:2
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
title_color:: yellow:2
|
|
66
|
+
content_color:: white:1
|
|
67
|
+
shadow:: true
|
|
68
|
+
>>
|
|
69
|
+
|
|
70
|
+
<<form
|
|
71
|
+
style:: 19
|
|
72
|
+
border_color:: orange:2
|
|
73
|
+
title_color:: yellow:2
|
|
74
|
+
focus_color:: yellow:2
|
|
75
|
+
shadow:: true
|
|
60
76
|
>>
|
data/grmenu/animation.rb
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class GRmenu
|
|
4
|
+
# Divide una cadena con secuencias ANSI en un arreglo de caracteres individuales,
|
|
5
|
+
# asociando a cada caracter el código de estilo ANSI que tenía activo.
|
|
6
|
+
# Esto permite manipular o animar la matriz de texto celda por celda sin romper escapes.
|
|
7
|
+
def self.split_ansi_chars(str)
|
|
8
|
+
segments = []
|
|
9
|
+
current_style = String.new("")
|
|
10
|
+
in_escape = false
|
|
11
|
+
escape_buffer = String.new("")
|
|
12
|
+
|
|
13
|
+
str.to_s.each_char do |ch|
|
|
14
|
+
if ch == "\e"
|
|
15
|
+
in_escape = true
|
|
16
|
+
escape_buffer << ch
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
if in_escape
|
|
20
|
+
escape_buffer << ch
|
|
21
|
+
# Las secuencias de formato de estilo terminan en una letra
|
|
22
|
+
if ch =~ /[a-zA-Z]/
|
|
23
|
+
in_escape = false
|
|
24
|
+
current_style = escape_buffer.dup
|
|
25
|
+
escape_buffer.clear
|
|
26
|
+
end
|
|
27
|
+
next
|
|
28
|
+
end
|
|
29
|
+
segments << { char: ch, style: current_style.dup }
|
|
30
|
+
end
|
|
31
|
+
segments
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Ejecuta una animación de transición al presentar el menú en pantalla.
|
|
35
|
+
# Tipos soportados:
|
|
36
|
+
# - :diagonal -> Ola diagonal progresiva que revela el menú desde la esquina superior izquierda.
|
|
37
|
+
# - :linear -> Aparición línea por línea de arriba hacia abajo.
|
|
38
|
+
# - :fade -> Efecto de aparición gradual aumentando el brillo de los caracteres.
|
|
39
|
+
# - :rainbow -> Barrido espectral multicolor continuo de arcoíris.
|
|
40
|
+
# - :chroma -> Alias de rainbow.
|
|
41
|
+
def self.animate_render(lines, type = :diagonal, delay = 0.012)
|
|
42
|
+
type_str = type.to_s.downcase
|
|
43
|
+
return if lines.nil? || lines.empty?
|
|
44
|
+
reset_seq = ansi_reset
|
|
45
|
+
|
|
46
|
+
case type_str
|
|
47
|
+
when "diagonal"
|
|
48
|
+
# Tokeniza cada fila conservando sus estilos ANSI
|
|
49
|
+
parsed_rows = lines.map { |l| split_ansi_chars(l) }
|
|
50
|
+
max_len = parsed_rows.map(&:length).max || 0
|
|
51
|
+
total_steps = max_len + (parsed_rows.length * 2)
|
|
52
|
+
step = 0
|
|
53
|
+
|
|
54
|
+
while step <= total_steps
|
|
55
|
+
buffer = String.new(CURSOR_HOME)
|
|
56
|
+
parsed_rows.each_with_index do |row_segments, y|
|
|
57
|
+
rendered_row = String.new("")
|
|
58
|
+
row_segments.each_with_index do |segment, x|
|
|
59
|
+
# Ecuación de frente de onda diagonal: los puntos donde (x + 2y) <= step ya se revelaron
|
|
60
|
+
if (x + y * 2) <= step
|
|
61
|
+
rendered_row << segment[:style] << segment[:char] << reset_seq
|
|
62
|
+
else
|
|
63
|
+
rendered_row << " "
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
buffer << rendered_row << CLEAR_TO_EOL << "\r\n"
|
|
67
|
+
end
|
|
68
|
+
buffer << CLEAR_TO_EOS
|
|
69
|
+
Kernel.print(buffer)
|
|
70
|
+
$stdout.flush
|
|
71
|
+
sleep(delay)
|
|
72
|
+
step += 4 # Incremento de paso para mantener fluidez a 30-60 FPS
|
|
73
|
+
end
|
|
74
|
+
when "linear"
|
|
75
|
+
buffer = String.new(CURSOR_HOME)
|
|
76
|
+
lines.each do |line|
|
|
77
|
+
Kernel.print("#{line}#{CLEAR_TO_EOL}\r\n")
|
|
78
|
+
$stdout.flush
|
|
79
|
+
sleep(delay * 3)
|
|
80
|
+
end
|
|
81
|
+
when "fade"
|
|
82
|
+
[1, 2].each do |lvl|
|
|
83
|
+
buffer = String.new(CURSOR_HOME)
|
|
84
|
+
lines.each do |line|
|
|
85
|
+
clean_line = line.gsub(/\e\[[0-9;]*m/, '')
|
|
86
|
+
buffer << ansi_color("white", lvl) << clean_line << reset_seq << CLEAR_TO_EOL << "\r\n"
|
|
87
|
+
end
|
|
88
|
+
buffer << CLEAR_TO_EOS
|
|
89
|
+
Kernel.print(buffer)
|
|
90
|
+
$stdout.flush
|
|
91
|
+
sleep(delay * 8)
|
|
92
|
+
end
|
|
93
|
+
when "rainbow", "chroma", "rgb"
|
|
94
|
+
# Barrido de espectro cromático que revela la matriz con una ola multicolor viva
|
|
95
|
+
parsed_rows = lines.map { |l| split_ansi_chars(l) }
|
|
96
|
+
max_len = parsed_rows.map(&:length).max || 0
|
|
97
|
+
total_steps = max_len + (parsed_rows.length * 2)
|
|
98
|
+
step = 0
|
|
99
|
+
|
|
100
|
+
while step <= total_steps
|
|
101
|
+
buffer = String.new(CURSOR_HOME)
|
|
102
|
+
parsed_rows.each_with_index do |row_segments, y|
|
|
103
|
+
rendered_row = String.new("")
|
|
104
|
+
row_segments.each_with_index do |segment, x|
|
|
105
|
+
if (x + y * 2) <= step
|
|
106
|
+
# Al revelarse, genera la secuencia espectral de color arcoíris
|
|
107
|
+
wave_color = rgb_color(step * 0.12, (x + y * 2) * 0.08)
|
|
108
|
+
rendered_row << wave_color << segment[:char] << reset_seq
|
|
109
|
+
else
|
|
110
|
+
rendered_row << " "
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
buffer << rendered_row << CLEAR_TO_EOL << "\r\n"
|
|
114
|
+
end
|
|
115
|
+
buffer << CLEAR_TO_EOS
|
|
116
|
+
Kernel.print(buffer)
|
|
117
|
+
$stdout.flush
|
|
118
|
+
sleep(delay)
|
|
119
|
+
step += 4
|
|
120
|
+
end
|
|
121
|
+
when "chromatic", "cromatico"
|
|
122
|
+
# Transición de aparición con brillo cromático gradual
|
|
123
|
+
[0.3, 0.6, 1.0].each do |factor|
|
|
124
|
+
buffer = String.new(CURSOR_HOME)
|
|
125
|
+
lines.each do |line|
|
|
126
|
+
clean_line = line.gsub(/\e\[[0-9;]*m/, '')
|
|
127
|
+
r = (60 * factor).to_i
|
|
128
|
+
g = (255 * factor).to_i
|
|
129
|
+
b = (255 * factor).to_i
|
|
130
|
+
buffer << "\e[38;2;#{r};#{g};#{b}m#{clean_line}#{reset_seq}" << CLEAR_TO_EOL << "\r\n"
|
|
131
|
+
end
|
|
132
|
+
buffer << CLEAR_TO_EOS
|
|
133
|
+
Kernel.print(buffer)
|
|
134
|
+
$stdout.flush
|
|
135
|
+
sleep(delay * 6)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
data/grmenu/banner.rb
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class GRmenu
|
|
4
|
+
# Construye las filas de arte ASCII 3D para un texto dado utilizando una de las 10 fuentes.
|
|
5
|
+
# Intenta ajustar el espaciado entre letras (2, 1 o 0 espacios) para que el banner quepa
|
|
6
|
+
# comodamente dentro del ancho de la terminal sin desbordar.
|
|
7
|
+
def self.build_ascii_lines(text, max_cols = terminal_width, font_id = 1)
|
|
8
|
+
target_font = FONTS[font_id.to_i] || FONTS[1]
|
|
9
|
+
clean_chars = text.to_s.upcase.chars.select { |char| target_font.key?(char) }
|
|
10
|
+
return [] if clean_chars.empty?
|
|
11
|
+
|
|
12
|
+
font_height = target_font.values.first.length
|
|
13
|
+
|
|
14
|
+
# Prueba sucesivamente con espaciado amplio (2), medio (1) y compacto (0)
|
|
15
|
+
[2, 1, 0].each do |spacing|
|
|
16
|
+
lines = Array.new(font_height, "")
|
|
17
|
+
clean_chars.each_with_index do |char, idx|
|
|
18
|
+
glyph_rows = target_font[char]
|
|
19
|
+
padding = (idx == clean_chars.length - 1) ? "" : (" " * spacing)
|
|
20
|
+
font_height.times { |row_i| lines[row_i] += glyph_rows[row_i] + padding }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
max_line_len = lines.map { |line| display_width(line) }.max
|
|
24
|
+
# Si cabe con holgura en las columnas de la terminal, lo retornamos
|
|
25
|
+
return lines if (max_line_len + 6) <= max_cols
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Si el texto es demasiado largo para cualquier espaciado ASCII, retornamos nil para fallback a texto plano
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Renderiza un banner ASCII 3D enmarcado directamente en la terminal.
|
|
33
|
+
# Soporta colores ANSI calibrados o degradado continuo RGB Chroma Wave.
|
|
34
|
+
def self.banner(text, delay = 0, color: "magenta", level: 2, style: 3, font: 1)
|
|
35
|
+
cols = terminal_width
|
|
36
|
+
is_rgb = (color.to_s.downcase == "rgb" || color.to_s.downcase == "rainbow" || color.to_s.downcase == "chroma")
|
|
37
|
+
color_code = is_rgb ? "" : ansi_color(color, level)
|
|
38
|
+
reset_code = ansi_reset
|
|
39
|
+
|
|
40
|
+
ascii_rows = build_ascii_lines(text, cols, font)
|
|
41
|
+
border_cfg = BORDERS[style] || BORDERS[3]
|
|
42
|
+
h_top = border_cfg[:ht] || border_cfg[:h]
|
|
43
|
+
h_bot = border_cfg[:hb] || border_cfg[:h]
|
|
44
|
+
v_l = border_cfg[:vl] || border_cfg[:v]
|
|
45
|
+
v_r = border_cfg[:vr] || border_cfg[:v]
|
|
46
|
+
|
|
47
|
+
# Caso A: El texto cupo como arte ASCII 3D
|
|
48
|
+
if ascii_rows
|
|
49
|
+
max_len = ascii_rows.map { |r| display_width(r) }.max
|
|
50
|
+
# Calcula la linea de relleno superior e inferior repitiendo el caracter de borde
|
|
51
|
+
top_fill = (h_top * ((max_len + 4).to_f / h_top.length).ceil)[0...(max_len + 4)]
|
|
52
|
+
bot_fill = (h_bot * ((max_len + 4).to_f / h_bot.length).ceil)[0...(max_len + 4)]
|
|
53
|
+
|
|
54
|
+
if is_rgb
|
|
55
|
+
Kernel.print("#{Color.rgb("#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}")}\r\n")
|
|
56
|
+
ascii_rows.each_with_index do |line, idx|
|
|
57
|
+
pad = " " * (max_len - display_width(line))
|
|
58
|
+
row_content = " #{line}#{pad} "
|
|
59
|
+
Kernel.print("#{Color.rgb(v_l)}#{Color.rgb(row_content, idx * 0.2)}#{Color.rgb(v_r)}\r\n")
|
|
60
|
+
sleep(delay) if delay > 0
|
|
61
|
+
end
|
|
62
|
+
Kernel.print("#{Color.rgb("#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}")}\r\n")
|
|
63
|
+
else
|
|
64
|
+
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
65
|
+
ascii_rows.each do |line|
|
|
66
|
+
pad = " " * (max_len - display_width(line))
|
|
67
|
+
Kernel.print("#{color_code}#{v_l} #{line}#{pad} #{v_r}#{reset_code}\r\n")
|
|
68
|
+
sleep(delay) if delay > 0
|
|
69
|
+
end
|
|
70
|
+
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
71
|
+
end
|
|
72
|
+
# Caso B: Fallback a texto normal centrado dentro de una caja
|
|
73
|
+
else
|
|
74
|
+
clean_t = text.to_s.strip
|
|
75
|
+
box_w = [display_width(clean_t) + 6, cols - 2].min
|
|
76
|
+
top_fill = (h_top * ((box_w - 2).to_f / h_top.length).ceil)[0...(box_w - 2)]
|
|
77
|
+
bot_fill = (h_bot * ((box_w - 2).to_f / h_bot.length).ceil)[0...(box_w - 2)]
|
|
78
|
+
|
|
79
|
+
pad_t = [box_w - 4 - display_width(clean_t), 0].max
|
|
80
|
+
l_p = " " * (pad_t / 2)
|
|
81
|
+
r_p = " " * (pad_t - (pad_t / 2))
|
|
82
|
+
|
|
83
|
+
if is_rgb
|
|
84
|
+
Kernel.print("#{Color.rgb("#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}")}\r\n")
|
|
85
|
+
Kernel.print("#{Color.rgb(v_l)} #{Color.rgb(l_p + clean_t + r_p)} #{Color.rgb(v_r)}\r\n")
|
|
86
|
+
Kernel.print("#{Color.rgb("#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}")}\r\n")
|
|
87
|
+
else
|
|
88
|
+
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
89
|
+
Kernel.print("#{color_code}#{v_l} #{l_p}#{clean_t}#{r_p} #{v_r}#{reset_code}\r\n")
|
|
90
|
+
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
alias_method :message, :banner
|
|
97
|
+
alias_method :logo, :banner
|
|
98
|
+
end
|
|
99
|
+
end
|
data/grmenu/color.rb
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class GRmenu
|
|
4
|
+
# Genera un color TrueColor (24 bits) basado en una onda senoidal cromática.
|
|
5
|
+
# Los tres canales (R, G, B) están desfasados en 120 grados (2*pi/3 = 2.0943951 rad)
|
|
6
|
+
# para producir una transición de arcoíris fluida a lo largo del tiempo.
|
|
7
|
+
def self.rgb_color(tick, offset = 0.0)
|
|
8
|
+
time = tick.to_f + offset.to_f
|
|
9
|
+
red = (Math.sin(time) * 127 + 128).clamp(0, 255).to_i
|
|
10
|
+
green = (Math.sin(time + 2.0943951) * 127 + 128).clamp(0, 255).to_i
|
|
11
|
+
blue = (Math.sin(time + 4.1887902) * 127 + 128).clamp(0, 255).to_i
|
|
12
|
+
"\e[38;2;#{red};#{green};#{blue}m"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Resuelve cualquier especificación de color a su secuencia de escape ANSI correspondiente.
|
|
16
|
+
# Admite:
|
|
17
|
+
# - Nombres integrados con nivel ("cyan:2", "red:1", etc.)
|
|
18
|
+
# - Palabras clave dinámicas ("rgb", "chroma", "rainbow")
|
|
19
|
+
# - Prefijos cromáticos ("chromatic_cyan", "cromatico_red")
|
|
20
|
+
# - Hexadecimal de 6 dígitos ("#FF0055" o "FF0055")
|
|
21
|
+
# - Hexadecimal shorthand de 3 dígitos ("#F05" o "F05")
|
|
22
|
+
def self.ansi_color(color_name, level = 1)
|
|
23
|
+
clean_name = color_name.to_s.downcase.strip
|
|
24
|
+
if clean_name.include?(":")
|
|
25
|
+
parts = clean_name.split(":")
|
|
26
|
+
clean_name = parts[0].strip
|
|
27
|
+
level = parts[1].to_i if parts[1] && !parts[1].empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return rgb_color(0.0) if clean_name == "rgb" || clean_name == "rainbow" || clean_name == "chroma"
|
|
31
|
+
|
|
32
|
+
# Hexadecimal de 6 dígitos (#RRGGBB)
|
|
33
|
+
if clean_name =~ /\A#?([0-9a-f]{6})\z/i
|
|
34
|
+
hex_code = $1
|
|
35
|
+
r = hex_code[0..1].to_i(16)
|
|
36
|
+
g = hex_code[2..3].to_i(16)
|
|
37
|
+
b = hex_code[4..5].to_i(16)
|
|
38
|
+
return "\e[38;2;#{r};#{g};#{b}m"
|
|
39
|
+
# Hexadecimal shorthand de 3 dígitos (#RGB) duplicado a (#RRGGBB)
|
|
40
|
+
elsif clean_name =~ /\A#?([0-9a-f]{3})\z/i
|
|
41
|
+
hex_code = $1
|
|
42
|
+
r = (hex_code[0] * 2).to_i(16)
|
|
43
|
+
g = (hex_code[1] * 2).to_i(16)
|
|
44
|
+
b = (hex_code[2] * 2).to_i(16)
|
|
45
|
+
return "\e[38;2;#{r};#{g};#{b}m"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Mapeo transparente para nombres cromáticos contra colors.json
|
|
49
|
+
lookup_name = clean_name
|
|
50
|
+
unless COLORS.key?(lookup_name)
|
|
51
|
+
if lookup_name.start_with?("cromatico_")
|
|
52
|
+
lookup_name = lookup_name.sub(/\Acromatico_/, "chromatic_")
|
|
53
|
+
elsif lookup_name.start_with?("chromatic_")
|
|
54
|
+
lookup_name = lookup_name.sub(/\Achromatic_/, "cromatico_")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
level_str = level.to_s
|
|
59
|
+
code_raw = COLORS.dig(lookup_name, level_str) || COLORS.dig(lookup_name, level.to_i) || COLORS[lookup_name]
|
|
60
|
+
return "\e[#{code_raw}" if code_raw
|
|
61
|
+
"\e[37m"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Secuencia de reseteo ANSI a colores predeterminados de la terminal.
|
|
65
|
+
def self.ansi_reset
|
|
66
|
+
"\e[0m"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Módulo utilitario para colorear texto directamente en consola o scripts de Ruby.
|
|
70
|
+
module Color
|
|
71
|
+
RESET = "\e[0m"
|
|
72
|
+
BOLD = "\e[1m"
|
|
73
|
+
module_function
|
|
74
|
+
|
|
75
|
+
# Pinta un texto con el color indicado y asegura el reseteo al final.
|
|
76
|
+
def paint(text, color_name, level = 1)
|
|
77
|
+
color_str = color_name.to_s.downcase
|
|
78
|
+
if color_str == "rgb" || color_str == "rainbow" || color_str == "chroma"
|
|
79
|
+
return rgb(text)
|
|
80
|
+
end
|
|
81
|
+
code = GRmenu.ansi_color(color_name, level) || "\e[37m"
|
|
82
|
+
"#{code}#{text}#{RESET}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Aplica un degradado RGB dinámico caracter por caracter respetando secuencias previas.
|
|
86
|
+
def rgb(text, offset = 0.0)
|
|
87
|
+
output_str = String.new("")
|
|
88
|
+
char_index = 0
|
|
89
|
+
in_escape = false
|
|
90
|
+
escape_buffer = String.new("")
|
|
91
|
+
|
|
92
|
+
text.to_s.each_char do |ch|
|
|
93
|
+
# Si encontramos el inicio de una secuencia ANSI existente, no la rompemos
|
|
94
|
+
if ch == "\e"
|
|
95
|
+
in_escape = true
|
|
96
|
+
escape_buffer << ch
|
|
97
|
+
next
|
|
98
|
+
end
|
|
99
|
+
if in_escape
|
|
100
|
+
escape_buffer << ch
|
|
101
|
+
# Las secuencias ANSI terminan en una letra (ej. m, H, J, etc.)
|
|
102
|
+
if ch =~ /[a-zA-Z]/
|
|
103
|
+
in_escape = false
|
|
104
|
+
output_str << escape_buffer
|
|
105
|
+
escape_buffer.clear
|
|
106
|
+
end
|
|
107
|
+
next
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Espacios y saltos de línea avanzan la posición de columna visual para mantener la fase continua
|
|
111
|
+
w = (GRmenu.char_width(ch) rescue 1)
|
|
112
|
+
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t"
|
|
113
|
+
output_str << ch
|
|
114
|
+
char_index += w
|
|
115
|
+
else
|
|
116
|
+
# Calcula el desfase de color individual para dar efecto de ola (chroma wave)
|
|
117
|
+
t = char_index * 0.12 + offset
|
|
118
|
+
r = (Math.sin(t) * 127 + 128).clamp(0, 255).to_i
|
|
119
|
+
g = (Math.sin(t + 2.0943951) * 127 + 128).clamp(0, 255).to_i
|
|
120
|
+
b = (Math.sin(t + 4.1887902) * 127 + 128).clamp(0, 255).to_i
|
|
121
|
+
output_str << "\e[38;2;#{r};#{g};#{b}m#{ch}"
|
|
122
|
+
char_index += w
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
output_str << RESET
|
|
126
|
+
output_str
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Métodos directos para el espectro cromático continuo (RGB / Rainbow / Chroma)
|
|
130
|
+
def rainbow(text, offset = 0.0)
|
|
131
|
+
rgb(text, offset)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def chroma(text, offset = 0.0)
|
|
135
|
+
rgb(text, offset)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def chromatic(text, offset = 0.0)
|
|
139
|
+
rgb(text, offset)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def cromatico(text, offset = 0.0)
|
|
143
|
+
rgb(text, offset)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Métodos directos para colores clásicos
|
|
147
|
+
def red(s); paint(s, :red, 1); end
|
|
148
|
+
def bright_red(s); paint(s, :red, 2); end
|
|
149
|
+
def dark_red(s); paint(s, :red, 1); end
|
|
150
|
+
|
|
151
|
+
def green(s); paint(s, :green, 1); end
|
|
152
|
+
def bright_green(s); paint(s, :green, 2); end
|
|
153
|
+
def dark_green(s); paint(s, :green, 1); end
|
|
154
|
+
|
|
155
|
+
def yellow(s); paint(s, :yellow, 1); end
|
|
156
|
+
def bright_yellow(s); paint(s, :yellow, 2); end
|
|
157
|
+
|
|
158
|
+
def blue(s); paint(s, :blue, 1); end
|
|
159
|
+
def bright_blue(s); paint(s, :blue, 2); end
|
|
160
|
+
|
|
161
|
+
def magenta(s); paint(s, :magenta, 1); end
|
|
162
|
+
def bright_magenta(s); paint(s, :magenta, 2); end
|
|
163
|
+
|
|
164
|
+
def purple(s); paint(s, :purple, 1); end
|
|
165
|
+
def bright_purple(s); paint(s, :purple, 2); end
|
|
166
|
+
|
|
167
|
+
def pink(s); paint(s, :pink, 1); end
|
|
168
|
+
def bright_pink(s); paint(s, :pink, 2); end
|
|
169
|
+
|
|
170
|
+
def cyan(s); paint(s, :cyan, 1); end
|
|
171
|
+
def bright_cyan(s); paint(s, :cyan, 2); end
|
|
172
|
+
|
|
173
|
+
def aqua(s); paint(s, :aqua, 1); end
|
|
174
|
+
def bright_aqua(s); paint(s, :aqua, 2); end
|
|
175
|
+
|
|
176
|
+
def orange(s); paint(s, :orange, 1); end
|
|
177
|
+
def bright_orange(s); paint(s, :orange, 2); end
|
|
178
|
+
|
|
179
|
+
def white(s); paint(s, :white, 1); end
|
|
180
|
+
def bright_white(s); paint(s, :white, 2); end
|
|
181
|
+
|
|
182
|
+
def black(s); paint(s, :black, 1); end
|
|
183
|
+
def gray(s); paint(s, :gray, 1); end
|
|
184
|
+
def bright_gray(s); paint(s, :gray, 2); end
|
|
185
|
+
def grey(s); gray(s); end
|
|
186
|
+
|
|
187
|
+
# Métodos cromáticos vivos (anteriormente llamados neón)
|
|
188
|
+
def chromatic_red(s); paint(s, :chromatic_red, 2); end
|
|
189
|
+
def chromatic_green(s); paint(s, :chromatic_green, 2); end
|
|
190
|
+
def chromatic_cyan(s); paint(s, :chromatic_cyan, 2); end
|
|
191
|
+
def chromatic_blue(s); paint(s, :chromatic_blue, 2); end
|
|
192
|
+
def chromatic_pink(s); paint(s, :chromatic_pink, 2); end
|
|
193
|
+
def chromatic_yellow(s); paint(s, :chromatic_yellow, 2); end
|
|
194
|
+
def chromatic_orange(s); paint(s, :chromatic_orange, 2); end
|
|
195
|
+
def chromatic_purple(s); paint(s, :chromatic_purple, 2); end
|
|
196
|
+
def chromatic_magenta(s); paint(s, :chromatic_magenta, 2); end
|
|
197
|
+
def chromatic_aqua(s); paint(s, :chromatic_aqua, 2); end
|
|
198
|
+
def chromatic_lime(s); paint(s, :chromatic_lime, 2); end
|
|
199
|
+
def chromatic_white(s); paint(s, :chromatic_white, 2); end
|
|
200
|
+
|
|
201
|
+
# Variantes en español
|
|
202
|
+
def cromatico_red(s); paint(s, :cromatico_red, 2); end
|
|
203
|
+
def cromatico_green(s); paint(s, :cromatico_green, 2); end
|
|
204
|
+
def cromatico_cyan(s); paint(s, :cromatico_cyan, 2); end
|
|
205
|
+
def cromatico_blue(s); paint(s, :cromatico_blue, 2); end
|
|
206
|
+
def cromatico_pink(s); paint(s, :cromatico_pink, 2); end
|
|
207
|
+
def cromatico_yellow(s); paint(s, :cromatico_yellow, 2); end
|
|
208
|
+
def cromatico_orange(s); paint(s, :cromatico_orange, 2); end
|
|
209
|
+
def cromatico_purple(s); paint(s, :cromatico_purple, 2); end
|
|
210
|
+
def cromatico_magenta(s); paint(s, :cromatico_magenta, 2); end
|
|
211
|
+
def cromatico_aqua(s); paint(s, :cromatico_aqua, 2); end
|
|
212
|
+
def cromatico_lime(s); paint(s, :cromatico_lime, 2); end
|
|
213
|
+
def cromatico_white(s); paint(s, :cromatico_white, 2); end
|
|
214
|
+
|
|
215
|
+
# Abreviaturas comunes para prototipado veloz
|
|
216
|
+
def r(s); bright_red(s); end
|
|
217
|
+
def dr(s); dark_red(s); end
|
|
218
|
+
def g(s); bright_green(s); end
|
|
219
|
+
def y(s); bright_yellow(s); end
|
|
220
|
+
def w(s); bright_white(s); end
|
|
221
|
+
def gr(s); gray(s); end
|
|
222
|
+
def cy(s); bright_cyan(s); end
|
|
223
|
+
def mg(s); bright_magenta(s); end
|
|
224
|
+
def bl(s); bright_blue(s); end
|
|
225
|
+
|
|
226
|
+
# Pinta texto en negrita (bold)
|
|
227
|
+
def bold(s)
|
|
228
|
+
"#{BOLD}#{s}#{RESET}"
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Pinta texto con color hexadecimal directo
|
|
232
|
+
def hex(code, text)
|
|
233
|
+
c = GRmenu.ansi_color(code.to_s)
|
|
234
|
+
"#{c}#{text}#{RESET}"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
238
|
+
m = method_name.to_s
|
|
239
|
+
GRmenu::COLORS.key?(m) || GRmenu::COLORS.key?(m.sub(/\Acromatico_/, 'chromatic_')) || super
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Permite invocar dinámicamente cualquier color cargado en colors.json o alias cromáticos
|
|
243
|
+
def method_missing(method_name, *args, &block)
|
|
244
|
+
m_str = method_name.to_s
|
|
245
|
+
lookup = m_str
|
|
246
|
+
unless GRmenu::COLORS.key?(lookup)
|
|
247
|
+
if lookup.start_with?("cromatico_")
|
|
248
|
+
lookup = lookup.sub(/\Acromatico_/, 'chromatic_')
|
|
249
|
+
elsif lookup.start_with?("chromatic_")
|
|
250
|
+
lookup = lookup.sub(/\Achromatic_/, 'cromatico_')
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
if GRmenu::COLORS.key?(lookup)
|
|
254
|
+
text = args[0].to_s
|
|
255
|
+
lvl = args[1] || 2
|
|
256
|
+
paint(text, m_str, lvl)
|
|
257
|
+
else
|
|
258
|
+
super
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Alias rápido para el módulo Color
|
|
264
|
+
C = Color
|
|
265
|
+
end
|