Difference between revisions of "Dialog Control"

From Second Life Wiki
Jump to navigation Jump to search
m (<lsl> tag to <source>)
 
(15 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{LSL Header}}
{{LSL Header}}
This is example of usage and function library needed in each scripts to use this dialog module.


;Functions List:
==Functions List==
# Standard dialog with buttons. There can be more than 12 buttons.
#* dialog(key id, string message, list buttons, list returns);


# Normal notification dialog, ie: dialog which requires no response from user.
===Basic Dialog Functionalities===
#* dialogNotify(key id, string message);
# Standard dialog with buttons. If there are more than 12 buttons, BACK/NEXT button will be added ''automatically''! In that case, the last button on the list will be used as Close/Cancel button.
#* '''dialog'''(key id, string message, list buttons, list returns);
# Standard dialog with an input box
#* '''dialogTextbox'''(key id, string message);
# To recall last dialog (only work if a dialog has been called before):
#* '''dialogReshow'''();
# To cancel current dialog; Currently displayed dialog will give no response when clicked.
#* '''dialogCancel'''();


# To display current dialog again:
===Menus Control===
#* dialogReshow();
# Initialize menus control using:
#* [[llMessageLinked]](LINK_THIS, lnkMenuClear, "", NULL_KEY);
# Using "packDialogMessage" function to generate new menu, and add it to menus list:
#* [[llMessageLinked]](LINK_THIS, lnkMenuAdd, '''packDialogMessage'''(....), "MenuName");
#* While "MenuName" is the name of this menu, ie: MainMenu
# To make a dialog button show a sub-menu, use following as return value of the button:
#* MENU_<name>
#* Replace "<name>" with the actual name of the added menu (without parenthesis).
# Repeat (2) and (3) for all menus you want
# To show a menu, use:
#* [[llMessageLinked]](LINK_THIS, lnkMenuShow, "MenuName", [[llGetOwner]]());
#* Where "MenuName" is the name of menu to show. To show last-used menu, leave this field empty.
# Dialog will return value the same way as usual call to Nargus Dialog Module script.


# To cancel current dialog; Currently displayed dialog will give no response when clicked.
==Sample Scripts==
#* dialogCancel();
# '''[[OnTouchSelectTexture]]''' (Tiyuk Quellmalz)


;Sample Scripts:
# '''[[OnTouchSelectTexture]]''' (Tiyuk Quellmalz)


If anyone uses this module, please IM ''Nargus Asturias'', I'd love to hear what you think.
If anyone uses this module, please IM ''Nargus Asturias'', I'd love to hear what you think.


<lsl>
==Scripts==
 
* '''[[Dialog Control v1.8]]''' (with built-in [[Dialog Menus Control]])
* '''[[Dialog Control (legacy)]]''' (''Legacy version''; do not have built-in menus control)
* '''[[Dialog Menus Control]]''' (''Legacy version''; Need [[Dialog Control (legacy)]] for the script to work)
 
===Basic Usage Examples===
====Dialog Usage====
 
<source lang="lsl2">
// READ ME:
// READ ME:
// To see this sample in action;
// To see this sample in action;
Line 28: Line 51:
// Dialog constants
// Dialog constants
integer lnkDialog = 14001;
integer lnkDialog = 14001;
integer lnkDialogNotify = 14004;
integer lnkDialogTextbox = 14007;
integer lnkDialogResponse = 14002;
integer lnkDialogResponse = 14002;
integer lnkDialogTimeOut = 14003;
integer lnkDialogTimeOut = 14003;
Line 62: Line 85:
}
}


dialogNotify(key id, string message){
dialogTextbox(key id, string message){
    list rows;
     llMessageLinked(LINK_THIS, lnkDialogTextbox, message + seperator + (string)dialogTimeOut, id);
   
     llMessageLinked(LINK_THIS, lnkDialogNotify,
        message + seperator + (string)dialogTimeOut + seperator,
        id);
}
}
// ********** END DIALOG FUNCTIONS **********
// ********** END DIALOG FUNCTIONS **********
Line 79: Line 98:
     link_message(integer sender_num, integer num, string str, key id){
     link_message(integer sender_num, integer num, string str, key id){
         if(num == lnkDialogTimeOut){
         if(num == lnkDialogTimeOut){
             dialogNotify(llGetOwner(), "Menu time-out. Please try again.");
             llOwnerSay("Menu time-out. Please try again.");
             state default;
             state default;
         }else if(num == lnkDialogResponse){
         }else if(num == lnkDialogResponse){
Line 101: Line 120:
     }
     }
}
}
</lsl>
</source>


