Creating a Slideshow Viewer, Part 1: Buttons

Recently I decided to update my Texture Vendors. If you have seen my stores inworld, you will know that the Texture Vendors display one texture at a time, with buttons to let you cycle through the different textures in the set.

The current version of the vendors was created quite a while ago, and uses techniques that are now out-dated and inefficient. Each vendor uses three scripts — one for each button, and a script in the central prim to actually display the texture. Messages are sent from the script to the central prim to tell it to change textures.

With some of the new features that have appeared over the last couple of years it should now be possible to reduce those three scripts into one much more efficient script, and that’s what I am planning on doing.

While I am doing it, I am also going to write it up in this blog, because the basic idea of a gadget which lets you move forwards and backwards through a set of images obviously has more uses than just as a texture vendor.

The slideshow needs at least two buttons, to move forwards and backwards through the slides. It might need ‘first’ and ‘last’ buttons as well.

Until a couple of years ago the only reasonable way to implement buttons was to create a prim for each button, with a script in each prim to detect a touch event, and probably then to send a message to some kind of controller. This is the way my existing vendors work, and you can see that this is hardly efficient for either prims or scripts.

In Viewer 1.21, however, the llDetectedTouchST() function was introduced (along with some other related and useful functions). This allows you to detect which point on a prim face was touched. This makes it possible to have several buttons as a single texture, and then determine which button was actually clicked on.

Second Life Wiki: “Detected” functions

To test this out, and create a starting point for the viewer, I’m going to create two prims. One will be the surface on which the images will be displayed, and the other will be the surrounding frame, which will include any buttons.

Create two prims, one with a size of 1.0 x 1.0 x 0.1, which will be the frame, and the other with a size of 0.75, 0.75, x 0.1, which will be the display face. For the frame, set it to hollow, at a value of 80, and rotate both prims so that they are facing the right way. Centre the display prim inside the frame prim. Given those values, you should find that the prims fit together exactly. Set the textures to blank.

If you want a larger version, try setting the frame to 2.0 x 2.0 x 0.1, hollowed to 75, and the display frame to 1.5 x 1.5 x 0.1.

To test the buttons, I’m going to use a grid texture. I’ll assign it by UUID using a temporary script, so that you can use the same texture. In the Frame prim, create the following script. You might need to change the ‘face’ parameter to get the image onto the correct side of the prim. Once the texture has been applied, you can delete the script again:

            integer face = 0;
            llSetTexture("53e5f19a-70a0-ae65-831f-da5f4ec69fdb", face);

You should end up with something like this (I’ve coloured the display prim for clarity, but actually you should leave it white):

Blog tutmisc 0002a

We now have an 8×8 grid, of which the outermost cells are visible. Now we can test the button-handling. To work out which cell was clicked on requires a little bit of calculation, based on the size of the grid. The code I’m using is relatively generic, so you can modify it for different button layouts simply by changing the ‘btn_Rows’ and ‘btn_Cols’ values.

Create a new script in the Frame prim, and copy in the following code:

    float btn_Cols = 8.0;
    float btn_Rows = 8.0;
    integer btn_Id(vector pos)
        integer button = (btn_Row(pos) * (integer)btn_Cols) + btn_Col(pos);
        return button;
    integer btn_Row(vector pos)
        // Flip the y-axis, so that it runs from top to bottom.
        float y = 1.0 - pos.y;
        integer row = (llFloor((y * 10) * btn_Rows) / 10);
        return row;
    integer btn_Col(vector pos)
        integer col = (llFloor((pos.x * 10) * btn_Cols) / 10);
        return col;
        touch_end(integer count)
            vector mousePos = llDetectedTouchST(0);
            integer id = btn_Id(mousePos);
            llOwnerSay("Button " + (string)id + " pressed");

Now try clicking on the grid. As you do so, it should display (in local chat) the number of the cell that you clicked. The top-left cell should be 0, and the bottom-right cell should be 63. You might need to rotate the prim if this is not the case, although as long as you know which cell is where, you don’t necessarily need to do that. It’s just more convenient.

Aside: Why am I using touch_end() instead of touch_start()? This is because if you use touch_start() to trigger a state change, the touch_end gets ‘lost’ (it’s expecting the wrong script state), which can cause problems, and result in touches not being detected. Using touch_end() is safer. If you know that you are not going to change state, you can use touch_start().

Leave a Reply