Le LSL de Second Life

Ce site est consacré principalement au langage LSL de Second Life...

« Retour aux scripts


Librairie pour la gestion de menus multiples


La gestion des menus avec LSL n'est pas toujours évidente et plusieurs solutions ont été proposées mais aucune ne me satisfait vraiment. J'ai un peu reconsidéré le problème en essayant de créer une librairie souple et solide.

Fonctionalités :


J'ai adopté la structure de commentaires de BlackShade parce que je la trouve très claire (merci à toi).

J'ai aussi abondamment commenté le code pour ceux qui désirent comprendre le fonctionnement.

-----------------------------------------------------------------------
Utilisation de la librairie
-----------------------------------------------------------------------

Vous pouvez regarder l'exemple dans l'état par défaut...

* Voici la syntaxe de l'initialisation d'un menu avec la fonction "InitMenu" :

InitMenu(clé avatar, texte du bouton, liste des boutons);


* Il faut aussi prévoir la gestion du changement de page dans l'événement "listen" dans le cas où vos menus comportent plus de 12 boutons avec la fonction "GestIndexBoutons" :

// Test de changement de page
if(llListFindList([PREVIOUS,NEXT], [message]) != -1)
GestIndexBoutons(id, message);


* La libération d'un menu se fait avec la fonction "CancelMenu" dont le seul paramètre est la clé de l'avatar :

CancelMenu(clé avatar);


* Il faut enfin renseigner l'événement "timer" si vous voulez bénéficier du timeout avec la fonction "FinMenu" :

timer() {FinMenu();}


* Si vous devez filtrer dans l'événement "listen" les canaux affectés aux menus :