Nargus Dialog Control v1.58 (by Nargus Asturias)
====Menus Usage====
<source lang="lsl2">
// READ ME:
// To see this sample in action;
// Put "Nargus Dialog Control v1.80" along with this script
// in a prim and touch.


<lsl>
// Dialog constants
// ********** SIMPLE DIALOG MODULE ********** //
// By Nargus Asturias
// Version 1.58
//
// Support only one dialog at a time. DO NOT request multiple dialog at once!
// Use of provided functions are recommented. Instruction here are for hardcore programmers only!
//
// Request: Send MessageLinked to the script. There are 3 dialog modes:
//      lnkDialog              : Normal dialog with message and buttons
//      lnkDialogNumericAdjust  : A dialog with buttons can be used to adjust numeric value
//      lnkDialogNotify        : Just a simple notification dialog. No return value and no buttons.
//
// Send MessageLinked with code lnkDialogReshow to force active dialog to reappear.
// Send MessageLinked with code lnkDialogCancel to force cancel active dialog
//
// If a lnkDialog is requested with more than 12 buttons, multi-pages dialog is used to show the buttons.
//
// [ For lnkDialog ]
// MessageLinked Format:
//      String part: List dumped into string, each entry seperated by '||'
//          Field 1:    Dialog message (512 bytes maximum)
//          Field 2:    Time-out data (integer)
//          Field 3-4:  Button#1 and return value pair
//          Field 5-6:  Button#2 and return value pair
//          And go on...
//      Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
//      num == lnkDialogResponse:  AV click on a button. The buttons value returned as a string
//      num == lnkDialogTimeOut:    Dialog timeout.
//
// [ For lnkDialogNumericAdjust ]
// MessageLinked Format:
//      String part: List dumped into string, each entry seperated by '||'
//          Field 1:    Dialog message (512 bytes maximum)
//                          Put {VALUE} where you want the current value to be displayed)
//          Field 2:    Time-out data (integer)
//          Field 3:    Most significant value (ie. 100, for +/-100)
//          Field 4:    String-casted numeric value to be adjusted
//          Field 5:    2nd most significant value (ie. 10, for +/-10)
//          Field 6:    Use '1' to enable "+/-" button, '0' otherwise.
//          Field 7:    3nd significant value (ie. 1, for +/-1)
//          Field 8:    Use '1' for integer, or '0' for float
//          Field 9:    Least significant value (ie. 0.1, for +/-0.1)
//          Field 10:  Reserved. Not used.
//      Key part: Key of AV who attend this dialog
//
// Response: MessageLinked to the prim that requested dialog (but no where else)
//      num == lnkDialogResponse:  OK or Cancel button is clicked. The final value returned as string.
//      num == lnkDialogTimeOut:    Dialog timeout.
//
// ******************************************* //
 
// Constants
integer lnkDialog = 14001;
integer lnkDialog = 14001;
integer lnkDialogNumericAdjust = 14005;
integer lnkDialogResponse = 14002;
integer lnkDialogNotify = 14004;
integer lnkDialogTimeOut = 14003;


integer lnkDialogReshow = 14011;
integer lnkDialogReshow = 14011;
integer lnkDialogCancel = 14012;
integer lnkDialogCancel = 14012;


integer lnkDialogResponse = 14002;     // A button is hit, or OK is hit for lnkDialogNumericAdjust
integer lnkMenuClear = 15001;
integer lnkDialogCanceled = 14006;     // Cancel is hit for lnkDialogNumericAdjust
integer lnkMenuAdd = 15002;
integer lnkDialogTimeOut = 14003;       // No button is hit, or Ignore is hit
integer lnkMenuShow = 15003;


string seperator = "||";
string seperator = "||";
integer dialogTimeOut = 0;


