[#_lesson_20_menus_and_screens] == Lezione 20: Menu e schermate image::learn/20-gui.png[La lezione finita, 480] Puoi costruire un menu con quello che sai già. Un riquadro, un po' di testo, un cursore che si sposta quando si sposta la levetta, un `if` per ogni scelta, e un po' di aritmetica per tenere tutto allineato quando aggiungi una quarta voce. Funziona. Sono anche un centinaio di righe per una pagina con sopra tre pulsanti, e ognuna di quelle righe tocca a te mantenerla. Singe ha al suo interno un intero motore per documenti -- RmlUi -- e un menu scritto per RmlUi è una pagina, come una pagina web, con un foglio di stile. Manda a capo il testo da sé. Si impagina da sé quando aggiungi una voce. Sa che cos'è un pulsante, che cos'è uno slider, e a quale controllo sta puntando il giocatore. Questa lezione ci costruisce un menu di pausa, sopra un gioco che continua a girare dietro. [#_two_files] === Due file Una pagina della GUI è fatta di due cose: un *documento* che dice che cosa c'è sulla pagina, e del *Lua* che dice che cosa fa la pagina. Stanno in file separati, ed è proprio questo il senso dell'esercizio -- l'impaginazione smette di essere codice. Comincia con uno script chiamato `menu.singe` e un gioco così piccolo che sta in un paragrafo: un'astronave che attraversa lo schermo, e un punteggio che sale ogni volta che arriva dall'altra parte. Scrivi cose del genere dalla settima lezione. [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 ---- Funziona. Adesso il documento. Accanto al tuo script, crea un file chiamato `pause.rml`: [source,html] ---- Pause

Paused

Score 000000

Arrow keys move, Return chooses.

---- Se hai mai visto il sorgente di una pagina web, ti sembrerà familiare. Se non l'hai mai visto, ecco tutto quello che ti serve per leggerlo. Un documento è fatto di *elementi*. Un elemento comincia con un nome tra parentesi angolari, `

`, finisce con lo stesso nome e una barra, `

`, e tutto quello che sta tra i due è al suo interno. Alcuni elementi non hanno niente dentro e si chiudono da soli con una barra alla fine, come l'elemento `` qui sopra. Gli elementi si annidano, e il rientro mostra l'annidamento nello stesso modo in cui lo mostra in Lua. Le parole dentro il tag di apertura sono *attributi*, scritti `name="value"`. Due ti interessano più degli altri. `id` dà a un elemento un nome con cui il tuo script può trovarlo, ed è così che Lua e il documento si parlano. `class` mette l'elemento in un gruppo a cui il foglio di stile può mirare; `panel`, `muted` e `list` sono gruppi che il foglio di stile del motore conosce già. Il linguaggio di markup si chiama RML e il linguaggio degli stili si chiama RCSS. Sono di RmlUi, non di Singe. Il capitolo sulla GUI del Riferimento rimanda alla documentazione di RmlUi per l'elenco completo di quello che puoi scrivere, e prima o poi ti servirà. Tutto quello che c'è in questa lezione funziona anche senza. [#_show_it] === Mostralo Torna allo script. Sopra `onOverlayUpdate`, aggiungi: [source,lua] ---- local gui = guiNew(overlayGetWidth(), overlayGetHeight()) local page = guiLoad(gui, DIR .. "pause.rml") ---- `guiNew` crea una *GUI*: un rettangolo di dimensione fissa in cui i documenti vengono impaginati e disegnati. Restituisce un handle, come fanno `fontLoad` e `spriteLoad`. Farla grande quanto l'overlay vuol dire che un pixel del documento è un pixel del tuo overlay, e così i conti che fai a mente restano semplici. `guiLoad` legge un documento dentro quella GUI e restituisce un secondo handle, per il documento stesso. Quasi tutte le altre chiamate li vogliono entrambi: la GUI e il documento che contiene. Una GUI può contenere fino a trentadue documenti -- una pagina del titolo, una pagina delle opzioni e una pagina di game over possono stare in una sola -- e possono esistere fino a sedici GUI contemporaneamente. Sullo schermo non c'è ancora niente, perché caricare un documento non lo disegna. Aggiungi una riga a `onOverlayUpdate`, subito prima del `return`: [source,lua] ---- guiDraw(gui) ---- Eseguilo. Il pannello è lì, sopra l'astronave, con il suo titolo, i suoi tre pulsanti e il suo slider, e non hai scritto nemmeno una riga di codice di disegno. `guiDraw` compone la GUI sopra l'overlay *solo per questo fotogramma*, come ogni altra chiamata di disegno, quindi va in `onOverlayUpdate` e va chiamata di nuovo al fotogramma successivo. Il punto in cui la metti decide che cosa sta sopra: tutto quello che hai disegnato prima sta sotto, e tutto quello che disegni dopo le va sopra. Un mirino disegnato dopo `guiDraw` non viene mai nascosto dal menu. [#_let_the_player_use_it] === Lascia che il giocatore lo usi Fai clic su un pulsante. Non succede niente, e il pulsante non si illumina nemmeno. Una GUI nuova è un'immagine, non un pannello di controllo. È voluto: un HUD o un cartello non devono inghiottire il pulsante di fuoco. Per fare in modo che una pagina riceva l'input: [source,lua] ---- guiSetInput(gui, true) ---- Adesso il mouse funziona, i pulsanti si illuminano sotto il puntatore, e con i tasti freccia ti sposti tra i controlli, perché il documento l'ha chiesto con `nav: auto`. Su un controller o una levetta le quattro direzioni arrivano come tasti freccia, il primo pulsante d'azione arriva come Invio, e il secondo arriva come Esc. Un cabinato con un joystick e due pulsanti usa questa pagina senza sapere che è un documento. Del controllo su cui stanno lavorando i tasti si dice che ha il *focus*, e un pulsante con il focus porta un bordo dorato. Il focus parte da Resume perché quel pulsante dice `autofocus`: ogni volta che la pagina viene mostrata, il focus va al controllo segnato così. Toglilo e i tasti freccia non hanno nessun punto da cui partire finché il giocatore non fa clic su qualcosa, il che, su un cabinato senza mouse, vuol dire mai. Fare clic però continua a non fare niente, perché nessuno sta ascoltando. [#_talk_to_it] === Parlagli Tre chiamate collegano il documento al tuo script, e da sole fanno quasi tutto. [source,lua] ---- guiSetHandler(gui, page, "resume", "click", function() closeMenu() end) ---- `guiSetHandler` dice: quando l'elemento con questo `id` fa questa cosa, chiama questa funzione. Questo `id` è quello scritto nel documento. Il nome dell'evento è di RmlUi: `"click"` per i pulsanti, `"change"` per qualsiasi cosa il giocatore regoli, e un lungo elenco di altri che puoi cercare quando ti servono. Viene tenuta una funzione per elemento e per evento, quindi impostarne un'altra sostituisce la precedente, e passare `nil` al posto di una funzione la rimuove. La tua funzione viene chiamata con cinque argomenti -- la GUI, il documento, l'id, l'evento e il valore attuale dell'elemento come stringa. Prendine quanti ne vuoi e ignora il resto, come già ignori gli argomenti in Lua. [source,lua] ---- guiSetHandler(gui, page, "speed", "change", function(g, d, id, event, value) speed = math.floor(tonumber(value)) end) ---- Lo slider consegna il suo valore come stringa, e RmlUi formatta i numeri a modo suo, quindi `4` arriva come `"4.000000"`. `tonumber` trasforma la stringa in un numero e `math.floor` butta via la parte decimale. Saltare questo passaggio e usare la stringa come numero è un errore che farai una volta. [source,lua] ---- guiSetValue(gui, page, "score", string.format("%06d", score)) ---- `guiSetValue` scrive dentro un elemento. Per un controllo di un modulo imposta il valore del controllo; per qualsiasi altra cosa -- un paragrafo, un `div`, lo `` nel tuo documento -- sostituisce quello che c'è dentro l'elemento, e la pagina si reimpagina al fotogramma successivo. `guiGetValue` è la stessa chiamata al contrario, e risponde `nil` per un id che nessun elemento ha, così puoi chiedere di un elemento che potrebbe non esserci senza morire. Ecco tutta l'API di tutti i giorni: `guiSetHandler`, `guiSetValue`, `guiGetValue`. C'è molto di più a disposizione tramite gli oggetti Lua di RmlUi, raggiungibili dalla variabile globale `rmlui`, e il capitolo sulla GUI del riferimento mostra come. Farai molta strada prima di averne bisogno. [#_hide_it_again] === Nascondilo di nuovo Un menu di pausa non sta sempre aperto. `guiHide` toglie un documento dallo schermo e `guiShow` lo rimette, conservando tutto quello che il giocatore ha scritto o scelto. La pausa vera e propria è `singeSetPauseFlag(true)`, che ferma il disco, i video e i suoni ma *continua a chiamare le tue callback*, così puoi ancora disegnare. Il motore ha un suo tasto di pausa, che congela completamente lo script, e i due si darebbero battaglia; `singeSetPauseKeyEnabled(false)` toglie quel tasto al motore, così `SWITCH_PAUSE` arriva a `onInputPressed` come qualsiasi altro interruttore. Sulla tastiera quel tasto è *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 ---- Spegnere l'input insieme alla pagina non è un abbellimento. Una pagina nascosta che riceve ancora l'input si mangia i tasti freccia del giocatore per il resto della partita. [#_what_the_page_does_not_use_you_still_get] === Quello che la pagina non usa arriva comunque a te Questa è la regola che rende sicuro lasciare una GUI aperta durante il gioco, e vale la pena dirla chiaramente: *un evento usato da un elemento non raggiunge mai le tue callback.* Un Invio che ha premuto il pulsante con il focus non arriva come `onInputPressed`. Una freccia che ha spostato il focus non arriva come `SWITCH_LEFT`. Un clic su un pulsante non è un interruttore del mouse. Tutto quello che la pagina non ha usato passa dritto come al solito. È per questo che `P` chiude questo menu: nessun elemento del documento usa `P`, quindi il tasto arriva a `onInputPressed` esattamente come quando il menu è chiuso. Ed è anche per questo che un HUD che riceve l'input lascia comunque passare il pulsante di fuoco. Un tasto non segue la regola. RmlUi usa Tab per spostare il focus e non lo restituisce mai, quindi Singe passa Tab a una GUI solo mentre si sta davvero scrivendo in un campo di testo. Un gioco che assegna Tab a qualcosa se lo tiene. [#_style_it] === Dagli uno stile Riguarda il `` del documento. Una riga fa la maggior parte del lavoro: [source,html] ---- ---- È il tema che il motore fornisce, ed è per questo che il tuo pannello ha un bordo, una sfumatura e un titolo dorato senza che tu abbia scelto niente di tutto ciò. Senza un foglio di stile RmlUi non disegna quasi niente, perché ogni elemento parte in linea e grande quanto il suo contenuto, quindi collegare prima questo e sovrascrivere quello che non ti piace è la strada più rapida verso una pagina che sembri qualcosa. Il tema ti dà `.panel` (un riquadro con bordo e angoli arrotondati), `.list` (un riquadro che scorre, le cui righe `div` figlie si evidenziano e prendono il focus), `.muted` (testo più tenue per i suggerimenti), i titoli, e ogni controllo dei moduli con i suoi stati di mouse sopra, focus e pressione. Il capitolo sulla GUI del Riferimento li elenca. Tutto quello che viene dopo il collegamento è tuo. Il blocco `