[#_lesson_20_menus_and_screens] == Leçon 20 : Menus et écrans image::learn/20-gui.png[La leçon terminée, 480] Vous pouvez construire un menu avec ce que vous savez déjà. Une boîte, un peu de texte, un curseur qui bouge quand le stick bouge, un `if` pour chaque choix, et de l'arithmétique pour garder tout ça aligné quand vous ajoutez une quatrième option. Ça marche. C'est aussi une centaine de lignes pour une page avec trois boutons, et chacune de ces lignes, c'est à vous de l'entretenir. Singe contient tout un moteur de documents -- RmlUi -- et un menu écrit pour lui est une page, comme une page web, avec une feuille de style. Elle fait passer son texte à la ligne toute seule. Elle se met en page d'elle-même quand vous ajoutez une option. Elle sait ce qu'est un bouton, ce qu'est un curseur, et quel contrôle le joueur est en train de viser. Cette leçon s'en sert pour construire un menu de pause, par-dessus un jeu qui continue de tourner derrière. [#_two_files] === Deux fichiers Une page de GUI, c'est deux choses : un *document* qui dit ce qu'il y a sur la page, et du *Lua* qui dit ce que fait la page. Ils vivent dans des fichiers séparés, et c'est tout l'intérêt de l'exercice -- la mise en page cesse d'être du code. Commencez par un script appelé `menu.singe` et un jeu si petit qu'il tient en un paragraphe : un vaisseau qui traverse l'écran, et un score qui augmente chaque fois qu'il arrive de l'autre côté. Vous écrivez ce genre de chose depuis la leçon sept. [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 ---- Ça tourne. Maintenant, le document. À côté de votre script, créez un fichier appelé `pause.rml` : [source,html] ---- Pause

Paused

Score 000000

Arrow keys move, Return chooses.

---- Si vous avez déjà vu le code source d'une page web, ça vous dira quelque chose. Sinon, voici tout ce qu'il vous faut pour le lire. Un document est fait d'*éléments*. Un élément commence par un nom entre chevrons, `

`, finit par le même nom avec une barre oblique, `

`, et tout ce qui se trouve entre les deux est à l'intérieur. Certains éléments n'ont rien à l'intérieur et se ferment eux-mêmes avec une barre oblique à la fin, comme le `` ci-dessus. Les éléments s'imbriquent, et l'indentation montre l'imbrication de la même façon qu'en Lua. Les mots à l'intérieur de la balise ouvrante sont des *attributs*, écrits `name="value"`. Deux d'entre eux vous concernent plus que les autres. `id` donne à un élément un nom grâce auquel votre script peut le trouver, et c'est ainsi que Lua et le document se parlent. `class` met l'élément dans un groupe que la feuille de style peut viser ; `panel`, `muted` et `list` sont des groupes que la feuille de style du moteur connaît déjà. Le langage de balisage s'appelle RML et le langage de style s'appelle RCSS. Ce sont ceux de RmlUi, pas ceux de Singe. Le chapitre GUI de la Référence renvoie à la documentation de RmlUi pour la liste complète de ce que vous pouvez écrire, et vous en aurez besoin un jour. Tout ce qui est dans cette leçon fonctionne sans. [#_show_it] === Affichez-la Retour au script. Au-dessus de `onOverlayUpdate`, ajoutez : [source,lua] ---- local gui = guiNew(overlayGetWidth(), overlayGetHeight()) local page = guiLoad(gui, DIR .. "pause.rml") ---- `guiNew` crée une *GUI* : un rectangle de taille fixe dans lequel des documents sont mis en page et dessinés. L'appel renvoie un handle, comme le font `fontLoad` et `spriteLoad`. La faire de la taille de l'overlay veut dire qu'un pixel du document est un pixel de votre overlay, ce qui garde simple l'arithmétique que vous faites de tête. `guiLoad` lit un document dans cette GUI et renvoie un second handle, pour le document lui-même. Presque tous les autres appels prennent les deux : la GUI et le document qu'elle contient. Une GUI peut contenir jusqu'à trente-deux documents -- une page de titre, une page d'options et une page de fin de partie peuvent en partager une -- et il peut exister jusqu'à seize GUI à la fois. Rien n'est encore à l'écran, parce que charger un document ne le dessine pas. Ajoutez une ligne à `onOverlayUpdate`, juste avant le `return` : [source,lua] ---- guiDraw(gui) ---- Lancez-le. Le panneau est là, par-dessus le vaisseau, avec son titre, ses trois boutons et son curseur, et vous n'avez écrit aucun code de dessin. `guiDraw` superpose la GUI à l'overlay *pour cette image seulement*, comme tous les autres appels de dessin, donc sa place est dans `onOverlayUpdate` et il faut l'appeler de nouveau à l'image suivante. L'endroit où vous la mettez décide de ce qui est au-dessus : tout ce que vous avez dessiné avant est en dessous, et tout ce que vous dessinez après passe par-dessus. Un réticule dessiné après `guiDraw` n'est jamais caché par le menu. [#_let_the_player_use_it] === Laissez le joueur s'en servir Cliquez sur un bouton. Il ne se passe rien, et le bouton ne s'allume même pas. Une nouvelle GUI est une image, pas un tableau de commandes. C'est voulu : un HUD ou une pancarte ne doit pas avaler le bouton de tir. Pour qu'une page accepte les entrées : [source,lua] ---- guiSetInput(gui, true) ---- Maintenant la souris fonctionne, les boutons s'allument sous le pointeur, et les touches fléchées passent d'un contrôle à l'autre, parce que le document l'a demandé avec `nav: auto`. Sur une manette ou un stick, les quatre directions arrivent comme les touches fléchées, le premier bouton d'action arrive comme Entrée, et le second comme Échap. Une borne avec un joystick et deux boutons pilote cette page sans savoir que c'est un document. On dit que le contrôle sur lequel agissent les touches a le *focus*, et un bouton qui a le focus porte une bordure dorée. Le focus démarre sur Resume parce que ce bouton porte `autofocus` : chaque fois que la page s'affiche, le focus va au contrôle marqué ainsi. Retirez-le et les touches fléchées n'ont aucun point de départ tant que le joueur n'a pas cliqué quelque part, ce qui, sur une borne sans souris, n'arrive jamais. Pourtant, cliquer ne fait toujours rien, parce que personne n'écoute. [#_talk_to_it] === Parlez-lui Trois appels relient le document à votre script, et à eux trois ils font presque tout. [source,lua] ---- guiSetHandler(gui, page, "resume", "click", function() closeMenu() end) ---- `guiSetHandler` dit : quand l'élément qui porte cet `id` fait telle chose, appelle cette fonction. Cet `id` est celui du document. Le nom de l'événement est celui de RmlUi : `"click"` pour les boutons, `"change"` pour tout ce que le joueur règle, et une longue liste d'autres que vous pourrez chercher quand vous en aurez besoin. Une seule fonction est gardée par élément et par événement, donc en régler une autre la remplace, et passer `nil` au lieu d'une fonction la retire. Votre fonction est appelée avec cinq arguments -- la GUI, le document, l'id, l'événement, et la valeur actuelle de l'élément sous forme de chaîne. Prenez-en autant que vous voulez et ignorez le reste, comme vous ignorez déjà des arguments en Lua. [source,lua] ---- guiSetHandler(gui, page, "speed", "change", function(g, d, id, event, value) speed = math.floor(tonumber(value)) end) ---- Le curseur transmet sa valeur sous forme de chaîne, et RmlUi formate les nombres à sa façon, donc `4` arrive sous la forme `"4.000000"`. `tonumber` transforme la chaîne en nombre et `math.floor` jette la partie décimale. Sauter cette étape et utiliser la chaîne comme un nombre est une erreur que vous ferez une fois. [source,lua] ---- guiSetValue(gui, page, "score", string.format("%06d", score)) ---- `guiSetValue` écrit dans un élément. Pour un contrôle de formulaire, elle règle la valeur du contrôle ; pour tout le reste -- un paragraphe, une `div`, le `` de votre document -- elle remplace ce qu'il y a dans l'élément, et la page refait sa mise en page à l'image suivante. `guiGetValue` est le même appel à l'envers, et elle répond `nil` pour un id que rien ne porte, si bien que vous pouvez demander des nouvelles d'un élément qui n'est peut-être pas là sans que tout plante. Voilà toute l'API de tous les jours : `guiSetHandler`, `guiSetValue`, `guiGetValue`. Il y a bien plus à disposition à travers les objets Lua propres à RmlUi, accessibles par la globale `rmlui`, et le chapitre GUI de la référence montre comment. Vous irez loin avant d'en avoir besoin. [#_hide_it_again] === Cachez-la de nouveau Un menu de pause n'est pas affiché tout le temps. `guiHide` retire un document de l'écran et `guiShow` le remet, en gardant tout ce que le joueur a tapé ou choisi. La pause elle-même, c'est `singeSetPauseFlag(true)`, qui arrête le disque, les vidéos et les sons mais *continue d'appeler vos callbacks*, si bien que vous pouvez encore dessiner. Le moteur a sa propre touche de pause, qui fige complètement le script, et les deux se battraient ; `singeSetPauseKeyEnabled(false)` retire cette touche au moteur pour que `SWITCH_PAUSE` arrive à `onInputPressed` comme n'importe quel autre contact. Sur un clavier, cette touche est *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 ---- Couper les entrées en même temps que la page n'est pas de la décoration. Une page cachée qui accepte encore les entrées mange les touches fléchées du joueur pour le reste de la partie. [#_what_the_page_does_not_use_you_still_get] === Ce que la page n'utilise pas, vous le recevez quand même C'est la règle qui permet de laisser une GUI affichée pendant le jeu sans danger, et elle mérite d'être dite clairement : *un événement qu'un élément a utilisé n'atteint jamais vos callbacks.* Un appui sur Entrée qui a actionné le bouton qui a le focus n'arrive pas comme `onInputPressed`. Une flèche qui a déplacé le focus n'arrive pas comme `SWITCH_LEFT`. Un clic sur un bouton n'est pas un contact de souris. Tout ce que la page n'a pas utilisé passe tout droit, comme d'habitude. C'est pour ça que `P` ferme ce menu : aucun élément du document n'utilise `P`, donc la touche arrive à `onInputPressed` exactement comme quand le menu n'est pas affiché. C'est aussi pour ça qu'un HUD qui accepte les entrées laisse quand même passer le bouton de tir. Une touche ne suit pas la règle. RmlUi utilise Tab pour déplacer le focus et ne rend jamais la touche, donc Singe ne donne Tab à une GUI que pendant qu'on est réellement en train de taper dans un champ de texte. Un jeu qui affecte Tab à quelque chose le garde. [#_style_it] === Mettez-la en forme Revenez au `` du document. Une ligne fait l'essentiel du travail : [source,html] ---- ---- C'est le thème que le moteur fournit, et c'est pour ça que votre panneau a une bordure, un dégradé et un titre doré sans que vous ayez rien choisi. Sans feuille de style, RmlUi ne dessine presque rien, parce que chaque élément part en ligne et à la taille de son contenu ; alors lier celle-ci en premier et redéfinir ce qui vous déplaît est le chemin le plus rapide vers une page qui ressemble à quelque chose. Le thème vous donne `.panel` (une boîte arrondie avec une bordure), `.list` (une boîte qui défile et dont les lignes `div` enfants sont mises en évidence et prennent le focus), `.muted` (un texte plus pâle pour les indications), des titres, et tous les contrôles de formulaire avec leurs états survol, focus et enfoncé. Le chapitre GUI de la Référence les liste. Tout ce qui vient après le lien est à vous. Le bloc `