// Dialog variables
// ********** DIALOG FUNCTIONS **********
integer dialogChannel;      // Channel number used to spawn this dialog
string packDialogMessage(string message, list buttons, list returns){
string message;            // Message to be shown with the dialog
     string packed_message = message + seperator + (string)dialogTimeOut;
integer timerOut;          // Dialog time-out
key keyId;                  // Key of user who attending this dialog
integer requestedNum;      // Link-number of the requested prim
list buttons;              // List of dialog buttons
list returns;              // List of results from dialog buttons
 
float numericValue;
integer useInteger;
 
// Other variables
integer buttonsCount;
integer startItemNo;
integer listenId;
 
string redirectState;
 
integer responseInt = -1;
string responseStr;
key responseKey;
 
 
// ********** String Functions **********
string replaceString(string pattern, string replace, string source){
     integer index = llSubStringIndex(source, pattern);
    if(index < 0) return source;
      
      
     source = llDeleteSubString(source, index, (index + llStringLength(pattern) - 1));
     integer i;
     return llInsertString(source, index, replace);
    integer count = llGetListLength(buttons);
}
     for(i=0; i<count; i++) packed_message += seperator + llList2String(buttons, i) + seperator + llList2String(returns, i);


// ********** Dialog Functions **********
     return packed_message;
// Function: createDialog
// Create dialog with given message and buttons, direct to user with give id
integer createDialog(key id, string message, list buttons){
    integer channel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;
 
    llListenRemove(listenId);
    listenId = llListen(channel, "", keyId, "");
    llDialog(keyId, message, buttons, channel);
 
     return channel;
}
}


// Function: createMultiDialog
dialogReshow(){llMessageLinked(LINK_THIS, lnkDialogReshow, "", NULL_KEY);}
// Create dialog with multiple pages. Each page has Back, Next, and a Close button. Otherwise same functionality as createDialog() function.
dialogCancel(){
integer createMultiDialog(key id, string message, list buttons, integer _startItemNo){
     llMessageLinked(LINK_THIS, lnkDialogCancel, "", NULL_KEY);
    integer channel = -llRound(llFrand( llFabs(llSin(llGetGMTclock())) * 1000000 )) - 11;
     llSleep(1);
 
    if(_startItemNo < 0) _startItemNo = 0;
    if(_startItemNo >= buttonsCount - 1) _startItemNo -= 9;
    startItemNo = _startItemNo;
   
    integer vButtonsCount = buttonsCount - 2;
   
    // Generate list of buttons to be shown
    string closeButton = llList2String(buttons, buttonsCount - 1);
   
    integer stopItemNo = startItemNo + 8;
    if(stopItemNo >= buttonsCount - 1) stopItemNo = vButtonsCount;
 
    list thisButtons = llList2List(buttons, startItemNo, stopItemNo);
 
    // Generate dialog navigation buttons
    integer i = stopItemNo - startItemNo + 1;
    i = i % 3;
    if(i > 0) while(i < 3){
        thisButtons += [" "];
        ++i;
     }
   
    if(startItemNo > 0)
        thisButtons += ["<< BACK"];
    else thisButtons += [" "];
   
    thisButtons += [closeButton];
   
    if(stopItemNo < vButtonsCount)
        thisButtons += ["NEXT >>"];
    else thisButtons += [" "];
   
    // Append page number to the message
    integer pageNumber = (integer)(stopItemNo / 9) + 1;
    integer pagesCount = llCeil(vButtonsCount / 9.0);
    string vMessage = "PAGE: " + (string)pageNumber + " of " + (string)pagesCount + "\n" +
        message;
 
    // Display dialog
    llListenRemove(listenId);
    listenId = llListen(channel, "", keyId, "");
     llDialog(keyId, vMessage, thisButtons, channel);
 
    return channel;
}
}
// ********** END DIALOG FUNCTIONS **********


