Scrolling behaviours
January 1, 1998
by Pat McClellan
Dear Multimedia Handyman,
I want to incorporate a historical timeline with interactive buttons. How can I scroll the image horizontally across the stage.
Many Thanks,
Hayden Evans,
H.Evans / at / chester.ac.uk
Dear Hayden,
There are many approaches to creating custom scrolling controls. For example, you could create an parent script which births an object which would control all of the sprites which make up the scroll bar graphics, and then applying the user input to a list of sprites to be used. Frankly, I'm fairly new to Object Oriented Programming, so I prefer to stick with writing some custom Behaviors. They're very powerful and achieve many of the same objectives as a strictly OOP approach.
Here's what we need to do:
- Create (or swipe) the graphics which will make up your left & right scroll arrow buttons, the "thumb" (that's the slider that indicates where you are on the page), and the track for the thumb.
- Create a behavior for the arrow buttons -- preferably a single behavior which can be used for either button. This behavior needs to send out the message to the thumb and scrollable sprites that they need to move.
- Create a behavior for the thumb which "listens" to the arrows, and moves itself accordingly. This behavior also needs to deal with the user dragging the thumb directly.
- Create a behavior for the thumb track (the graphic behind the thumb). In standard user interfaces, you can click in this track to move the thumb in large increments. We want to copy this capability.
- Finally, create a behavior which will be applied to every sprite that will be scrolled. This behavior will "listen" to the scroll arrows, the thumb and the thumb track, moving appropriately to user input.
OK, there's a lot to do, but I've already done it and tested it... so it'll go a lot quicker for you!
I started with the graphics which come in the button library with Director 6. There are all the scroll bar graphics, but they're for a verticle scroll bar. I just flipped them all 90 degrees. The up and down state graphics are all there too. I laid the graphics out and applied the cursor change on rollover and push button behaviors from the Behavior Library. If you're not using the Behaviors which came with Director, you're probably wasting a lot of time.
All of these behaviors are about twice as long as they need to be for our example. I wrote them so that they can be applied to Vertical or Horizontal Scroll bars, and the button behavior can be applied to any of the buttons. You can specify which arrow you're applying it to when you drag it onto a sprite. NOTE: all of these behaviors are posted in Ren's Lingo Behavior Database, located in the Resources section of Director Online.
Some of this behavior won't make any sense until you see the other behaviors, so for now, focus on what happens in the mouseDown script. As long as the user holds the mouseDown, a repeat loop increments a global variable -- called gScrollH for our example. If the scroll bar were vertical, gScrollV would be incremented.
property pMyDirection -- options are UP, DOWN, LEFT & RIGHT property pThumbSprite -- sprite number of the corresponding scroll thumb property pThumbRange -- range of motion for the scroll thumb sprite property pMyIncrement -- converts pMyDirection to an integer value global gScrollV -- amount scroll has been incremented --Vertically from initial value global gScrollH -- amount scroll has been incremented --Horizontally from initial value on getPropertyDescriptionList set directionList = [#UP, #DOWN, #LEFT, #RIGHT] set p_list = [ #pMyDirection: [ #comment: ¬ "Direction of Button Arrow", ¬ #format: #symbol, #range: directionList,¬ #default: #UP]] return p_list end on beginSprite me case pMyDirection of -- converts string direction -- into integer increment #UP: set pMyIncrement = -1 #DOWN: set pMyIncrement = 1 #LEFT: set pMyIncrement = -1 #RIGHT: set pMyIncrement = 1 end case end
on setThumbV me, range, thumbSprite if pMyDirection = #UP or pMyDirection ¬ = #DOWN then set pThumbRange = range end if set pThumbSprite = thumbSprite end on setThumbH me, range, thumbsprite if pMyDirection = #LEFT or pMyDirection ¬ = #RIGHT then set pThumbRange = range end if set pThumbSprite = thumbSprite end on mouseDown me case pMyDirection of #UP: repeat while the stillDown and gScrollV > 0 set gScrollV = gScrollV + pMyIncrement sendSprite(pThumbSprite, #ScrollThumbV) sendAllSprites(#ScrollV) updateStage end repeat #DOWN: repeat while the stillDown and ¬ gScrollV < pThumbRange set gScrollV = gScrollV + pMyIncrement sendSprite(pThumbSprite, #ScrollThumbV) sendAllSprites(#ScrollV) updateStage end repeat #LEFT: repeat while the stillDown and gScrollH > 0 set gScrollH = gScrollH + pMyIncrement sendSprite(pThumbSprite, #ScrollThumbH) sendAllSprites(#ScrollH) updateStage end repeat #RIGHT: repeat while the stillDown and ¬ gScrollH < pThumbRange set gScrollH = gScrollH + pMyIncrement sendSprite(pThumbSprite, #ScrollThumbH) sendAllSprites(#ScrollH) updateStage end repeat end case end
Next, the behavior for the thumb. You'll need to calculate the range of motion for the thumb sprite. This is the number of pixels between the far left and far right positions for the thumb. Or, if your scroll bar is vertical, top & bottom. When this sprite is initialized, the beginSprite handler broadcasts to the other sprites the spriteNum of the thumb, as well as its range. This is used by the Arrow Button behavior so that the thumb is not instructed to scroll beyond its boundary. The range will also be used by the scrollable sprite behaviors.
Note the scrollThumbV me handler. This is the part of the behavior which is called by the Arrow Button behavior and is responsible for actually moving the thumb to respond to the arrows.
At the end of this behavior, look through the mouseDown handler. This portion of the script allow users to drag the thumb, within the limits of the thumb's range of motion. This script also increments the global variable gScrollV (or gScrollH) and sends out the message to the scrollable sprites to move themselves.
property pMyDirection -- options are Horizontal and Vertical property pMyRange -- number of pixels that the thumb sprite may move property pMyStartV -- initial locV of the thumb sprite property pMyStartH -- initial locH of the thumb sprite global gScrollV -- amount scroll has been incremented --Vertically from initial value global gScrollH -- amount scroll has been incremented --Horizontally from initial value on getPropertyDescriptionList set directionList = [#HORIZONTAL, #VERTICAL] set p_list = [ ¬ #pMyDirection: [ #comment: "Direction ¬ of Button Arrow", #format: #symbol, ¬ #range: directionList,¬#default: ¬ #VERTICAL], #pMyRange: [ #comment:¬ "Range of motion (in pixels)", #format: ¬ #integer,#default: 100]] return p_list end on beginSprite me if pMyDirection = #HORIZONTAL then sendAllSprites (#setThumbH, pMyRange, ¬ the spriteNum of me) set pMyStartH = the locH of sprite the ¬ spriteNum of me if gScrollH < pMyRange then set the locH of sprite the spriteNum ¬ of me = pMyStartH + gScrollH else set the locH of sprite the spriteNum of me =¬ pMyStartH + pMyRange end if else sendAllSprites (#setThumbV, pMyRange, ¬ the spriteNum of me) beep set pMyStartV = the locV of sprite ¬ the spriteNum of me if gScrollV < pMyRange then set the locV of sprite the spriteNum of me = ¬ pMyStartV + gScrollV else set the locV of sprite the spriteNum of me = ¬ pMyStartV + pMyRange end if end if end on scrollThumbH me if pMyDirection = #HORIZONTAL then set the locH of sprite the spriteNum of me = ¬ pMyStartH + gScrollH end if end on scrollThumbV me if pMyDirection = #VERTICAL then set the locV of sprite the spriteNum of me = ¬ pMyStartV + gScrollV end if end on mouseDown me if pMyDirection = #VERTICAL then set startMouse = the mouseV set startScroll = gScrollV repeat while the stillDown and gScrollV >= 0 ¬ and gScrollV <= pMyRange set gScrollV = min(pMyRange, startScroll + ¬ (the mouseV - startMouse)) set gScrollV = max (gScrollV, 0) set the locV of sprite the spriteNum of me = ¬ pMyStartV + gScrollV sendAllSprites(#ScrollV) updateStage end repeat else set startMouse = the mouseH set startScroll = gScrollH repeat while the stillDown and gScrollH >= 0 ¬ and gScrollH <= pMyRange set gScrollH = min (pMyRange,startScroll + ¬ (the mouseH - startMouse)) set gScrollH = max(gScrollH, 0) set the locH of sprite the spriteNum of me = ¬ pMyStartH + gScrollH sendAllSprites(#ScrollH) updateStage end repeat end if end
The behavior for the thumb track allows you to set how large an increment you want the thumb to move when the user clicks in the track. For the example movie, I used a value of 80. If you click in the thumb track, the thumb moves 80 pixels in the direction of the mouse. I use the max and min functions to limit the thumb motion to its boundary. Most importantly, the behavior sets the global variable gScrollV and broadcasts the message for all scrollable sprites to update their location.
property pMyDirection -- options are Horizontal and Vertical property pMyIncrement -- number of pixels that the thumb sprite -- will advance per click property pThumbSprite -- the sprite number for the scroll thumb property pThumbRange -- the range of motion for the Thumb sprite global gScrollV -- amount scroll has been incremented -- Vertically from initial value global gScrollH -- amount scroll has been incremented -- Horizontally from initial value on getPropertyDescriptionList set directionList = [#HORIZONTAL, #VERTICAL] set p_list = [ ¬ #pMyDirection: [ #comment: "Direction ¬ of Slider Track", #format: #symbol, #range: ¬ directionList, #default: #VERTICAL], ¬ #pMyIncrement: [ #comment: "Incremental ¬ change per click (in pixels)", #format: ¬ #integer,#default: 100]] return p_list end on setThumbV me, range, thumbSprite set pThumbRange = range set pThumbSprite = thumbSprite end on setThumbH me, range, thumbsprite set pThumbRange = range set pThumbSprite = thumbSprite end
on mouseDown me if pMyDirection = #VERTICAL then if the locV of sprite pThumbSprite ¬ < the mouseV then set gScrollV = min (pThumbRange, ¬ gScrollV + pMyIncrement) else set gScrollV = max (0, gScrollV - ¬ pMyIncrement) end if sendSprite (pThumbSprite, #scrollThumbV) sendAllSprites (#scrollV) else if the locH of sprite pThumbSprite ¬ < the mouseH then set gScrollH = min (pThumbRange, ¬ gScrollH + pMyIncrement) else set gScrollH = max (0, gScrollH - ¬ pMyIncrement) end if sendSprite (pThumbSprite, #scrollThumbH) sendAllSprites (#scrollH) end if end
Finally, the behavior for the scrollable sprites epitomizes the concept of Object Oriented Programming. This behavior allows each sprite to monitor the global variable gScrollV (or gScrollH) and adjust its own location accordingly. The key to this behavior is understanding 2 concepts. First, the sprites will be moving the opposite direction from the thumb. So, when you push the right arrow, the sprites move to the left.
Second, the amount that the sprite moves is dependent on its size. In our example, the ruler graphic which is 853 pixels wide and the stage is 352 pixels wide, so the minimum range of motion necessary is 853 - 352 = 501 pixels. In our example, the graphic doesn't start flush left, so I actually used 508 for the range. Now the complicating factor is that the thumb's range of motion is only 298 pixels. That means that my ruler graphic has to move approximately 5 pixels to the left for every 3 pixels that the thumb moves to the right.
As long as you input the initial range figure (508), the behavior will check the range of the thumb and make the necessary calculations to come up with its property pMyIncrement. Since I wanted the other sprites which appear to scroll in synchonization with the ruler, I input a value of 508 for all scrolling sprites. (Entering different values would give you some interesting results -- suggesting depth.)
property pMyRange -- number of pixels that the sprite may move property pMyIncrement -- number of pixels that the thumb sprite -- will advance per click property pMyStartV -- initial locV of the thumb sprite property pMyStartH -- initial locH of the thumb sprite global gScrollV -- amount scroll has been incremented -- Vertically from initial value global gScrollH -- amount scroll has been incremented -- Horizontally from initial value on getPropertyDescriptionList set directionList = [#HORIZONTAL, #VERTICAL] set p_list = [ #pMyRange:[ #comment: ¬ "Range of motion (in pixels)", ¬ #format: #integer,#default: 100]] return p_list end on beginSprite me set pMyStartH = the locH of sprite ¬ the spriteNum of me set pMyStartV = the locV of sprite ¬ the spriteNum of me end
on setThumbH me, thumbRange, thumbSprite set pMyIncrement = pMyRange * 1.00/thumbRange set the locH of sprite the spriteNum of me = ¬ MyStartH - integer(gScrollH * pMyIncrement) end on setThumbV me, thumbRange, thumbSprite set pMyIncrement = pMyRange * 1.00/thumbRange set the locV of sprite the spriteNum of me = ¬ pMyStartV - integer(gScrollV * pMyIncrement) end on scrollH me set the locH of sprite the spriteNum of me = ¬ pMyStartH - integer(gScrollH * pMyIncrement) end on scrollV me set the locV of sprite the spriteNum of me = ¬ pMyStartV - integer(gScrollV * pMyIncrement) end
NOTE: One thing frustrated me when working out this example. I couldn't figure out why some of the scrollable sprites weren't working. Turns out that they weren't getting the thumb range information which the thumb behavior sends out in its beginSprite handler (and without that info, they couldn't calculate their own pMyIncrement.) The reason is that the thumb sprite (and its behavior) were in a higher (lower number) sprite channel than the scrollable sprites. The scrollable sprite objects didn't exist yet when the thumb sent out its information. So, the fix was simply to move the thumb sprite to a channel number greater than all of the scrollable sprites.
Well, sorry there's so much code to this one, but this gets pretty complicated. You can't really skimp on the features, because everybody out there knows how a scroll bar is suppose to function.
Finally, I want to talk about repeat loops. Many people have heard that repeat loops are "dangerous", particularly in Shockwave. They don't really know why, but they just don't use them. Nonsense. The "danger" of repeat loops is also why they are powerful. While your Director or Shockwave movie is executing a repeat loop, the computer's full processing attention is focused on executing that code. That means that all other functions -- such as browser buttons -- are temporarily non-functioning. This is perfect for creating the smoothest motion/animation in Shockwave and Director.
In these behaviors, the repeat loops are only functioning while the mouse button is held down. So, the user couldn't be trying to do anything else anyway. Repeat loops ARE problematic if you set them to work in the background. So, don't shy away from repeat loops, just use them appropriately.
As I mentioned earlier, I've uploaded these behaviors to Ren Feinstein's Lingo Behavior Database, which is available in the Resources section of Director Online. A Director 6 movie with the behaviors is also available for download (HQX or ZIP).
Copyright 1997-2024, Director Online. Article content copyright by respective authors.