FURWARE text/Tutorial
First steps
Creating displays
You may (and should) use the included "FURWARE display creator" to have a perfectly aligned grid of display prims automatically created for you. Every prim will be automatically assigned a special object name that is used internally by the text script so that it knows how to order them correctly, independently of the link order.
Creating a display is simple:
- Rez the display creator on a parcel where you have sufficient permissions.
- Touch the creator object. A dialog appears where you can set some parameters of the new display:
- A name for the display. You will use this name to identify which display you wish to manipulate when using multiple displays within one linkset.
- The number of rows and columns of the display (the columns are counted in prims here, not in characters).
- The number of faces per prim. There are mesh prims from 1 to 8 faces available. Generally speaking, using more faces per prim is more efficient.
- When you're happy with the settings, click "Create" to start rezzing the prims. Link them to your creation as appropriate.
Setup and initialization
When you have created your display(s) and linked them together in your creation, simply put a single copy of the FURWARE text script into the linkset. Only a single copy of the text script is required for handling multiple display sets within a linkset. The script does not have to be in the root prim.
Important hint:
You may wish to send some initial commands to the text script as soon as its initialization is done. A reset of the text script may happen in a number of cases:
- The script was just put into the object.
- The object in which the script resides was shift-copied.
- The linkset has changed (then the script needs to search for display prims again).
- The script was reset manually (for instance using the "fw_reset" command).
In order to know when exactly the script is ready to take commands, it sends a link message to the whole set with the "id" parameter set to "fw_ready". It is good practice to watch for these messages and send your initialization commands when receiving this message. Your code could look something like this:
<lsl>
link_message(integer sender, integer num, string str, key id) {
if (id == "fw_ready") { llOwnerSay("FW text is up and running!");
// Start sending some initialization stuff. llMessageLinked(sender, 0, "c=red", "fw_conf"); llMessageLinked(sender, 0, "Default text", "fw_data");
// ... }
} </lsl>
Tutorial
Preparations
Let's first prepare a small framework that you can use to try out the examples in this tutorial. It may be convenient to reset the FURWARE text script each time you make changes to the controller script to start with a freshly initialized script so that settings made in previous runs won't interfere. You can use the following snippet as a starting point:
<lsl>
default {
state_entry() { // Reset the FURWARE text script. llMessageLinked(LINK_SET, 0, "", "fw_reset"); }
link_message(integer sender, integer num, string str, key id) { // The text script sends "fw_ready" when it has initialized itself. if (id == "fw_ready") { // Here you can try out your commands.
} }
} </lsl>
Tip: For the sake of simplicity, all commands shown in this tutorial will be sent to LINK_SET. You can of course use more specific/optimized receivers in your own objects. |
Important: It is usually not considered good practice to reset the script each time you wish to change something. Once you get more familiar with the concepts of FURWARE text you will see that you can do almost anything without ever having to fully reset the script. It is just more convenient for your first experiments. |
Single display set
Let's first consider the simplest case. As a concrete example, let's assume that we have created a display consisting of 3 by 4 prims, each of them having 8 faces. Here's a schematic representation of our display:
We first take look at the two most important commands of FURWARE text, namely "fw_data" for setting text and "fw_conf" to control style preferences (text color, alignment, font, etc.). Setting the text is done like so:
<lsl>
llMessageLinked(LINK_SET, 0, "This is some example text", "fw_data");
</lsl>
This will yield:
Similarly, setting the text color to blue and the alignment to centered is done using the following call:
<lsl>
llMessageLinked(LINK_SET, 0, "c=blue; a=center", "fw_conf");
</lsl>
Combined with the previous "fw_data" call, the display now looks like this:
Tip: You do not have to send both style and text data each time you wish to change one or the other. When setting the style, the currently set text is re-used and rendered with the new style. Likewise, when setting text, the currently set style will be used. |
Multiple display sets
A single FURWARE text script can handle multiple display sets within a linkset. Let's consider the following example consisting of three sets named "Alpha", "Beta" and "Gamma":
Tip: The sets do not have to use the same prim type (i.e. number of faces per prim) but within a particular set, the prim type and the number of columns in each row need to be the same. |
If we use the same two lines of code for setting the text and style as in the single-set case, all sets will use the same settings. So using the code
<lsl>
llMessageLinked(LINK_SET, 0, "This is some example text", "fw_data");
llMessageLinked(LINK_SET, 0, "c=blue; a=center", "fw_conf");
</lsl>
will yield
Now the question arises how to set text and styles for the individual sets independently. This is where the names of the display sets that we have specified when we created the displays come into play. The last parameter of the llMessageLinked()-call needs to be modified. For instance, for setting the text and style of the set "Beta", instead of writing "fw_data" and "fw_conf" we specify the set name by writing "fw_data : Beta" and "fw_conf : Beta":
<lsl>
llMessageLinked(LINK_SET, 0, "Only for set Beta", "fw_data : Beta");
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Beta");
</lsl>
Again combining these lines with the previous code, this will yield:
Virtual text boxes
In addition to using multiple sets, we can also define multiple virtual text boxes within a display set. This can be used to easily position text on a display, for example for displaying tables, dialog boxes and much more.
In the following example we have two sets called "Dialog" and "Table". Let's assume that we wish to use the set "Dialog" as a simple dialog with two buttons and the set "Table" as a small table having three rows, three columns and an additional column that will be used for "up" and "down" buttons.
The small overlap between the column boxes will be useful later when adding borders to our boxes.
The first task is to add the text boxes to the sets. This is done using the "fw_addbox" command. The syntax of the command is:
fw_addbox : boxName : parentName : dx, dy, sx, sy : stylePrefs
The meaning of the individual parameters is as follows:
- boxName: A user-specified name for the new box. Must be unique among all box and set names.
- parentName: The name of the set or box that the new box should be aligned relatively to.
- dx, dy, sx, sy: Four integers specifying the position relative to the parent set or box (dx, dy) and the size of the box (sx, sy).
- stylePrefs: A style string (for example "c=red; a=center") that the box should use as its default; may be omitted.
Let us write down the code for adding the boxes in our example. We will first make it rather verbose and take a look at a shorter version later.
<lsl>
// Add the boxes.
llMessageLinked(LINK_SET, 0, "", "fw_addbox : ButtonOK : Dialog : 1, 2, 10, 1");
llMessageLinked(LINK_SET, 0, "", "fw_addbox : ButtonCancel : Dialog : 13, 2, 10, 1");
llMessageLinked(LINK_SET, 0, "", "fw_addbox : Column0 : Table : 0, 1, 8, 3"); llMessageLinked(LINK_SET, 0, "", "fw_addbox : Column1 : Column0 : 7, 0, 8, 3"); llMessageLinked(LINK_SET, 0, "", "fw_addbox : Column2 : Column1 : 7, 0, 8, 3"); llMessageLinked(LINK_SET, 0, "", "fw_addbox : UpDown : Column2 : 9, 0, 1, 3");
// Set the boxes' style. llMessageLinked(LINK_SET, 0, "a=center", "fw_conf : ButtonOK"); llMessageLinked(LINK_SET, 0, "a=center", "fw_conf : ButtonCancel");
llMessageLinked(LINK_SET, 0, "w=none", "fw_conf : Column0"); llMessageLinked(LINK_SET, 0, "w=none", "fw_conf : Column1"); llMessageLinked(LINK_SET, 0, "w=none", "fw_conf : Column2");
// Set some text for the boxes. llMessageLinked(LINK_SET, 0, "Some dialog", "fw_data : Dialog"); llMessageLinked(LINK_SET, 0, "OK", "fw_data : ButtonOK"); llMessageLinked(LINK_SET, 0, "Cancel", "fw_data : ButtonCancel");
llMessageLinked(LINK_SET, 0, "One\nTwo\nThree", "fw_data : Column0"); llMessageLinked(LINK_SET, 0, "123\n456\n789", "fw_data : Column1"); llMessageLinked(LINK_SET, 0, "1.2\n2.3\n3.4", "fw_data : Column2"); llMessageLinked(LINK_SET, 0, "▲\n\n▼", "fw_data : UpDown"); </lsl>
This will yield a constellation as depicted below.
Please take a moment to understand all parts of the commands.
The first two boxes were added to the parent "Dialog" which is the name of the left set, hence the "Dialog" as the third parameter to "fw_addbox". You may also specify the name of another box as the parent, then the coordinates of the new box will be relative to the upper-left corner of that (already added) box. For instance, for box "Column1" we have used "Column0" as the parent box and then specified the coordinates relative to the upper-left corner of that box. The same was done for the boxes "Column2" and "UpDown".
After the boxes have been created, we have set some style preferences for them. For instance, we have set the alignment of the dialog buttons to center ("a=center") and disabled wrapping ("w=none") for the table's columns so that even overlong lines will always stay within one row of our table.
Lastly, we have set some text for each of the boxes.
A more concise version
As mentioned before, we can write the above example in a more concise way by directly specifying style and text data when creating the boxes. The following code results in the same constellation as in the last example:
<lsl>
llMessageLinked(LINK_SET, 0, "OK", "fw_addbox : ButtonOK : Dialog : 1, 2, 10, 1 : a=center");
llMessageLinked(LINK_SET, 0, "Cancel", "fw_addbox : ButtonCancel : Dialog : 13, 2, 10, 1 : a=center");
llMessageLinked(LINK_SET, 0, "One\nTwo\nThree", "fw_addbox : Column0 : Table : 0, 1, 8, 3 : w=none"); llMessageLinked(LINK_SET, 0, "123\n456\n789", "fw_addbox : Column1 : Column0 : 7, 0, 8, 3 : w=none"); llMessageLinked(LINK_SET, 0, "1.2\n2.3\n3.4", "fw_addbox : Column2 : Column1 : 7, 0, 8, 3 : w=none"); llMessageLinked(LINK_SET, 0, "▲\n\n▼", "fw_addbox : UpDown : Column2 : 9, 0, 1, 3");
llMessageLinked(LINK_SET, 0, "Some dialog", "fw_data : Dialog"); </lsl>
Box borders
FURWARE text offers a special style option that comes in especially handy when dealing with boxes: The border style option. Because an example probably says more than words here, consider the following modification of the previous example:
<lsl>
llMessageLinked(LINK_SET, 0, "OK", "fw_addbox : ButtonOK : Dialog : 1, 2, 10, 1 : a=center; border=lr");
llMessageLinked(LINK_SET, 0, "Cancel", "fw_addbox : ButtonCancel : Dialog : 13, 2, 10, 1 : a=center; border=lr");
llMessageLinked(LINK_SET, 0, "One\nTwo\nThree", "fw_addbox : Column0 : Table : 0, 1, 8, 3 : w=none; border=lr"); llMessageLinked(LINK_SET, 0, "123\n456\n789", "fw_addbox : Column1 : Column0 : 7, 0, 8, 3 : w=none; border=lr"); llMessageLinked(LINK_SET, 0, "1.2\n2.3\n3.4", "fw_addbox : Column2 : Column1 : 7, 0, 8, 3 : w=none; border=lr"); llMessageLinked(LINK_SET, 0, "▲\n\n▼", "fw_addbox : UpDown : Column2 : 9, 0, 1, 3");
llMessageLinked(LINK_SET, 0, "Some dialog", "fw_data : Dialog"); llMessageLinked(LINK_SET, 0, "border=lrtb", "fw_conf : Dialog"); </lsl>
In the following picture, the separating lines between the prims and faces were omitted so you can get a better view on the borders introduced by the above commands. Note how FURWARE text automatically adjusts the text positions according to the borders.
The "border" style setting takes any (meaningful) combination of the letters tblrTBLR12 (order does not matter). The lowercase letters t, b, l, r introduce borders at the top, bottom, left or right side. The uppercase letters T, B, L, R additionally introduce special characters at the corners of boxes to connect them with other boxes (see also the next picture). The numbers "1" or "2" yield different border styles. Here are a few examples:
Now it should become clear why the previously mentioned overlap of the table's columns is useful: This way we could simply set the borders of all table columns to left and right without having to distinguish between them.
Tip: The additional space that is needed to draw the borders must be manually added to the respective box sizes. |
Deleting boxes
Deleting boxes is done using the "fw_delbox" command followed by the box's name. For instance, the following code would remove the dialog's buttons:
<lsl>
llMessageLinked(LINK_SET, 0, "", "fw_delbox : ButtonOK");
llMessageLinked(LINK_SET, 0, "", "fw_delbox : ButtonCancel");
</lsl>
You can also delete multiple boxes at once. The following code yields the same result:
<lsl>
llMessageLinked(LINK_SET, 0, "", "fw_delbox : ButtonOK : ButtonCancel");
</lsl>
Important: You cannot delete the "root" boxes of the display sets ("Dialog" and "Table" in this example). |
Touch queries
The example we used in the last chapter contains parts of the text that the user should be able to touch on the object and get some response, namely the buttons of the "dialog" and the up/down buttons right of our table.
The FURWARE text script can be queried about which box has been touched and at what coordinates, given the prim and face number that has been touched. The script will automatically take into account the correct translation into coordinates relative to the box, the correct ordering and any overlap between boxes.
Performing a query
Let's take a look at the touch_start()-handler in our script and how it can send a "fw_touchquery" to the FURWARE text script:
<lsl>
touch_start(integer numDetected) {
string link = (string)llDetectedLinkNumber(0); string face = (string)llDetectedTouchFace(0); llMessageLinked(LINK_SET, 0, "Some user data", "fw_touchquery:" + link + ":" + face);
} </lsl>
As you can see, the last parameter of llMessageLinked() contains the "fw_touchquery" command, the touched link number and face number, all separated by ":".
In addition, you may pass an arbitrary string as the third parameter to llMessageLinked(); "Some user data" in this example. This data will be sent back unmodified in the response message. This way you can associate queries with respones.
Parsing the reply
The FURWARE text script will always reply to such a query, even if something went wrong and it could not make sense of the parameters given in the query (for instance when a prim was touched that is not a display prim).
The "id" parameter of the "link_message()" handler always contains the string "fw_touchreply" so you can quickly distinguish the reply messages from other link messages.
The "str" parameter contains ":"-separated data about the result of the query in the form
boxName:dx:dy:rootName:x:y:userData
where
- boxName is the name of the box that has been touched.
- dx is the column (in characters) of the touch relative to the box's left side.
- dy is the row of the touch relative to the box's top side.
- rootName is the name of the base box of the display set in which the box resides.
- x is the column (in characters) of the touch relative to the display set's left side.
- y is the row of the touch relative to the display set's top side.
- userData is the string that has been passed as third parameter of "llMessageLinked()" in the touch query.
Tip: All fields except for "userData" will be empty if the touch query was invalid (e.g. a prim was touched that is not part of a display). |
Here is an example how we could parse a reply in our example:
<lsl>
link_message(integer sender, integer num, string str, key id) {
if (id == "fw_touchreply") { list tokens = llParseStringKeepNulls(str, [":"], []); string boxName = llList2String(tokens, 0); integer dx = llList2Integer(tokens, 1); integer dy = llList2Integer(tokens, 2); string rootName = llList2String(tokens, 3); integer x = llList2Integer(tokens, 4); integer y = llList2Integer(tokens, 5); string userData = llList2String(tokens, 6);
if (boxName == "ButtonOK") { llOwnerSay("OK button was clicked."); } else if (boxName == "ButtonCancel") { llOwnerSay("Cancel button was clicked."); } else if (boxName == "UpDown") { // We use the Y coordinate to determine whether "up" or "down" was touched. if (dy == 0) { llOwnerSay("Up button was clicked."); } else if (dy == 2) { llOwnerSay("Down button was clicked."); } } }
} </lsl>
Style templates
Let's say we wish to make the buttons of our dialog a bit more fancy. For instance, we might wish to give them another font and color (in addition to their left and right borders and the centered alignment). We could of course write something like this:
<lsl>
llMessageLinked(LINK_SET, 0, "border=lr; a=center; c=darkgreen; f=5054fec3-1465-af8f-2fe5-e9507795c82a", "fw_conf : ButtonOK");
llMessageLinked(LINK_SET, 0, "border=lr; a=center; c=darkgreen; f=5054fec3-1465-af8f-2fe5-e9507795c82a", "fw_conf : ButtonCancel");
</lsl>
Although it might not be that bad in this small example, it seems to be a little redundant. If you have more boxes or text passages with styles that you wish to share among boxes, using templates is probably more efficient.
FURWARE text allows you to store string "variables" that you can assign a name. Currently, these variables are only used for style templates. Let's put the above shared style commands into such a variable using the "fw_var" command and give it the name "button":
<lsl>
llMessageLinked(LINK_SET, 0, "border=lr; a=center; c=darkgreen; f=5054fec3-1465-af8f-2fe5-e9507795c82a", "fw_var : button");
</lsl>
We may now use this variable in style specifications using the style=... syntax. Let's rewrite the two "fw_conf" lines from before:
<lsl>
llMessageLinked(LINK_SET, 0, "style=button", "fw_conf : ButtonOK");
llMessageLinked(LINK_SET, 0, "style=button", "fw_conf : ButtonCancel");
</lsl>
Our dialog then looks like this:
You may even mix templates with other style settings (even other templates). For instance, we might want to make the "Cancel" button dark red but keep the other style attributes of the "button" template:
<lsl>
llMessageLinked(LINK_SET, 0, "style=button; c=darkred", "fw_conf : ButtonCancel");
</lsl>
Tip: You can also use "fw_var" to change the contents of an existing variable. Passing the empty string as the variable's contents deletes the variable. Note that changing an existing variable will trigger a complete refresh of the display because it doesn't know where the variables might be currently in use. |
Important: Style templates allow recursion (i.e. calling a style template from within another style template). The text script does not check for infinite recursion! |
Configuring multiple boxes at once
Sometimes you may wish to set the same style or text data for multiple boxes at once. As an example, let us consider the following "game board" that has 9 separate boxes for the "X" and "O" cells:
Now let's say that we wish to highlight the middle row like this:
To achieve this, we could of course write:
<lsl>
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Cell3");
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Cell4");
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Cell5");
</lsl>
But this obviously requires three link messages. FURWARE text offers a special syntax that can be used for the "fw_data" and "fw_conf" commands to set text or style data for multiple boxes at once. One possibilty for our example is to specify the names of all boxes that should be configured like so:
<lsl>
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Cell3 : Cell4 : Cell5");
</lsl>
If we assume that the boxes were added to the display in the order "Cell0", "Cell1", and so on, we can shorten the code even more. We may specify intervals of boxes that we wish to set using the following syntax (note the semicolon (";") between the box names!):
<lsl>
llMessageLinked(LINK_SET, 0, "c=red", "fw_conf : Cell3 ; Cell5");
</lsl>
Let's say that we wish to "reset" the game so that all fields to show the "?" symbol like this:
To achieve this, we need to reset the color and the text for all fields. We can use the interval notation for both "fw_conf" and "fw_data":
<lsl>
llMessageLinked(LINK_SET, 0, "", "fw_conf : Cell0 ; Cell8");
llMessageLinked(LINK_SET, 0, "?", "fw_data : Cell0 ; Cell8");
</lsl>
In this concrete example we can even omit one end of the interval: Because "Cell8" is the last box in the display set, we may omit its name and write:
<lsl>
llMessageLinked(LINK_SET, 0, "", "fw_conf : Cell0 ;");
llMessageLinked(LINK_SET, 0, "?", "fw_data : Cell0 ;");
</lsl>
This means "apply to Cell0 and all following boxes in the same display set".
It is even possible to mix interval- and single-box-notation. Consider the following code (the additional whitespace is just inserted for clarity, you may of course omit it):
<lsl>
llMessageLinked(LINK_SET, 0, "!", "fw_data : Cell1;Cell3 : Cell5 : Cell7; ");
</lsl>
Applied to the above example board, this yields:
Tip: Please see the reference for a complete overview of all possible parameter notations. |
Global style settings
Some of the style settings of your board may have a "global" nature. For instance, you might wish to always use a certain font for all boxes and be able to change that setting quickly.
Using the style templates from the last section, we already have a way to give multiple boxes a style that we can later on change centrally (by changing the respective variable using "fw_var"). The drawback of this method is that we still need to specify the "style=..." for each box separately and remember to include it again when changing some other style settings specific to a box using "fw_conf".
For such cases, the script offers the "fw_defaultconf" command. Using this command, you may set global defaults for all boxes, all root boxes (i.e. the "base" boxes of each display set) and all non-root boxes.
There currently are three variants of the command that can be sent for setting the defaults as mentioned above. Here are examples showing their usage (note the different arguments for "fw_defaultconf:..." command):
<lsl>
// Sets the default style for all boxes to red, centered.
llMessageLinked(LINK_SET, 0, "c=red; a=center", "fw_defaultconf");
// Sets the default style for all ROOT boxes to green, no wrapping. llMessageLinked(LINK_SET, 0, "c=green; w=none", "fw_defaultconf : root");
// Sets the default style for all NON-ROOT boxes to "no trimming". llMessageLinked(LINK_SET, 0, "t=off", "fw_defaultconf : nonroot"); </lsl>
Inline styles
By now we have used global and per-box style configuration. FURWARE text supports yet another level which is the most fine-grained: Style commands that are placed directly within the text. This allows you to override parts of the current style settings for only parts of your text, for instance to highlight words or align individual lines of text differently.
Such a style command is started by "<!" (note the exclamation mark) and ended by ">". In between those tags, you may specify style commands. As an example, let's make the heading text of our dialog centered, one word within the text randomly colored and make it use a different font:
<lsl>
llMessageLinked(LINK_SET, 0, "<!a=center>Some <!c=rand; f=5054fec3-1465-af8f-2fe5-e9507795c82a>fancy<!c=def> dialog", "fw_data : Dialog");
</lsl>
The result may look something like this (of course, here the color is random each time the dialog is rendered):
Take a close look at the result and at the code: We have used a special setting "c=def" to restore the normal color of the text after the word "fancy" but we have not reset the font and thus it will stay active for the rest of the text. You may use the special setting "def" for any setting (color, font, alignment, etc.) to restore the box's default.
Tip: You may also use style templates using the inline formatting command "style". |
Important: Using "border" settings as an inline style does not have any effect. |
Important: Alignment, trimming or wrapping settings may only be used immediately at the beginning of a new line (also immediately after each "\n") and will then affect that line and following lines. |
Selectively disabling inline styles
You might want to disable inline styles for parts of your display, for instance when displaying user input where you do not want the "<!" to be interpreted as the start of style data.
Curiously enough, the command for doing this is a style setting itself, namely "tags=off". After it has been parsed, further "<!" tags will be ignored. As an example, the following command disables inline styles for the contents of our "Dialog":
<lsl>
llMessageLinked(LINK_SET, 0, "border=tblr; tags=off", "fw_conf : Dialog");
</lsl>
You can even use this setting in an inline fashion, but only at the beginning of new lines (similar to alignment, etc.). Then this line and all following lines will ignore inline styles (indeed there is no way to enable them again using an inline command).