// Function: generateNumericAdjustButtons
// Generate numeric adjustment dialog which adjustment values are in given list.
// If useNegative is TRUE, "+/-" button will be available.
list generateNumericAdjustButtons(list adjustValues, integer useNegative){
    list dialogControlButtons;
    list positiveButtons;
    list negativeButtons;
    list additionButtons;
    dialogControlButtons = ["OK", "Cancel"];
    // Config adjustment buttons
    integer count = llGetListLength(adjustValues);
    integer index;
    for(index = 0; (index < count) && (index < 3); index++){
        string sValue = llList2String(adjustValues, index);
        if((float)sValue != 0){
            positiveButtons += ["+" + sValue];
            negativeButtons += ["-" + sValue];
        }
    }
    // Check positive/negative button
    if(useNegative)
        additionButtons = ["+/-"];
    else additionButtons = [];
    // If there is fourth adjustment button
    if(count > 3){
        if(llGetListLength(additionButtons) == 0) additionButtons = [" "];
        string sValue = llList2String(adjustValues, index);
        additionButtons += ["+" + sValue, "-" + sValue];
    }else if(additionButtons != []) additionButtons += [" ", " "];
   
    // Return list dialog buttons
    return additionButtons + negativeButtons + positiveButtons + dialogControlButtons;
}
setResponse(integer int, string str, key id){
    responseInt = int;
    responseStr = str;
    responseKey = id;
}
checkDialogRequest(integer sender_num, integer num, string str, key id){
    if((num == lnkDialogNotify) || (num == lnkDialogNumericAdjust) || (num == lnkDialog)){
        list data = llParseString2List(str, [seperator], []);
   
        message = llList2String(data, 0);
        timerOut = llList2Integer(data, 1);
        keyId = id;
        requestedNum = sender_num;
        buttons = [];
        returns = [];
   
        if(timerOut > 7200) timerOut = 7200;
   
        integer i;
        integer count = llGetListLength(data);
        for(i=2; i<count;){
            buttons += [llList2String(data, i++)];
            returns += [llList2String(data, i++)];
        }
       
        buttonsCount = llGetListLength(buttons);
   
        if(num == lnkDialogNotify){
            dialogChannel = -((integer)llFrand(8388608))*(255) - (integer)llFrand(8388608) - 11;
            llDialog(keyId, message, buttons, dialogChannel);
            return;
        }else if(num == lnkDialogNumericAdjust)
            redirectState = "NumericAdjustDialog";
        else if(num == lnkDialog){
            if(buttonsCount > 12)
                redirectState = "MultiDialog";
            else redirectState = "Dialog";
        }
       
        state StartDialog;
    }
}


// ********** States **********
default{
default{
     state_entry(){
     state_entry(){
         if(responseInt > 0) llMessageLinked(requestedNum, responseInt, responseStr, responseKey);
         llMessageLinked(LINK_THIS, lnkMenuClear, "", NULL_KEY);
    }
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
   
            "[ Main Menu ]\n" +
    link_message(integer sender_num, integer num, string str, key id){
            "Messages go here",
        checkDialogRequest(sender_num, num, str, id);
            [ "BUTTON_1", "BUTTON_2", "BUTTON_3", "BUTTON_X" ],
    }
            [ "MENU_SubMenu1", "MENU_SubMenu2", "MENU_SubMenu3", "EXIT" ]
}
        ), "MainMenu");
 
         llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
state StartDialog{
            "[ Sub Menu 1 ]\n" +
    state_entry(){
            "Messages go here",
        if(redirectState == "Dialog")                   state Dialog;
            [ "SUB_1_1", "SUB_1_2", "SUB_1_3", "MAIN MENU", "SUB_3", "BUTTON_X" ],
         else if(redirectState == "MultiDialog")        state MultiDialog;
            [ "1.1", "1.2", "1.3", "MENU_MainMenu", "MENU_SubMenu3", "EXIT" ]
        else if(redirectState == "NumericAdjustDialog") state NumericAdjustDialog;
         ), "SubMenu1");
        else state default;
         llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
    }
            "[ Sub Menu 2 ]\n" +
}
            "Messages go here",
 
            [ "SUB_2_1", "SUB_2_2", "SUB_2_3", "MAIN MENU", "SUB_1", "BUTTON_X" ],