listen(integer channel, string name, key id, string message) {
...
// Test des canaux du Chat
if(llListFindList(lCanalMenu, [channel]) != -1) {


-----------------------------------------------------------------------
Gestion des boutons
-----------------------------------------------------------------------

Dans le cas où il y a plus de 12 boutons j'ai considéré deux cas :

* Moins de 23 boutons : deux pages de menus avec un bouton NEXT sur la première et un bouton PREVIOUS sur la seconde
* A partir de 23 boutons : plusieurs pages de menus avec sur chacune un boutons NEXT et un bouton PREVIOUS avec un fonctionnement "circulaire".

Le nom des boutons NEXT et PREVIOUS est défini dans les paramètres (par défaut "<<<" et ">>>")


-----------------------------------------------------------------------
TimeOut
-----------------------------------------------------------------------

J'ai prévu un timeout global, il n'y a pas trop d'intérêt de différencier par avatar. A chaque initialisation d'un menu le compteur est remis à zéro pour tous les avatars. La valeur est défini dans les paramètres par la variable TIMEOUT (par défaut 30 secondes).

-----------------------------------------------------------------------
Script
-----------------------------------------------------------------------

A tester à plusieurs avatars

//----------------------------------------------------------------------------------

// Test Menu V1.0

//----------------------------------------------------------------------------------

//----------------------------------------------------------------------------------

// LIBRARY FUNCTIONS

//----------------------------------------------------------------------------------

//---------------------------------------------------------------

// MENU FUNCTIONS

//---------------------------------------------------------------

// -- Initialisation menu --

// @ param [key] clé de l'avatar

// @ param [string] texte pour le menu

// @ param [list] noms des boutons

InitMenu(key id, string texte, list boutons) {

// Test avatar déjà présent

if(llListFindList(lAvaMenu, [id]) != -1) CancelMenu(id);

// Détermination de l'index du menu

integer i = llGetListLength(lAvaMenu);

// Choix d'un canal

integer c = GetRandomChannel();

// Vérification unicité de ce canal

while(llListFindList(lCanalMenu, [c]) != -1)

c = GetRandomChannel();

// Enregistrement des paramètres

lAvaMenu += id;

lBoutonsMenu += llDumpList2String(boutons, "|");

lCanalMenu += c;

lTexteMenu += texte;

lIndexMenu += 0;

// Mise en place et enregistrement de l'écoute

lEcouteMenu += llListen(llList2Integer(lCanalMenu, i), "", id, "");

// Envoi du menu

GestMenu(id, i);

}

// -- Gestion de l'index des boutons du menu --

// @ param [key] clé de l'avatar

// @ param [string] bouton de déplacement

GestIndexBoutons(key id, string browse) {

// Index du menu

integer i = llListFindList(lAvaMenu, [id]);

// Index dans les boutons du menu

integer IndexBoutons = llList2Integer(lIndexMenu, i);

// Nombre de boutons

integer n = llGetListLength(llParseString2List(llList2String(lBoutonsMenu, i), ["|"], []));

// Navigation simple pour deux pages

if(n < 23) {

if(IndexBoutons) IndexBoutons = 0;

else IndexBoutons = 11;}

// Navigation riche pour plus de deux pages

else {

if(browse == NEXT) {

IndexBoutons += 10;

if(IndexBoutons >= n) IndexBoutons = 0;}

else {

IndexBoutons -= 10;

if(IndexBoutons < 0) IndexBoutons = n - n % 10;}

}

// Mise à jour de l'index des boutons du menu

lIndexMenu = llListReplaceList(lIndexMenu, [IndexBoutons], i, i);

// Envoi du menu

GestMenu(id, i);

}

// -- Gestion menu --

// @ param [key] clé de l'avatar

// @ param [index] index du menu

GestMenu(key id, integer i) {

// Index pour les boutons

integer IndexBoutons = llList2Integer(lIndexMenu, i);

// Liste globale des boutons

list lBoutons = llParseString2List(llList2String(lBoutonsMenu, i), ["|"], []);

// Nombre total de boutons

integer n = llGetListLength(lBoutons);

// Si plusieurs pages

if(n > 12) {

// Que 2 pages -> navigation simple

if(n < 23) {

// Deuxième page

if(IndexBoutons)

lBoutons = [PREVIOUS] + llList2List(lBoutons, IndexBoutons, -1);

// Première page

else

lBoutons = llList2List(lBoutons, 0, 1) + [NEXT] + llList2List(lBoutons, 2, 10);}

// Plus de 2 pages -> navigation riche

else {

list l = [PREVIOUS, llList2String(lBoutons, IndexBoutons), NEXT];

// Première page ou page intermédiaire

if(n - IndexBoutons > 10)

lBoutons = l + llList2List(lBoutons, IndexBoutons + 1, IndexBoutons + 9);

// Dernière page

else {

if(IndexBoutons + 1 < n)

lBoutons = l + llList2List(lBoutons, IndexBoutons + 1, -1);

else lBoutons = l;}

}

}


// Mise en route ou réinitialisation du timer

llSetTimerEvent(TIMEOUT);

// Envoi du menu

llDialog(id, llList2String(lTexteMenu, i), lBoutons, llList2Integer(lCanalMenu, i));

}

// -- Suppression d'un menu --

// @ param [key] clé de l'avatar

CancelMenu(key id) {

// Index du menu

integer i = llListFindList(lAvaMenu, [id]);

// On teste s'il y a quelque chose à arrêter

if(i != -1) {

// Arrêt de l'écoute du canal

llListenRemove(llList2Integer(lEcouteMenu, i));

// Suppression des paramètres pour ce menu

lAvaMenu = llDeleteSubList(lAvaMenu, i, i);

lBoutonsMenu = llDeleteSubList(lBoutonsMenu, i, i);

lCanalMenu = llDeleteSubList(lCanalMenu, i, i);

lEcouteMenu = llDeleteSubList(lEcouteMenu, i, i);

lIndexMenu = llDeleteSubList(lIndexMenu, i, i);

lTexteMenu = llDeleteSubList(lTexteMenu, i, i);

// Si c'est le dernier on arrête le timer

if(llGetListLength(lAvaMenu))

llSetTimerEvent(.0);}

}

// -- Fin du délai d'écoute des menu --

FinMenu() {

// Balayage de tous les menus

while(llGetListLength(lAvaMenu)) {

key k = llList2Key(lAvaMenu, 0);

// On prévient l'avatar

llInstantMessage(llList2Key(lAvaMenu, 0), "Time out for dialog.");

// On supprime le menu

CancelMenu(k);}

}

// -- Fonction qui génère un canal au hasard (négatif) --

// @ return [integer] retourne le canal

integer GetRandomChannel() {

return ~(integer)llFrand((float)DEBUG_CHANNEL);

} // fonction de BlackShade NightFire

//----------------------------------------------------------------------------------

// LIBRARY VARIABLES

//----------------------------------------------------------------------------------

//----------------------------------------------

// MENUS

//----------------------------------------------

string PREVIOUS = "<<<"; // Bouton pour page arrière

string NEXT = ">>>"; // Bouton pour page avant

float TIMEOUT = 30.0; // Délai pour réponse au menu

list lCanalMenu; // Canaux du Chat

list lEcouteMenu; // Handle de l'écoute

list lIndexMenu; // Index de position dans les boutons

list lTexteMenu; // Texte pour le menu

list lAvaMenu; // Clé de l'avatar

list lBoutonsMenu; // Boutons du menu

//--------------------------------------------------------------------------------------

// SCRIPT GLOBAL VARIABLES

//--------------------------------------------------------------------------------------

// Liste des boutons

list lBoutons;

//--------------------------------------------------------------------------------------

// STATE default de test

//--------------------------------------------------------------------------------------

default

{

state_entry() {

// Création d'une liste de boutons pour les tests

integer c;

for(c = 0; c < 54; ++c)

lBoutons += (string)c;

}

listen(integer channel, string name, key id, string message) {

// Test de changement de page pour navigation

if(llListFindList([PREVIOUS,NEXT], [message]) != -1)

GestIndexBoutons(id, message);

// Traitement du bouton choisi

else {

llInstantMessage(id, "Vous avez choisi le bouton " + message);

CancelMenu(id);

}

}

touch_start(integer total_number) {

// Traitement pour tous les avatars qui ont cliqué

integer c;

for(c = 0; c < total_number; ++c)

InitMenu(llDetectedKey(c), "\nChoisissez une option :", lBoutons);

}

timer() {FinMenu();}

}


« Retour aux scripts