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[1][#DRect]
w = r.width
if myMapH >= w then
aTile = myMap[1]
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
Downloads
Source Movie is available here