Difference between revisions of "SimpleDialogMenuSystem"
m (added see also) |
m (<lsl> tag to <source>) |
||
(9 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
{{LSL Header}} __NOTOC__ | {{LSL Header}} __NOTOC__ | ||
<div style="padding: 0.5em;"> | <div style="padding: 0.5em;"> | ||
A bare-bones multi-page dialog script, supporting next and previous page buttons. | |||
The variable 'gActionsPerPage' (initially set to 9) determines the number of useful buttons per page, apart from the page scroll buttons. | |||
9 is a good value. You can set it to any value between 1 and 12. If set to 11 or 12, the paging buttons will not appear, but no more than 11 or 12 choices may then exist in the action list. | |||
'gListActions' is a very simplified example of menu choices available to the user. Replace the letters with action words appropriate to your application. | |||
In various places, this code uses an integer where you might expect a float value, e.g. [[llSetTimerEvent]](0). This has been done in order to save space in the 'Byte Code' version of the script that exists once a script has been compiled. Often space is a more critical consideration than speed in script design. | |||
The script uses [[state]] changes as a means of avoiding multi-user concurrency and multiple open listener issues. A state change ensures that any and all active listeners are released. | |||
The first detected touch sends the script into a 'busy' condition, and no more touches will be detected until that user has finished with the dialog(s), or the time for a response expires. | |||
Then the script returns to the 'ready' state, and a subsequent touch will be detected. | |||
The 'ready' state uses touch_end() rather than touch_start() as this avoids the 'missed touch' problem, documented elsewhere, that occurs when changing state from within touch_start(). | |||
It would be simple to add floating text via [[llSetText]] or maybe a colour change to indicate when the script is in a 'ready' or 'busy' state. | |||
See also: [[DialogMenus|Building a dialog menu step by step]] | |||
<source lang="lsl2"> | |||
// Multi-Page Dialog Menu System | |||
integer | // Omei Qunhua December 2013 | ||
integer gActionsPerPage = 9; // Number of action choice buttons per menu page (must be 1 to 10, or 12) | |||
list gListActions = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","Y","Z"]; | |||
// ======================================================================================================== | |||
integer | |||
integer gTotalActions; | |||
integer gPage; // Current dialog page number (counting from zero) | |||
list buttons | integer gMaxPage; // Highest page number (counting from zero) | ||
integer gChan; // Channel used for dialog communications. | |||
key gUser; // Current user accessing the dialogs | |||
BuildDialogPage(key user) | |||
{ | |||
// Build a dialog menu for current page for given user | |||
integer start = gActionsPerPage * gPage; // starting offset into action list for current page | |||
// set up scrolling buttons as needed | |||
list buttons = [ "<<", " ", ">>" ]; | |||
if (gActionsPerPage == 10) buttons = [ "<<", ">>" ]; | |||
else if (gActionsPerPage > 10) buttons = []; // No room for paging buttons | |||
if ( | // 'start + gActionsPerPage -1' might point beyond the end of the list - | ||
// - but LSL stops at the list end, without throwing a wobbly | |||
buttons += llList2List(gListActions, start, start + gActionsPerPage - 1); | |||
llDialog(user, "\nPage " + (string) (gPage+1) + " of " + (string) (gMaxPage + 1) + "\n\nChoose an action", buttons, gChan); | |||
llSetTimerEvent(60); // If no response in time, return to 'ready' state | |||
} | |||
default | |||
{ | |||
state_entry() | |||
{ | |||
gTotalActions = (gListActions != [] ); // get length of action list | |||
// Validate 'ActionsPerPage' value | |||
if (gActionsPerPage < 1 || gActionsPerPage > 12) | |||
{ | |||
llOwnerSay("Invalid 'gActionsPerPage' - must be 1 to 12"); | |||
return; | |||
} | } | ||
// Compute number of menu pages that will be available | |||
gMaxPage = (gTotalActions - 1) / gActionsPerPage; | |||
if (gActionsPerPage > 10) | |||
{ | |||
gMaxPage = 0; | |||
if (gTotalActions > gActionsPerPage) | |||
{ | |||
llOwnerSay("Too many actions in total for this ActionsPerPage setting"); | |||
return; | |||
} | |||
} | } | ||
if ( | |||
// Compute a negative communications channel based on prim UUID | |||
gChan = 0x80000000 | (integer) ( "0x" + (string) llGetKey() ); | |||
state ready; | |||
} | |||
} | |||
state ready | |||
{ | |||
touch_end(integer total_number) | |||
{ | |||
gUser = llDetectedKey(0); | |||
state busy; | |||
// Changing state sets the application to a busy condition while one user is selecting from the dialogs | |||
// In the event of multiple 'simultaneous' touches, only one user will get a dialog | |||
} | |||
} | |||
state busy | |||
{ | |||
state_entry() | |||
{ | |||
llListen(gChan, "", gUser, ""); // This listener will be used throughout this state | |||
gPage = 0; | |||
BuildDialogPage(gUser); // Show Page 0 dialog to current user | |||
} | |||
listen (integer chan, string name, key id, string msg) | |||
{ | |||
if (msg == "<<" || msg == ">>") // Page change ... | |||
{ | |||
if (msg == "<<") --gPage; // Page back | |||
if (msg == ">>") ++gPage; // Page forward | |||
if (gPage < 0) gPage = gMaxPage; // cycle around pages | |||
if (gPage > gMaxPage) gPage = 0; | |||
BuildDialogPage(id); | |||
return; | |||
} | } | ||
if (msg != " ") // no action on blank menu button | |||
{ | |||
// User has selected an action from the menu | |||
llRegionSayTo(id, 0, "You chose action <" + msg + ">"); | |||
} | } | ||
state ready; // changing state will release ANY and ALL open listeners | |||
} | |||
timer() | |||
{ | |||
llRegionSayTo(gUser, 0, "Too slow, menu cancelled"); | |||
state ready; | |||
} | |||
state_exit() | |||
{ | |||
llSetTimerEvent(0); // would be dangerous to leave a dormant timer | |||
} | } | ||
} | } | ||
</source> | |||
== Texture Selector == | |||
Here is a variant of the above script, to allow a user to display a texture selected from a multi-page menu of available textures. | |||
<source lang="lsl2"> | |||
// Multi-Page Texture Selector | |||
// Omei Qunhua April 2014 | |||
list gListFullNames; // List of inventory textures | |||
list gListBriefNames; // List of abbreviated texture names for dialog buttons | |||
integer gPage; // Current dialog page number (counting from zero) | |||
integer gMaxPage; // Highest page number (counting from zero) | |||
integer gChan; // Channel used for dialog communications. | |||
key gUser; // Current user accessing the dialogs | |||
BuildDialogPage(key user) | |||
{ | |||
integer TotalChoices = (gListBriefNames != [] ); // get length of texture list | |||
// set up scrolling buttons if needed | |||
// | list buttons = [ "<<", " ", ">>" ]; | ||
integer ChoicesPerPage = 9; | |||
if ( | if (TotalChoices < 13) | ||
{ | |||
buttons = []; | |||
ChoicesPerPage = 12; | |||
} | } | ||
// Compute number of menu pages that will be available | |||
gMaxPage = (TotalChoices - 1) / ChoicesPerPage; | |||
// Build a dialog menu for current page for given user | |||
integer start = ChoicesPerPage * gPage; // starting offset into action list for current page | |||
// 'start + ChoicesPerPage -1' might point beyond the end of the list - | |||
// - but LSL stops at the list end, without throwing a wobbly | |||
buttons += llList2List(gListBriefNames, start, start + ChoicesPerPage - 1); | |||
llDialog(user, "\nPage " + (string) (gPage+1) + " of " + (string) (gMaxPage + 1) + "\n\nChoose an item", buttons, gChan); | |||
llSetTimerEvent(30); // If no response in time, return to 'ready' state | |||
} | |||
default | |||
{ | |||
touch_end(integer total_number) | |||
{ | |||
// Compute a negative communications channel based on prim UUID | |||
gChan = 0x80000000 | (integer) ( "0x" + (string) llGetKey() ); | |||
gUser = llDetectedKey(0); | |||
integer count = llGetInventoryNumber(INVENTORY_TEXTURE); | |||
string name; | |||
while (count--) | |||
{ | |||
name = llGetInventoryName(INVENTORY_TEXTURE, count); | |||
gListFullNames += name; | |||
gListBriefNames += llGetSubString(name, 0, 23); | |||
} | |||
state busy; | |||
// Changing state sets the application to a busy condition while one user is selecting from the dialogs | |||
// In the event of multiple 'simultaneous' touches, only one user will get a dialog | |||
} | } | ||
} | |||
// | state busy | ||
{ | |||
state_entry() | |||
{ | |||
llListen(gChan, "", gUser, ""); // This listener will be used throughout this state | |||
gPage = 0; | |||
BuildDialogPage(gUser); // Show Page 0 dialog to current user | |||
} | } | ||
listen (integer chan, string name, key id, string msg) | |||
{ | |||
( | integer index; | ||
string name2; | |||
if (msg == "<<" || msg == ">>") // Page change ... | |||
{ | |||
if (msg == "<<") --gPage; // Page back | |||
if (msg == ">>") ++gPage; // Page forward | |||
if (gPage < 0) gPage = gMaxPage; // cycle around pages | |||
if (gPage > gMaxPage) gPage = 0; | |||
BuildDialogPage(id); | |||
return; | |||
} | |||
if (msg != " ") // no action on blank menu button | |||
{ | |||
// User has selected a texture from the menu | |||
index = llListFindList(gListBriefNames, [msg]); | |||
name2 = llList2String(gListFullNames, index); | |||
llRegionSayTo(id, 0, "You chose texture <" + name2 + ">"); | |||
llSetTexture(name2, ALL_SIDES); | |||
} | |||
llResetScript(); | |||
} | } | ||
timer() | |||
{ | |||
llRegionSayTo(gUser, 0, "Too slow, menu cancelled"); | |||
llResetScript(); | |||
} | } | ||
} | |||
</source> | |||
</ | |||
Latest revision as of 17:02, 24 January 2015
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
A bare-bones multi-page dialog script, supporting next and previous page buttons.
The variable 'gActionsPerPage' (initially set to 9) determines the number of useful buttons per page, apart from the page scroll buttons. 9 is a good value. You can set it to any value between 1 and 12. If set to 11 or 12, the paging buttons will not appear, but no more than 11 or 12 choices may then exist in the action list.
'gListActions' is a very simplified example of menu choices available to the user. Replace the letters with action words appropriate to your application.
In various places, this code uses an integer where you might expect a float value, e.g. llSetTimerEvent(0). This has been done in order to save space in the 'Byte Code' version of the script that exists once a script has been compiled. Often space is a more critical consideration than speed in script design.
The script uses state changes as a means of avoiding multi-user concurrency and multiple open listener issues. A state change ensures that any and all active listeners are released. The first detected touch sends the script into a 'busy' condition, and no more touches will be detected until that user has finished with the dialog(s), or the time for a response expires. Then the script returns to the 'ready' state, and a subsequent touch will be detected.
The 'ready' state uses touch_end() rather than touch_start() as this avoids the 'missed touch' problem, documented elsewhere, that occurs when changing state from within touch_start().
It would be simple to add floating text via llSetText or maybe a colour change to indicate when the script is in a 'ready' or 'busy' state.
See also: Building a dialog menu step by step
// Multi-Page Dialog Menu System
// Omei Qunhua December 2013
integer gActionsPerPage = 9; // Number of action choice buttons per menu page (must be 1 to 10, or 12)
list gListActions = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","Y","Z"];
// ========================================================================================================
integer gTotalActions;
integer gPage; // Current dialog page number (counting from zero)
integer gMaxPage; // Highest page number (counting from zero)
integer gChan; // Channel used for dialog communications.
key gUser; // Current user accessing the dialogs
BuildDialogPage(key user)
{
// Build a dialog menu for current page for given user
integer start = gActionsPerPage * gPage; // starting offset into action list for current page
// set up scrolling buttons as needed
list buttons = [ "<<", " ", ">>" ];
if (gActionsPerPage == 10) buttons = [ "<<", ">>" ];
else if (gActionsPerPage > 10) buttons = []; // No room for paging buttons
// 'start + gActionsPerPage -1' might point beyond the end of the list -
// - but LSL stops at the list end, without throwing a wobbly
buttons += llList2List(gListActions, start, start + gActionsPerPage - 1);
llDialog(user, "\nPage " + (string) (gPage+1) + " of " + (string) (gMaxPage + 1) + "\n\nChoose an action", buttons, gChan);
llSetTimerEvent(60); // If no response in time, return to 'ready' state
}
default
{
state_entry()
{
gTotalActions = (gListActions != [] ); // get length of action list
// Validate 'ActionsPerPage' value
if (gActionsPerPage < 1 || gActionsPerPage > 12)
{
llOwnerSay("Invalid 'gActionsPerPage' - must be 1 to 12");
return;
}
// Compute number of menu pages that will be available
gMaxPage = (gTotalActions - 1) / gActionsPerPage;
if (gActionsPerPage > 10)
{
gMaxPage = 0;
if (gTotalActions > gActionsPerPage)
{
llOwnerSay("Too many actions in total for this ActionsPerPage setting");
return;
}
}
// Compute a negative communications channel based on prim UUID
gChan = 0x80000000 | (integer) ( "0x" + (string) llGetKey() );
state ready;
}
}
state ready
{
touch_end(integer total_number)
{
gUser = llDetectedKey(0);
state busy;
// Changing state sets the application to a busy condition while one user is selecting from the dialogs
// In the event of multiple 'simultaneous' touches, only one user will get a dialog
}
}
state busy
{
state_entry()
{
llListen(gChan, "", gUser, ""); // This listener will be used throughout this state
gPage = 0;
BuildDialogPage(gUser); // Show Page 0 dialog to current user
}
listen (integer chan, string name, key id, string msg)
{
if (msg == "<<" || msg == ">>") // Page change ...
{
if (msg == "<<") --gPage; // Page back
if (msg == ">>") ++gPage; // Page forward
if (gPage < 0) gPage = gMaxPage; // cycle around pages
if (gPage > gMaxPage) gPage = 0;
BuildDialogPage(id);
return;
}
if (msg != " ") // no action on blank menu button
{
// User has selected an action from the menu
llRegionSayTo(id, 0, "You chose action <" + msg + ">");
}
state ready; // changing state will release ANY and ALL open listeners
}
timer()
{
llRegionSayTo(gUser, 0, "Too slow, menu cancelled");
state ready;
}
state_exit()
{
llSetTimerEvent(0); // would be dangerous to leave a dormant timer
}
}
Texture Selector
Here is a variant of the above script, to allow a user to display a texture selected from a multi-page menu of available textures.
// Multi-Page Texture Selector
// Omei Qunhua April 2014
list gListFullNames; // List of inventory textures
list gListBriefNames; // List of abbreviated texture names for dialog buttons
integer gPage; // Current dialog page number (counting from zero)
integer gMaxPage; // Highest page number (counting from zero)
integer gChan; // Channel used for dialog communications.
key gUser; // Current user accessing the dialogs
BuildDialogPage(key user)
{
integer TotalChoices = (gListBriefNames != [] ); // get length of texture list
// set up scrolling buttons if needed
list buttons = [ "<<", " ", ">>" ];
integer ChoicesPerPage = 9;
if (TotalChoices < 13)
{
buttons = [];
ChoicesPerPage = 12;
}
// Compute number of menu pages that will be available
gMaxPage = (TotalChoices - 1) / ChoicesPerPage;
// Build a dialog menu for current page for given user
integer start = ChoicesPerPage * gPage; // starting offset into action list for current page
// 'start + ChoicesPerPage -1' might point beyond the end of the list -
// - but LSL stops at the list end, without throwing a wobbly
buttons += llList2List(gListBriefNames, start, start + ChoicesPerPage - 1);
llDialog(user, "\nPage " + (string) (gPage+1) + " of " + (string) (gMaxPage + 1) + "\n\nChoose an item", buttons, gChan);
llSetTimerEvent(30); // If no response in time, return to 'ready' state
}
default
{
touch_end(integer total_number)
{
// Compute a negative communications channel based on prim UUID
gChan = 0x80000000 | (integer) ( "0x" + (string) llGetKey() );
gUser = llDetectedKey(0);
integer count = llGetInventoryNumber(INVENTORY_TEXTURE);
string name;
while (count--)
{
name = llGetInventoryName(INVENTORY_TEXTURE, count);
gListFullNames += name;
gListBriefNames += llGetSubString(name, 0, 23);
}
state busy;
// Changing state sets the application to a busy condition while one user is selecting from the dialogs
// In the event of multiple 'simultaneous' touches, only one user will get a dialog
}
}
state busy
{
state_entry()
{
llListen(gChan, "", gUser, ""); // This listener will be used throughout this state
gPage = 0;
BuildDialogPage(gUser); // Show Page 0 dialog to current user
}
listen (integer chan, string name, key id, string msg)
{
integer index;
string name2;
if (msg == "<<" || msg == ">>") // Page change ...
{
if (msg == "<<") --gPage; // Page back
if (msg == ">>") ++gPage; // Page forward
if (gPage < 0) gPage = gMaxPage; // cycle around pages
if (gPage > gMaxPage) gPage = 0;
BuildDialogPage(id);
return;
}
if (msg != " ") // no action on blank menu button
{
// User has selected a texture from the menu
index = llListFindList(gListBriefNames, [msg]);
name2 = llList2String(gListFullNames, index);
llRegionSayTo(id, 0, "You chose texture <" + name2 + ">");
llSetTexture(name2, ALL_SIDES);
}
llResetScript();
}
timer()
{
llRegionSayTo(gUser, 0, "Too slow, menu cancelled");
llResetScript();
}
}