A Basic Slider Script
1. Designing The Slider Object
The slider object is designed to draw a list of images into another image and 'slide' the list of images along. The first step is to sketch out the main methods this object will use to do its thing. The two main methods of this object will be, naturally enough, "loadImages" and "SlideImages":
on LoadImages (me, aListOfMembers) -- create a list of images to display end on Slide (me, shiftAmt) -- Shifts the display left or right by the specified amount. -- and draw the visible portion of the map onto the end
In order to interact with this object, we are going to create a method which we can use to determine which tile is clicked. We are going to create another method to 'hilight' a selected tile.
on GetTileAtPoint (me, pntOnCanvas) -- determine which tile is at the specified point end on SelectTile (me, whichTileID) -- show the specified has been 'selected' end
To keep this object portable and generic, we are going to pass in the output image - and the rect to use on this image - as parameters to a generic 'start' method:
on Initialise (me, aCanvas, aRectOnCanvas) -- initialise the object, specifying the image to draw to -- and the rect on that image to use end
Most imaging-lingo code at the Lingoworkshop uses an Initialise method like this. It helps keep our imaging object portable - it doesn't care whether the canvas is the stage, the image of a sprite's cast member - or an image object controlled by some other object.
You might notice that there are no methods in this object to "grab and drag" the strip, or "slide it more quickly as you move further away from the centre" - this object will simply provide a method for sliding the thumbnails a specified amount. This one simple method provides a sufficient enough interface to the Slider Object to enable us to create a whole range of behaviours - click and drag, auto-scroll, follow the mouse and so on. If we can disentangle the application specific behaviour (the way the object interacts with the particular application it is used in) and put that code in a separate script, then the SliderObject remains portable and can be easily used in different applications.
2. Fleshing out the script
So far, we have identified the 5 main methods of the object. Now it is time to flesh out the methods in more detail.
Initialising the Slider
To initialise the Slider, we need to specify the output canvas and the rect on that canvas. We can also include an optional list of 'settings' which over-ride the default settings for things like border thickness and colours.
This object keeps a reference to the output canvas, and then it makes a 'buffer' image the same size as the output rect. The various thumbnail images are copied to this buffer image, and then after all the thumbnails, borders and hilights have been painted to the buffer, the buffer is then painted to the output image.
There are two main reasons for using a buffer image rather than painting the thumbnails directly to the output. Firstly, it is usually quicker to manipulate images 'off-screen' (so director doesn't need to update the stage as theimage is manipulated); secondly, we can paint on to the buffer without having to worry about masking parts of the thumbnails that run over the edges of the buffer (the buffer image is of a fixed size, therefore any thumbnails that do not fit on it are effectively cropped)
on Initialise (me, aCanvas, aRectOnCanvas, settings) myCanvas = aCanvas myDestRect = aRectOnCanvas me._LoadSettings(settings) myWidth = myDestRect.width myHeight = myDestRect.height myBuffer = image(myWidth, myHeight, 24) myBuffer.fill(myBuffer.rect, rgb(0,0,0)) myMapH= 0 myMap =  -- create a pixel image (used for hilighting etc) myPixel = image(1,1,1) myPixel.setPixel(0,0,1) end on _LoadSettings (me, settings) -- firstly, set the defaults mySettings = [:] mySettings[#backColour] = rgb("#f1f1f1") mySettings[#borderColour] = rgb("#000000") mySettings[#BorderSize] = 1 -- now, over-ride them if settings.ilk = #PropList then mx = settings.count repeat with i = 1 to mx thisProp = settings.getPropAt(i) thisValue = settings.getAt(i) mySettings.setAProp(thisProp, thisValue) end repeat end if end
The LoadImages method
This method accepts a list of cast members that are the source images. Assuming that these images are different sizes and dimensions, we are going to resize them to fit on the output image. If the source image is too tall, we will scale it down. If it is not as tall as the output image, we will centre it vertically.
In this version, we are going to create new images for each thumbnail (if there many source images, it might be better to create the thumbnails 'on-the-fly' as they are need since it would be memory intensive to create and keep in memory all the thumbnails). We are also going to keep some additional information about the tile - such as the name of the source member, its position in the list and its selected state.
on LoadImages (me, aListOfMembers) myMap =  myTileRects =  mx = aListOfMembers.count repeat with i = 1 to mx m = aListOfMembers[i] if m.ilk = #member then if m.type = #bitmap then img = m.image if img.height > myHeight then -- need to scale the image down originalRect = img.rect thescale = float(myHeight)/img.height scaledRect = rect(0, 0, integer(img.width*thescale), myHeight) else if img.height < myHeight then -- need to centre the rect vertically vOff = (myHeight-img.height)/2 scaledRect = img.rect.offset(0, vOff) else -- perfect fit! scaledRect = img.rect end if -- create the small version of the image tileImg = image(scaledRect.width, myHeight, 24) tileImg.fill(tileImg.rect, mySettings.backColour) tileImg.copyPixels(img, scaledRect, img.rect) if mySettings.BorderSize <> 0 then tileImg.draw(tileImg.rect, [#ShapeType: #Rect, #LineSize:mySettings.BorderSize, #Color: mySettings.borderColour]) end if -- store all the information we have about this tile tile = [ #Image: tileImg, #ID: m.name, #DRect: tileImg.rect, #Indx: i, #Selected: 0 ] -- add it to the map myMap.append(tile) end if end if end repeat end
The Slide Method
The slide method takes a parameter which is how many pixels to slide the images along. This is added to 'myMapH' which tracks the total amount the images have been slid.
When we slide the images to the left (by moving the 'MapH' position to the right), this method checks whether the left most tile has been pushed 'off' and if so, moves that tile from the start of the list and adds it to the end. Similarly, when sliding the images to the right, we check whether the right most tile has been pushed off, and if so, move it back to the start of the list.
on Slide (me, shiftAmt) -- Shifts the display left or right by the specified amount. -- Specify a positive amount to slide right -- first check that there are some images to slide if myMap.count < 1 then exit -- no images -- now adjust the current mapH myMapH = myMapH + shiftAmt if shiftAmt > 0 then -- check whether we have moved past the left most tile -- if so, move it from the start of the list and -- add it to the end r = myMap[#DRect] w = r.width if myMapH >= w then aTile = myMap myMap.deleteAt(1) myMap.append(aTile) myMapH = myMapH - w end if else if shiftAmt < 0 then -- check whether we have moved past the right most tile -- if it is, move it from the end to the start of the list if myMapH <= 0 then lastPos = myMap.count aTile = myMap[lastPos] myMap.deleteAt(lastPos) myMap.addAt(1, aTile) myMapH = myMapH + aTile[#DRect].width end if end if -- now draw the visible portion of the map onto the buffer image rectLeft = -myMapH buffer = myBuffer.duplicate() repeat with i = 1 to myMap.count img = myMap[i][#Image] destRect = myMap[i][#DRect].offset(rectLeft, 0) buffer.copyPixels(img, destRect, img.rect) if myMap[i][#selected] then buffer.copyPixels(myPixel, destRect, myPixel.rect, [#BlendLevel: 100]) end if rectLeft = rectLeft + destRect.width if rectLeft > myWidth then exit repeat end repeat myCanvas.copyPixels(buffer, myDestRect, buffer.rect) end
Methods for getting a tile at a point
Because the tiles may be of different widths, this method loop through the list of tiles and checks whether the supplied point is inside the rect of each tile.
on GetTileAtPoint (me, pntOnCanvas) rectLeft = -myMapH repeat with i = 1 to myMap.Count aRect = myMap[i][#DRect].offset(rectLeft, 0) if inside(pntOnCanvas, aRect) then thisTile = myMap[i][#ID] return thisTile end if rectLeft = rectLeft + aRect.width if rectLeft > myWidth then exit repeat end repeat end
Selecting a Tile
The SelectTile method accepts a tileID and then searches the list of tiles, toggling the tile's Selected property on. If the second multiSelect parameter is not true, all other tiles have their Selected property set to 0.
on SelectTile (me, whichTileID, multiSelect) repeat with i = 1 to myMap.count if myMap[i][#ID] = whichTileID then myMap[i][#Selected] = 1 else if NOT(multiSelect) then myMap[i][#Selected] = 0 end repeat end