De entrada, una mejor opción es ofrecer al usuario opciones que ya conoce, como subrayar determinada letra, para invitarle a pulsar dicha tecla, es decir relacionar la tecla con dicho menú.
Así se pasaría de este menú soseras:
1. UNO
2. DOS
3. TRES
4. CUATRO
5. ESC - SALIR
...a uno más elaborado así:
UNO
DOS
TRES
CUATRO
SUB
MENU »
SALIR (Esc)
Nota que ahora, cuando se pulsa una tecla se revisa si es alguna de las subrayadas, en vez de números. Las teclas Up y Down (left y Right) siguen haciendo lo mismo, moverse entre opciones deseleccionando el actual que pasa a ser el previo y seleccionado el nuevo que pasa a ser el actual, y al tiempo seleccionas el carácter para dicho menú... es decir tu tendrás un string tal que UDTCMS, y al tiempo de subir y bajar el cursor, paseas entre dichas letras, tal que cuando se pulse la tecla enter, haces equivaler esa letra como la opción.
Así tendrías:
char opcion, previa
entero actual
Cuando el usuario pulsa una tecla:
previa = opcion
si console.readkey = enter
opcion = "UDTCMS".CharAt(actual)
sino
opcion = console.readkey
fin si
llamada a menuAcciones
Verificar si la tecla pulsada 'opcion' es alguna para la que tenemos una acción definida, en cuyo caso ejecutamos su código.
funcion menuAcciones
seleccionar caso para opcion
caso "U"
// acción en este caso
caso "D"
// acción en este caso
caso "T"
// acción en este caso
caso "C"
// acción en este caso
caso "M"
// acción borrar el menú actual y mostrar las opciones del submenú elegido.
caso "S", consolekey.escape
// acción en este caso, salir...
resto de casos // no se admiten
opcion = previa
fin seleccion
fin funcion
Nota que he añadido un submenú
Segundo. Recuerda que puedes cambiar la fuente que se usa en la consola. Hay fuentes que contienes los glifos muy apropiados para determinadas cosas... por ejemplo hay fuentes que tienen las teclas (fuente: Game Key 2), muchas flechitas (fuente: Game control) símbolos (fuente: Alchemy), circulos como los que quieres para señalar el nivel de brillo (fuente: Almanac Mt) que tiene para las fases de la luna, los símbolos para luna llena y luna nueva, te pueden valer para lo que quieres... pero vamos hay muchísimas fuentes... tómate 1 día para verlas y elegir la que mejor se acomode. ten en cuenta que puede que al elegir un menú tengas que seleccionar una fuente y luego al slair de él, volver a la fuente previa. Y recuerda también que si el programa no son solo pruebas para ti, sino que es algo que será compilado y repartido, si las fuentes no son las habituales que contiene-mantiene el S.O. será conforme que se acompañen con el programa.
Ah... eso de "Alum." suena más a aluminio que a alumbrar, te sugiero que uses "Nivel de brillo" mejor, y si es demasiado largo, solo "Brillo
+" y "Brillo
-", y que responda a subir y bajar el brillo no solo las flechas de cursor, UP=right= + y Down=left= - es decir 3 teclas como equivalentes de la misma acción.
Por la misma razón, si la opción 3 ya tiene asignada una acción, no la llames
TRES, llámala
BRILLO y como aloja un submenú, síguele con una flechita de indicador de submenú:
BRILLO »
UNO
DOS
BRILLO »
CUATRO
SUB
MENU »
SALIR (Esc)
Así ahora el submenú que se abre al pulsar B (o tener seleccionada la opcion actual=2 (contando 0, es la 3ª), sería así:
SUBIR BRILLO +
BAJAR BRILLO -
--------------------------
NIVEL ●●●●○○○○
--------------------------
VOLVER
Y por fin, te señalo como hacer un menú consistente, del que puedas hacer cosas más o menos complejas, pero asequibles, facilitando enormemente la tarea.
Lo ideal es que crees una clase Menu, y añadas métodos... por ejemplo en el 'New', le pasas el string (que pondré unas líneas más abajo), como un array de strings... que luego la clase debe procesar.
Dibujar un menú es tener elaborado un pequeño menú, como operas con consola, puede ser elaborado enteramente en string... sigue leyendo...
-1 Raiz 7 -2
00 0UNO 0 -1
01 0DOS 0 -1
02 0BRILLO 6 -1
03 0SUBIR BRILLO + 0 2
04 0BAJAR BRILLO - 0 2
05 ---------------- 0 2
06 NIVEL (N) 0 2
07 ---------------- 0 2
08 0VOLVER 0 2
09 0CUATRO 0 -1
10 1CINCO 0 -1
11 ---------------- 0 -1
12 0SALIR 0 -1
Si te das cuenta, poner un tabulador (en el foro espacios, porque no deja poner tabuladores), es más por ver visualmente la jerarquía del menú y corregir si algo no está bien... ya que la jerarquía real se expresa con los números en las columnas de la derecha.
También si te fijas, hay varias columna...
Esto sugiere pués crear una estructura Item, debajo las explicaciones de dicha estructura y relacionarla con esas línea de texto que contienene el menú:
Estructura Item
entero indice //índice absoluto del menú.
entero hotspot // índice del caracter que está subrayado (el que activa su acción)
string nombre // el texto que se muestra.
entero hijos // cantidad de hijos que tiene dicho submenú.
// El índice de los mismo, empieza en el índice absoluto siguiente al padre.
// ojo, para localizar sus hijos y no sus nietos, se deben contar solo los que
entero padre // índice absoluto del menú padre. puede ser 0 para todos excepto para esos de volver.
// pero con menús complejos mejor que apunten al padre, así sabremos buscar si un menú es hijo o no de otro.
fin estructura
Inicialmente se parte de un array de esta estructura, que se crea a partir del string anteriormente indicado.
Visiblemente no está muy logrado (no me apetece perder demasiado tiempo en ello), pero te describo cada columna.
- La columna 0 en realidad es innecesaria reflejarla en el string. Es el índice correlativo (es decir la enésima línea), que viene a coincidir con el índice en el array, la he puesto solo para facilitar señalar a qué indice apuntar como padre. En la estructura si debe hacerse constar, pués en caso de tener hijos, empieza en el índice siguiente a éste, por lo que es preciso conocerlo.
- La columna 1 es un dígito (0-9) indica que carácter del texto estará subrayado (hotspot en la estructura). Si no tiene dígito, en la estructura debe consignarse -1. A la hora de dibujar dicho ítem, se considera si el número es mayor o igual a 0, en cuyo caso la letra en dicha posición se subraya (también vale dibujarla de otro color o ponerla en negrita).
- La columna 2 contiene el texto del menú (si te gusta todo en mayúsculas allá tú). Campo 'nombre' de la estructura.
- La columna 3 es un número, señala cuantos hijos tiene. Si un menú tiene hijos, tendrá un valor mayor que 0, y servirá para en un bucle localizarlos y saber cuando terminar... cuando se seleccione un ítem tu invocas a tu clase:
Si menu.Item(x).tieneHijos
dibujarSumenuDe(menu, item(x))
sino
llamada a ejecutarAccion(x) // la función que mas arriba llamamos menuAcciones
fin si
servirá también para a la hora de dibujar el ítem del menú, dibujar el " »" a la derecha del nombre del menú. Es decir, se añade el " »" detrás del nombre.
- La columna 4 es un número, señala quien es el padre de dicho menú. Observa como todo el submenú de brillo (indice 2), tiene como padre precisamente el valor 2 (indices 3 a 8). Un valor -1 indica que están el padre es raíz, es decir que no admite referencia al padre.
- Una posible columna 5 podría indicar otras opciones como fuente (supongamos que usas 5 fuentes distintas, un dígito indicaría que fuente usar) . nuevamente solo los ítems que son submenús tendrían un digito a considerar, para el resto un valor -1...
Este sería el code para el ítem '
Volver de un submenú...
if menu.item(x).padre > -1
dibujarSubmenuDe(menu, menu.getItemPadre(menu.item(x).padre))
fin si
Pués que el único menú
Volver en raíz, sería el equivalente a
Salir, no habrá opción de apuntar a un padre inexistente, pués solo sucede en los menús volver... es decir dicho valor se podría ignorar para todos (tener valor 0) excepto para los ítems volver. se podría modificar así:
if menu.item(x).padre => 0
dibujarSubmenuDe(menu, menu.getItemPadre(menu.item(x).padre))
sino
Salir
fin si
Por último, recuerda separar adecuadamente el tratamiento de datos del procesado de dibujado.
Para el procesado de los datos, sugiero una clase menú, que maneje ítems. Y un array de estructuras Item.
Para el dibujado también sugiero encapsular todo el proceso en una clase que en un método dibujar, recibe como parámetro la clase menú (para tener acceso a los miembros de la clase) y un ítem del menú, (el programa tiene un ítem -1 que es raíz y que despliega el menú principal, que suele ser un menú horizontal, del que descuelgan luego 'las ramas'), y es éste ítem para el que a través del menú dibujará sus ítems hijos, observando cuantos hijos tiene, sabiendo que empiezan un índice después y que sus hijos son aquellos que tiene en el campo padr,e el índice de dicho ítem.
La clase principal, es la que muestra el resultado y la que intercede entre el menú, el usuario y la clase dibujado.
La clase de dibujar, puede alojar también opciones sobre la fuente usada, colores a usar en cada caso, etc... si se simplifica al extremo, quizás no requiera usar una clase para ello, es decir contener un simple método "dibujarMenu", no justifica una clase.
...confío que te ilustre lo sufieciente para mejorar tu esquema aunque lo complique un poco y tardes más, a futuro tendrás un base adecuada para proyectos más o menos complejos... que te ayudarían a ahorrar mucho tiempo.
P.d:
Cuando solicitas al menú que te devuelve un submenú, también se crea una cadena o array 'hotkeys', que se compone exactamente de cada carácter hotkey de cada ítem del menú. Si un ítem no tiene hotkey, para ello se deja como carácter un char 'nulo', es decir un carácter que no sea pulsable por teclado, o dicho de otra manera, que no sean las teclas usadas 'enter, up, down, right, left, escape', ni letras o dígitos, por ejemplo '*' así de una sola evaluación puedes ver si hay que perder tiempo o no en procesar la tecla pulsada...
por ejemplo la cadena hotkeys para el submenú brillo, podría ser: hotkeys = "SB***V" (de Subir, Bajar y Volver, las otros ítems del menú no generan acciones, una muestra el nivel del brillo y las otras dos son separadores de menús).
opcion = console.readkey
k = InString(hotkeys, opcion) // devuelve el índice en el substring, que nos da el índice del menú pulsado (si existe)
Si (k >= 0)
si tecla es distinto de *
llamada a CambiarFoco(k)
llamada a ejecutarAccion(actual)
fin si
sino
// todavía puede ser enter, up, down, +, - left, right, pero estas es mejor procesarlas en otra función aparte
seleccionar opcion
caso enter
llamada a ejecutarAccion(actual)
caso up, right, +
si (actual < maxItems)
CambiarFoco(actual + 1)
fin si
caso down, left, -
si (actual > 0)
CambiarFoco(actual - 1)
fin si
fin seleccion
fin si
funcion CambiarFoco(entero ahora)
previo = actual
deseleccionar previo
actual = ahora
seleccionar actual
fin funcion
Si el menú tiene demasiados submenús, entre medias, al no estar seguidos los ítems, 'hermanos' exigirá una búsqueda, para obtener su índice absoluto, que es el que luego se habrá de seleccionar (bloque select case) para poder ejecutar la acción asociada... puede ahorrarse la búsqueda si se añade 2 columnas más, sobre el menú, donde se enlaza un item a su hermano siguiente y previo... Es decir complicando un poco más el menú, hacemos que sea más rápido el acceso.
Añadiendo ambos ´campos a la estructura:
Estructura Item
entero indice
entero hotspot
string nombre
entero padre
entero hijos
entero siguiente // el índice absoluto de su hermano siguiente (columna 5)
entero previo // el índice absouto de su hermano previo (columna 6)
fin estructura
Aquí como quedaría añadiendo esas 2 columnas nuevas a la derecha. El guión viene a indicar que no importa, no son seleccionables, no dan ancceso desde ellos al anterior y/o sigiente.
-1 Raiz 7 -2 - -
00 0UNO 0 -1 1 -
01 0DOS 0 -1 2 0
02 0BRILLO 6 -1 9 1
03 0SUBIR BRILLO + 0 2 4 -
04 0BAJAR BRILLO - 0 2 8 3
05 ---------------- 0 2 - -
06 NIVEL (N) 0 2 - -
07 ---------------- 0 2 - -
08 0VOLVER 0 2 - 4
09 0CUATRO 0 -1 10 2
10 1CINCO 0 -1 12 9
11 ---------------- 0 -1 - -
12 0SALIR 0 -1 - 10
También en el código debe mantenerrse la estructura del primer ítem, en pantalla, para obtener el índice absoluto en el array y poder obtener datos del resto de hermanos...