state Dialog{
            [ "2.1", "2.2", "2.3", "MENU_MainMenu", "MENU_SubMenu1", "EXIT" ]
    state_entry(){
         ), "SubMenu2");
        responseInt = -1;
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
        dialogChannel = createDialog(keyId, message, buttons);
             "[ Sub Menu 3 ]\n" +
         llSetTimerEvent(timerOut);
            "Messages go here",
    }
            [ "SUB_3_1", "SUB_3_2", "SUB_3_3", "MAIN MENU", "SUB_2", "BUTTON_X" ],
   
            [ "3.1", "3.2", "3.3", "MENU_MainMenu", "MENU_SubMenu2", "EXIT" ]
    state_exit(){
         ), "SubMenu3");
         llSetTimerEvent(0);
    }
   
    on_rez(integer start_param){
        state default;
    }
   
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        //llMessageLinked(requestedNum, lnkDialogTimeOut, "", keyId);
        state default;
    }
   
    link_message(integer sender_num, integer num, string str, key id){
         if(num == lnkDialogReshow){
            dialogChannel = createDialog(keyId, message, buttons);
             llSetTimerEvent(timerOut);
        }else if(num == lnkDialogCancel) state default;
 
        else checkDialogRequest(sender_num, num, str, id);
    }
 
    listen(integer channel, string name, key id, string msg){
         if((channel != dialogChannel) || (id != keyId)) return;
          
          
         integer index = llListFindList(buttons, [msg]);
         llSetText("Touch me to show menu", <1,1,1>, 1);
        setResponse(lnkDialogResponse, llList2String(returns, index), keyId);
        //llMessageLinked(requestedNum, lnkDialogResponse, llList2String(returns, index), keyId);
        state default;
     }
     }
}


state MultiDialog {
    state_entry(){
        responseInt = -1;
        startItemNo = 0;
        dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
        llSetTimerEvent(timerOut);
    }
   
    state_exit(){
        llSetTimerEvent(0);
    }
   
    on_rez(integer start_param){
        state default;
    }
   
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        state default;
    }
   
     link_message(integer sender_num, integer num, string str, key id){
     link_message(integer sender_num, integer num, string str, key id){
         if(num == lnkDialogReshow){
         if(num == lnkDialogTimeOut){
             dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
             llOwnerSay("Menu time-out. Please try again.");
            llSetTimerEvent(timerOut);
         }else if(num == lnkDialogResponse){
        }else if(num == lnkDialogCancel) state default;
             llWhisper(0, str);
 
        else checkDialogRequest(sender_num, num, str, id);
    }
 
    listen(integer channel, string name, key id, string msg){
        if((channel != dialogChannel) || (id != keyId)) return;
 
        // Dialog control buttons
        if(msg == "<< BACK"){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo - 9);
            llSetTimerEvent(timerOut);
        }else if(msg == "NEXT >>"){
            dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo + 9);
            llSetTimerEvent(timerOut);
         }else if(msg == " "){
             dialogChannel = createMultiDialog(keyId, message, buttons, startItemNo);
            llSetTimerEvent(timerOut);
       
        // Response buttons
        }else{
            integer index = llListFindList(buttons, [msg]);
            setResponse(lnkDialogResponse, llList2String(returns, index), keyId);
            //llMessageLinked(requestedNum, lnkDialogResponse, llList2String(returns, index), keyId);
            state default;
         }
         }
    }
}
state NumericAdjustDialog {
    state_entry(){
        responseInt = -1;
        numericValue = llList2Float(returns, 0);
        useInteger = llList2Integer(returns, 2);
        buttons = generateNumericAdjustButtons(buttons, llList2Integer(returns, 1));
       
        string vMessage;
        if(useInteger)
            vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
        else vMessage = replaceString("{VALUE}", (string)numericValue, message);
       
        dialogChannel = createDialog(keyId, vMessage, buttons);
        llSetTimerEvent(timerOut);
     }
     }
      
      
     state_exit(){
     touch_start(integer num_detected){
         llSetTimerEvent(0);
         llMessageLinked(LINK_THIS, lnkMenuShow, "", llDetectedOwner(0));
     }
     }
   
}
    on_rez(integer start_param){
</source>
        state default;
    }
   
    timer(){
        setResponse(lnkDialogTimeOut, "", keyId);
        //llMessageLinked(requestedNum, lnkDialogTimeOut, "", keyId);
        state default;
    }
   
    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogReshow){
            dialogChannel = createDialog(keyId, message, buttons);
            llSetTimerEvent(timerOut);
        }else if(num == lnkDialogCancel) state default;


        else checkDialogRequest(sender_num, num, str, id);
    }
    listen(integer channel, string name, key id, string msg){
        if((channel != dialogChannel) || (id != keyId)) return;
       
        // Dialog control button is hit
        if(msg == "OK"){
            setResponse(lnkDialogResponse, (string)numericValue, keyId);
            //llMessageLinked(requestedNum, lnkDialogResponse, (string)numericValue, keyId);
            state default;
        }else if(msg == "Cancel"){
            setResponse(lnkDialogCanceled, (string)numericValue, keyId);
            llMessageLinked(requestedNum, lnkDialogCanceled, (string)numericValue, keyId);
            state default;
       
        // Value adjustment button is hit
        }else if(msg == "+/-")
            numericValue = -numericValue;
        else if(llSubStringIndex(msg, "+") == 0)
            numericValue += (float)llDeleteSubString(msg, 0, 0);
        else if(llSubStringIndex(msg, "-") == 0)
            numericValue -= (float)llDeleteSubString(msg, 0, 0);
       
        // Spawn another dialog if no OK nor Cancel is hit
        string vMessage;
        if(useInteger)
            vMessage = replaceString("{VALUE}", (string)((integer)numericValue), message);
        else vMessage = replaceString("{VALUE}", (string)numericValue, message);
        dialogChannel = createDialog(keyId, vMessage, buttons);
        llSetTimerEvent(timerOut);
  }
}
</lsl>
{{LSLC|Library|Dialog Control}}
{{LSLC|Library|Dialog Control}}

