[#_lesson_20_menus_and_screens] == Lección 20: Menús y pantallas image::learn/20-gui.png[La lección terminada, 480] Puedes construir un menú con lo que ya sabes. Una caja, algo de texto, un cursor que se mueve cuando se mueve el stick, un `if` para cada opción, y aritmética para que todo siga alineado cuando añadas una cuarta opción. Funciona. También son unas cien líneas para una página con tres botones, y cada una de esas líneas te toca mantenerla a ti. Singe lleva dentro todo un motor de documentos -- RmlUi -- y un menú escrito para él es una página, como una página web, con una hoja de estilo. Reparte él solo su texto en líneas. Se vuelve a maquetar solo cuando añades una opción. Sabe qué es un botón, qué es un deslizador y a qué control está apuntando el jugador. Esta lección construye con él un menú de pausa, sobre un juego que sigue funcionando por detrás. [#_two_files] === Dos archivos Una página de GUI son dos cosas: un *documento* que dice qué hay en la página, y *Lua* que dice qué hace la página. Viven en archivos separados, y esa es toda la gracia del ejercicio -- la maquetación deja de ser código. Empieza con un script llamado `menu.singe` y un juego tan pequeño que cabe en un párrafo: una nave que cruza la pantalla y una puntuación que sube cada vez que llega al otro lado. Llevas escribiendo este tipo de cosas desde la lección siete. [source,lua] ---- dofile("Singe/Framework.singe") overlaySetResolution(discGetWidth(), discGetHeight()) local ship = spriteLoad(DIR .. "art/spaceship.png") local shipX = 0 local speed = 2 local score = 0 function onOverlayUpdate() overlayClear() shipX = shipX + speed if shipX > overlayGetWidth() then shipX = -spriteGetWidth(ship) score = score + 10 end spriteDraw(ship, shipX, 300) return OVERLAY_UPDATED end ---- Eso funciona. Ahora el documento. Junto a tu script, crea un archivo llamado `pause.rml`: [source,html] ---- Pause

Paused

Score 000000

Arrow keys move, Return chooses.

---- Si alguna vez has visto el código fuente de una página web, esto te sonará. Si no, aquí tienes todo lo que necesitas para leerlo. Un documento está hecho de *elementos*. Un elemento empieza con un nombre entre corchetes angulares, `

`, termina con el mismo nombre y una barra, `

`, y lo que haya entre los dos está dentro de él. Algunos elementos no tienen nada dentro y se cierran solos con una barra al final, como el `` de arriba. Los elementos se anidan, y la sangría muestra el anidamiento igual que lo muestra en Lua. Las palabras que hay dentro de la etiqueta de apertura son *atributos*, y se escriben `name="value"`. Dos de ellos te importan más que el resto. `id` le da a un elemento un nombre por el que tu script puede encontrarlo, que es como se hablan Lua y el documento. `class` mete el elemento en un grupo al que puede apuntar la hoja de estilo; `panel`, `muted` y `list` son grupos que la hoja de estilo del propio motor ya conoce. El lenguaje de marcado se llama RML y el lenguaje de estilos se llama RCSS. Son de RmlUi, no de Singe. El capítulo sobre la GUI de la referencia te remite a la documentación de RmlUi para la lista completa de lo que puedes escribir, y tarde o temprano la querrás. Todo lo de esta lección funciona sin ella. [#_show_it] === Muéstrala De vuelta al script. Encima de `onOverlayUpdate`, añade: [source,lua] ---- local gui = guiNew(overlayGetWidth(), overlayGetHeight()) local page = guiLoad(gui, DIR .. "pause.rml") ---- `guiNew` crea una *GUI*: un rectángulo de tamaño fijo en el que se maquetan y se dibujan los documentos. Devuelve un handle, igual que `fontLoad` y `spriteLoad`. Hacerla del tamaño del overlay significa que un píxel del documento es un píxel de tu overlay, y así las cuentas que haces de cabeza siguen siendo sencillas. `guiLoad` lee un documento en esa GUI y devuelve un segundo handle, para el propio documento. Casi todas las demás llamadas reciben los dos: la GUI y el documento que hay en ella. Una GUI puede contener hasta treinta y dos documentos -- una página de título, una de opciones y una de fin de partida pueden compartir una -- y pueden existir hasta dieciséis GUI a la vez. Todavía no hay nada en pantalla, porque cargar un documento no lo dibuja. Añade una línea a `onOverlayUpdate`, justo antes del `return`: [source,lua] ---- guiDraw(gui) ---- Ejecútalo. Ahí está el panel, encima de la nave, con su título, sus tres botones y su deslizador, y no has escrito nada de código de dibujo. `guiDraw` compone la GUI sobre el overlay *solo para este fotograma*, como todas las demás llamadas de dibujo, así que va en `onOverlayUpdate` y hay que volver a llamarla en el fotograma siguiente. Dónde la pongas decide qué queda encima: todo lo que dibujaste antes queda debajo, y todo lo que dibujes después va por encima. Un punto de mira dibujado después de `guiDraw` nunca queda tapado por el menú. [#_let_the_player_use_it] === Deja que el jugador la use Haz clic en un botón. No pasa nada, y el botón ni siquiera se ilumina. Una GUI nueva es un dibujo, no un panel de control. Es a propósito: un HUD o un cartel no deberían tragarse el botón de disparo. Para que una página reciba entrada: [source,lua] ---- guiSetInput(gui, true) ---- Ahora el ratón funciona, los botones se iluminan bajo el puntero y las teclas de flecha saltan de un control a otro, porque el documento lo pidió con `nav: auto`. En un mando o en un stick, las cuatro direcciones llegan como las teclas de flecha, el primer botón de acción llega como Intro y el segundo llega como Esc. Una recreativa con un joystick y dos botones maneja esta página sin saber que es un documento. Del control sobre el que actúan las teclas se dice que tiene el *foco*, y un botón con el foco lleva un borde dorado. El foco empieza en Resume porque ese botón dice `autofocus`: cada vez que se muestra la página, el foco va al control marcado así. Si lo quitas, las teclas de flecha no tienen desde dónde moverse hasta que el jugador hace clic en algo, lo que en una recreativa sin ratón no pasa nunca. Pero hacer clic sigue sin hacer nada, porque no hay nada escuchando. [#_talk_to_it] === Háblale Tres llamadas unen el documento a tu script, y entre las tres lo hacen casi todo. [source,lua] ---- guiSetHandler(gui, page, "resume", "click", function() closeMenu() end) ---- `guiSetHandler` dice: cuando el elemento con este `id` haga esto, llama a esta función. El `id` es el del documento. El nombre del evento es de RmlUi: `"click"` para los botones, `"change"` para cualquier cosa que el jugador ajuste, y una larga lista de otros que puedes consultar cuando los necesites. Se guarda una función por elemento y por evento, así que poner otra sustituye a la anterior, y pasar `nil` en lugar de una función la quita. Tu función se llama con cinco argumentos -- la GUI, el documento, el id, el evento y el valor actual del elemento como cadena. Recoge los que quieras e ignora el resto, igual que ya ignoras argumentos en Lua. [source,lua] ---- guiSetHandler(gui, page, "speed", "change", function(g, d, id, event, value) speed = math.floor(tonumber(value)) end) ---- El deslizador entrega su valor como cadena, y RmlUi escribe los números a su manera, así que `4` llega como `"4.000000"`. `tonumber` convierte la cadena en un número y `math.floor` tira los decimales. Saltarte ese paso y usar la cadena como si fuera un número es un error que cometerás una vez. [source,lua] ---- guiSetValue(gui, page, "score", string.format("%06d", score)) ---- `guiSetValue` escribe en un elemento. En un control de formulario pone el valor del control; en cualquier otra cosa -- un párrafo, un `div`, el `` de tu documento -- sustituye lo que hay dentro del elemento, y la página se vuelve a maquetar en el fotograma siguiente. `guiGetValue` es la misma llamada al revés, y responde `nil` para un id que no tiene ningún elemento, así que puedes preguntar por un elemento que quizá no exista sin morir en el intento. Esa es toda la API del día a día: `guiSetHandler`, `guiSetValue`, `guiGetValue`. Hay muchísimo más a tu alcance a través de los propios objetos Lua de RmlUi, a los que se llega desde la variable global `rmlui`, y el capítulo sobre la GUI de la referencia explica cómo. Llegarás muy lejos antes de necesitarlo. [#_hide_it_again] === Vuelve a esconderla Un menú de pausa no está a la vista todo el tiempo. `guiHide` quita un documento de la pantalla y `guiShow` lo vuelve a poner, conservando todo lo que el jugador haya escrito o elegido. La pausa en sí es `singeSetPauseFlag(true)`, que detiene el disco, los vídeos y los sonidos pero *sigue llamando a tus callbacks*, así que todavía puedes dibujar. El motor tiene su propia tecla de pausa, que congela el script por completo, y las dos se pelearían; `singeSetPauseKeyEnabled(false)` le quita esa tecla al motor para que `SWITCH_PAUSE` llegue a `onInputPressed` como cualquier otro interruptor. En un teclado, esa tecla es la *P*. [source,lua] ---- local function closeMenu() paused = false singeSetPauseFlag(false) guiHide(gui, page) guiSetInput(gui, false) end local function openMenu() paused = true singeSetPauseFlag(true) guiSetValue(gui, page, "score", string.format("%06d", score)) guiShow(gui, page) guiSetInput(gui, true) end ---- Apagar la entrada junto con la página no es decoración. Una página escondida que sigue recibiendo entrada se come las teclas de flecha del jugador durante el resto de la partida. [#_what_the_page_does_not_use_you_still_get] === Lo que la página no usa te sigue llegando Esta es la regla que hace que sea seguro dejar una GUI a la vista durante la partida, y vale la pena decirla claramente: *un evento que ha usado un elemento nunca llega a tus callbacks.* Un Intro que ha pulsado el botón que tiene el foco no llega como `onInputPressed`. Una flecha que ha movido el foco no llega como `SWITCH_LEFT`. Un clic en un botón no es un interruptor del ratón. Todo lo que la página no ha usado la atraviesa y te llega como siempre. Por eso `P` cierra este menú: ningún elemento del documento usa `P`, así que la tecla llega a `onInputPressed` exactamente igual que cuando el menú no está. También es por eso que un HUD que recibe entrada sigue dejando pasar el botón de disparo. Hay una tecla que no sigue la regla. RmlUi usa la tecla Tab para mover el foco y nunca la devuelve, así que Singe solo le pasa Tab a una GUI mientras alguien está escribiendo de verdad en un campo de texto. Un juego que asigna Tab a algo la conserva. [#_style_it] === Dale estilo Vuelve a mirar el `` del documento. Una sola línea hace casi todo el trabajo: [source,html] ---- ---- Ese es el tema que trae el motor, y es la razón de que tu panel tenga un borde, un degradado y un título dorado sin que tú hayas elegido nada de eso. Sin una hoja de estilo, RmlUi casi no dibuja nada, porque todos los elementos empiezan en línea y del tamaño de su contenido, así que enlazar esta primero y sobrescribir lo que no te guste es el camino más rápido hacia una página que parezca algo. El tema te da `.panel` (una caja con borde y esquinas redondeadas), `.list` (una caja con desplazamiento cuyas filas `div` hijas se resaltan y reciben el foco), `.muted` (texto más tenue para las pistas), títulos, y todos los controles de formulario con sus estados al pasar el ratón, con el foco y pulsados. El capítulo sobre la GUI de la referencia los enumera. Todo lo que va después del enlace es tuyo. El bloque `