Latest revision as of 18:55, 24 January 2015

Functions List

Basic Dialog Functionalities

  1. Standard dialog with buttons. If there are more than 12 buttons, BACK/NEXT button will be added automatically! In that case, the last button on the list will be used as Close/Cancel button.
    • dialog(key id, string message, list buttons, list returns);
  2. Standard dialog with an input box
    • dialogTextbox(key id, string message);
  3. To recall last dialog (only work if a dialog has been called before):
    • dialogReshow();
  4. To cancel current dialog; Currently displayed dialog will give no response when clicked.
    • dialogCancel();

Menus Control

  1. Initialize menus control using:
  2. Using "packDialogMessage" function to generate new menu, and add it to menus list:
    • llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(....), "MenuName");
    • While "MenuName" is the name of this menu, ie: MainMenu
  3. To make a dialog button show a sub-menu, use following as return value of the button:
    • MENU_<name>
    • Replace "<name>" with the actual name of the added menu (without parenthesis).
  4. Repeat (2) and (3) for all menus you want
  5. To show a menu, use:
    • llMessageLinked(LINK_THIS, lnkMenuShow, "MenuName", llGetOwner());
    • Where "MenuName" is the name of menu to show. To show last-used menu, leave this field empty.
  6. Dialog will return value the same way as usual call to Nargus Dialog Module script.

Sample Scripts

  1. OnTouchSelectTexture (Tiyuk Quellmalz)


If anyone uses this module, please IM Nargus Asturias, I'd love to hear what you think.

Scripts

Basic Usage Examples

Dialog Usage

// READ ME:
// To see this sample in action;
// Put "Nargus Dialog Control" along with this script in a prim and touch.

// ********** DIALOG FUNCTIONS **********
// Dialog constants
integer lnkDialog = 14001;
integer lnkDialogTextbox = 14007;
integer lnkDialogResponse = 14002;
integer lnkDialogTimeOut = 14003;

integer lnkDialogReshow = 14011;
integer lnkDialogCancel = 14012;

string seperator = "||";
integer dialogTimeOut = 0;

string packDialogMessage(string message, list buttons, list returns){
    string packed_message = message + seperator + (string)dialogTimeOut;
    
    integer i;
    integer count = llGetListLength(buttons);
    for(i=0; i<count; i++){
        string button = llList2String(buttons, i);
        if(llStringLength(button) > 24) button = llGetSubString(button, 0, 23);
        packed_message += seperator + button + seperator + llList2String(returns, i);
    }

    return packed_message;
}

dialogReshow(){llMessageLinked(LINK_THIS, lnkDialogReshow, "", NULL_KEY);}
dialogCancel(){
    llMessageLinked(LINK_THIS, lnkDialogCancel, "", NULL_KEY);
    llSleep(1);
}

dialog(key id, string message, list buttons, list returns){
    llMessageLinked(LINK_THIS, lnkDialog, packDialogMessage(message, buttons, returns), id);
}

dialogTextbox(key id, string message){
    llMessageLinked(LINK_THIS, lnkDialogTextbox, message + seperator + (string)dialogTimeOut, id);
}
// ********** END DIALOG FUNCTIONS **********


default{
    state_entry(){
        llSetText("Touch me to show dialog", <1,1,1>, 1);
    }

    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogTimeOut){
            llOwnerSay("Menu time-out. Please try again.");
            state default;
        }else if(num == lnkDialogResponse){
            llWhisper(0, str);
        }
    }
    
    touch_start(integer num_detected){
        dialog(llDetectedKey(0),
        
            // Dialog message here
            "Messages go here",
            
            // List of dialog buttons
            [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, "Close" ],

            // List of return value from the buttons, in same order
            // Note that this value do not need to be the same as button texts
            [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, "X" ]
        );
    }
}

Menus Usage

// READ ME:
// To see this sample in action;
// Put "Nargus Dialog Control v1.80" along with this script
// in a prim and touch.

// Dialog constants
integer lnkDialog = 14001;
integer lnkDialogResponse = 14002;
integer lnkDialogTimeOut = 14003;

integer lnkDialogReshow = 14011;
integer lnkDialogCancel = 14012;

integer lnkMenuClear = 15001;
integer lnkMenuAdd = 15002;
integer lnkMenuShow = 15003;

string seperator = "||";
integer dialogTimeOut = 0;

// ********** DIALOG FUNCTIONS **********
string packDialogMessage(string message, list buttons, list returns){
    string packed_message = message + seperator + (string)dialogTimeOut;
    
    integer i;
    integer count = llGetListLength(buttons);
    for(i=0; i<count; i++) packed_message += seperator + llList2String(buttons, i) + seperator + llList2String(returns, i);

    return packed_message;
}

dialogReshow(){llMessageLinked(LINK_THIS, lnkDialogReshow, "", NULL_KEY);}
dialogCancel(){
    llMessageLinked(LINK_THIS, lnkDialogCancel, "", NULL_KEY);
    llSleep(1);
}
// ********** END DIALOG FUNCTIONS **********


default{
    state_entry(){
        llMessageLinked(LINK_THIS, lnkMenuClear, "", NULL_KEY);
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
            "[ Main Menu ]\n" +
            "Messages go here",
            [ "BUTTON_1", "BUTTON_2", "BUTTON_3", "BUTTON_X" ],
            [ "MENU_SubMenu1", "MENU_SubMenu2", "MENU_SubMenu3", "EXIT" ]
        ), "MainMenu");
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
            "[ Sub Menu 1 ]\n" +
            "Messages go here",
            [ "SUB_1_1", "SUB_1_2", "SUB_1_3", "MAIN MENU", "SUB_3", "BUTTON_X" ],
            [ "1.1", "1.2", "1.3", "MENU_MainMenu", "MENU_SubMenu3", "EXIT" ]
        ), "SubMenu1");
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
            "[ Sub Menu 2 ]\n" +
            "Messages go here",
            [ "SUB_2_1", "SUB_2_2", "SUB_2_3", "MAIN MENU", "SUB_1", "BUTTON_X" ],
            [ "2.1", "2.2", "2.3", "MENU_MainMenu", "MENU_SubMenu1", "EXIT" ]
        ), "SubMenu2");
        llMessageLinked(LINK_THIS, lnkMenuAdd, packDialogMessage(
            "[ Sub Menu 3 ]\n" +
            "Messages go here",
            [ "SUB_3_1", "SUB_3_2", "SUB_3_3", "MAIN MENU", "SUB_2", "BUTTON_X" ],
            [ "3.1", "3.2", "3.3", "MENU_MainMenu", "MENU_SubMenu2", "EXIT" ]
        ), "SubMenu3");
        
        llSetText("Touch me to show menu", <1,1,1>, 1);
    }

    link_message(integer sender_num, integer num, string str, key id){
        if(num == lnkDialogTimeOut){
            llOwnerSay("Menu time-out. Please try again.");
        }else if(num == lnkDialogResponse){
            llWhisper(0, str);
        }
    }
    
    touch_start(integer num_detected){
        llMessageLinked(LINK_THIS, lnkMenuShow, "", llDetectedOwner(0));
    }
}