-- CUSTOM SCROLL BAR -- -- -- 27 Oct 1998: written for the D7 Behaviors Palette by James Newton -- 23 Dec 1999: JN -- + Scroll to end corrected when the bar is clicked -- NOTES FOR DEVELOPERS -- This is the most complex behavior I have written for the Behavior -- Library. I have included in one script all the handlers necessary -- to deal with each of the four elements of a scroll bar. -- -- In practice, this one script works as four separate behaviors. -- Each element is identified by its myScrollRole (upArrow, downArrow, -- dragger or bar). The behavior acts differently for each -- myScrollRole. Many handlers are divided into section by a -- "case myScrollRole of" statement. -- INSTALLATION -- Initializing the 4 behaviors cannot be done all at once on -- beginSprite because the behaviors on the other sprites may not -- exist. To get round this, I set a myState property to 0 in the -- StartInstallation handler (on beginSprite). The first prepareFrame -- (which is sent once all behaviors in the current frame have been -- instanciated) sees that "myState + 0 = 0" (or FALSE) and calls the -- FinishInstallation handler. -- Why "myState + 0"? Because once the installation is finished, -- myState is set to #done. This is a symbol. A symbol is simply an -- integer with a special tag. Adding zero to a symbol gives you -- access to the integer itself. Later, when the prepareFrame handler -- encounters "#done + 0" it evaluates this as a positive integer (or -- TRUE) and doesn't botherd to reinstall the elements. -- This technique is excessively fast: it slows down the following -- prepareFrames by something in the order of a millionth of a second. -- Installation itself is a three step-process. -- First: the behavior has to check if the sprite and member that it -- is to scroll do actually appear where it expects to find them. The -- ourScrolledElement property returned by the -- getPropertyDescriptionList handler is a double-barreled affair: -- "sprite X:field(member Y of castLib Z)". -- If sprite X contains a Field or Text member, the behavior assumes -- that this is the right one, and adopts it as myScrolledMember. If -- not, it sets out to look for (member Y of castLib Z), via the -- FindSprite handler. If it finds this member in one of the sprites -- in the frame, then it adopts the new sprite as myScrolledSprite. -- If not, it warns the author (4 times, once for each behavior). -- Second: the current behavior has to check if all the others are -- present. The Initialize handler has already prepared a list of all -- behaviors which treat the same sprite and/or member: -- ourControlList. The CheckControl handler ensures that this list -- contains one behavior for each control element... and only one. -- Third: the sprite to which the behavior is attached has to be -- placed correctly beside the sprite it is to scroll. This is most -- complex for the Dragger sprite, since it must be placed in -- accordance with the current scrollTop of myScrolledMember. -- The scrolling itself is always carried out by the behavior attached -- to the dragger. The Move and MoveBar handlers (which deal with -- clicks on the arrow buttons and on the backing bar) end with a... -- -- call (#SetDraggerShift, ourControlList.dragger) -- -- ... command. The bar behavior must in addition ask the dragger -- where it now is, using ... -- -- call (#GetDraggerData, ourControlList.dragger) -- -- ... so as to update its myActiveZone. -- EXTERNAL LINGO CALLS -- * CustomScrollbar_SetScroll me, theScroll -- If you use standard Lingo to set the scrollTop of myScrolledMember -- at runtime, the dragger position will not update until the scroll -- bar is next used. Use this call to tell the dragger to do all the -- work for you -- * CustomScrollbar_SwapMember me, newMember, currentMemberOrSprite -- If you use standard Lingo to swap the member of myScrolledSprite at -- runtime, the behaviors will continually happily to scroll the -- member which is now off-stage. Use this call to tell one of the -- behaviors do the swapping for you. -- * CustomScrollbar_GetReference me, memberOrSprite, controlOrList -- Using sendAllSprites with one of the above messages will ensure -- that the job gets done... four times, once by each behavior that -- makes up the scroll bar. If you call one of the behaviors -- directly, the command will be executed just once. But first you -- must get an object reference to the behavior in question. The -- following syntax is the simplest: -- -- scrollRef=sendAllSprites(#CustomScrollbar_GetReference,sprite X) -- -- This will give you a reference to the behavior on the highest -- sprite which controls the scrolling of sprite X. -- More details on using these external calls are given in the -- handlers themselves. -- PROPERTIES -- property spriteNum -- error checking property getPDLError -- author-defined parameters property ourScrolledElement -- ": " of element to -- scroll property myScrollRole -- sprite behaves as -- upArrow|downArrow|dragger|bar property myActiveMember -- member which appears on mouseDown property myStandardMember -- member which appears at all other times property myMultiThreading -- updates on prepareFrame | while the -- mouseDown -- internal properties property mySprite property myMember property myRect -- rect to which all members must be set -- to fit property myScrolledSprite -- sprite containing myScrolledMember property myScrolledMember -- Text or Field member to scroll property myActiveZone -- when mouse is in zone, scrolling -- continues property myState -- #paused if mouse is outside -- myActiveZone property myNextUpdate -- slows scrollByPage if necessary -- properties specific to the dragger property myZeroLoc -- loc of dragger when scrollTop = 0 property myDraggerShift -- vertical distance dwon from myZeroLoc property myClickV -- vertical coordinate where the dragger -- was clicked property myInterimShift -- value of shift while the dragger is -- being scrolled property myMaxDraggerShift -- maximum value of dragger shift -- property specific to the bar property myPageScroll -- direction of scroll: #up or #down -- properties shared between behaviors property ourControlList -- list of behaviors which collaborate on -- scrollbar property ourMaxScroll -- list shared by behaviors: [maximum -- scrollTop] -- EVENT HANDLERS -- on beginSprite me StartInstallation me end beginSprite on prepareFrame me if not myState + 0 then FinishInstallation me -- First prepareFrame only end if if myMultiThreading then UpdateScroll me end if end prepareFrame on mouseDown me StartScroll me end mouseDown on mouseUp me EndScroll me end mouseUp on mouseUpOutside me EndScroll me end mouseUpOutside -- CUSTOM HANDLERS -- on StartInstallation me -- sent by beginSprite mySprite = sprite(me.spriteNum) myMember = mySprite.member memberType = myMember.type -- Error checking if voidP (myScrollRole) then ErrorAlert (me, #getPDL_Invalid) end if -- The member of the sprite may have changed since getPDL myScrolledSprite = value (ourScrolledElement) myScrolledMember = myScrolledSprite.member -- Ensure that the scrolled sprite has not changed or moved memberType = myScrolledMember.type case memberType of #field, #text: -- do nothing otherwise -- Try to find the expected member in another sprite FindSprite (me) end case -- End of error checking -- Calculate ideal sprite.rect topLeft = point (myScrolledSprite.left, myScrolledSprite.top) myRect = myScrolledSprite.rect - rect (topLeft, topLeft) if memberType = #field then if myScrolledMember.border then myRect[1] = 1 end if end if -- Contact other sprites with the same behavior ourMaxScroll = [] set ourControlList = [:] sendAllSprites \ (\ #CustomScrollbar_RollCall, \ ourScrolledElement, \ ourControlList, \ ourMaxScroll \ ) myState = 0 -- Check will be run on first prepareFrame end StartInstallation on FindSprite me -- sent by StartInstallation -- Finds highest sprite containing the expected member to scroll saveDelimiter = the itemDelimiter the itemDelimiter = ":" memberData = ourScrolledElement.item[2] the itemDelimiter = saveDelimiter delete memberData.word[1] -- Remove type information delete memberData.char[1] -- Remove remaining space memberData = value (memberData) myScrolledMember = member (memberData) scrollSprite = the lastChannel repeat while scrollSprite if sprite(scrollSprite).member = myScrolledMember then -- We've found it! myScrolledSprite = sprite (scrollSprite) exit end if scrollSprite = scrollSprite - 1 end repeat if not scrollSprite then -- The expected member was not found ErrorAlert me, #notScrollable, myScrolledSprite.member.type end if end FindSprite on FinishInstallation me -- sent by first prepareFrame -- Terminates initialization process once all behaviors are in place CheckControlList me -- Place this sprite to the right of myScrolledSprite InstallElement me end FinishInstallation on CheckControlList me -- sent by FinishInstallation -- Checks if ourControlList contains one-and-only-one behavior with -- each role checkList = duplicate (ourControlList) roleList = [#upArrow, #downArrow, #dragger, #bar] i = roleList.count() repeat while i theRole = roleList[i] rolePosition = checkList.findPos (theRole) if rolePosition then roleList.deleteAt(i) checkList.deleteAt (rolePosition) end if i = i - 1 end repeat if checkList.count() then ErrorAlert me, #extraControl, checkList.getPropAt (1) end if if roleList.count() then ErrorAlert me, #missingControls, roleList end if end CheckControlList on InstallElement me -- sent by FinishInstallation on first prepareFrame, _SwapMembers -- Places sprite next to myScrolledSprite and determines -- myActiveZone scrollRoof = myScrolledSprite.top scrollFloor = myScrolledSprite.bottom scrollLeft = myScrolledSprite.right if myScrolledMember.type = #field then scrollLeft = scrollLeft - (myScrolledMember.border <> 0) end if case myScrollRole of #upArrow: mySprite.loc = point (scrollLeft, scrollRoof)+myMember.regPoint myActiveZone = mySprite.rect #downArrow: locAdjust = myMember.regPoint - [0, mySprite.height] rectAdjust = mySprite.rect - rect (mySprite.loc, mySprite.loc) mySprite.loc = point (scrollLeft, scrollFloor) + locAdjust myActiveZone = rectAdjust + rect (mySprite.loc, mySprite.loc) #bar: barRoof = scrollRoof+call(#SpriteHeight,ourControlList.upArrow) barRight = scrollLeft+call(#SpriteWidth,ourControlList.upArrow) barFloor = scrollFloor-call(#SpriteHeight,ourControlList.downArrow) mySprite.rect= rect (scrollLeft, barRoof, barRight, barFloor) #dragger: case myMember.type of #vectorShape: regOffset = (myMember.strokeWidth + 1) / 2 myMember.regPoint = point (regOffset, regOffset) otherwise myMember.regPoint = point (0,0) end case upHeight = call (#SpriteHeight, ourControlList.upArrow) upWidth = call (#SpriteHeight, ourControlList.upArrow) downHeight = call (#SpriteHeight, ourControlList.downArrow) draggerRoof = scrollRoof + upHeight myZeroLoc = point (scrollLeft, draggerRoof) barRight = scrollLeft + upWidth myActiveZone = rect(scrollLeft,scrollRoof,barRight,scrollFloor) myActiveZone = inflate (myActiveZone, 32, 32) arrowAdjust = upHeight + downHeight pageHeight = myScrolledSprite.height scrollHeight = pageHeight - arrowAdjust draggerHeight = mySprite.Height myMaxDraggerShift = 0 -- pre-emptive measure in case dragger -- can't scroll if scrollHeight < 0 then -- Adjust length of myScrolledMember so that arrow buttons fit minRect = myScrolledMember.rect minRect[4] = arrowAdjust myScrolledMember.rect = minRect call (#InstallElement, ourControlList.bar) end if if scrollHeight < draggerHeight then mySprite.loc = point (-999, -999) else -- Dragger can slide: set myMaxDraggerShift to reflect this if myScrolledMember.type = #field then theMargin = myScrolledMember.margin theMargin = theMargin - theMargin mod 2 borderAdjust = (myScrolledMember.border * 2) + theMargin else borderAdjust = 0 end if myMaxDraggerShift = scrollHeight-draggerHeight+borderAdjust end if ourMaxScroll[1] = GetMaxScroll (me) SetDraggerShift me end case myState = #done end InstallElement on GetMaxScroll me pageHeight = myScrolledSprite.height if myScrolledMember.type = #text then lastChar = myScrolledMember.char.count textHeight = charPostoloc (myScrolledMember, lastChar)[2] else textHeight = myScrolledMember.height end if maxScroll = textHeight - pageHeight if maxScroll < 1 then return 0 else return maxScroll end if end GetMaxScroll -- SCROLLING -- on StartScroll me -- sent by mouseDown -- StartInstallations properties for scroll and either performs one -- update (if myMultiThreading is TRUE) or completes the operation -- (in repeat loop). myState = #active ourMaxScroll[1] = GetMaxScroll (me) case myScrollRole of #dragger: -- Determine start of scroll myClickV = the clickLoc[2] myInterimShift = 0 #upArrow, #downArrow: -- Swap members mySprite.Member = myActiveMember #bar: -- Determine active zone draggerData = call (#GetDraggerData, ourControlList.dragger) if the clickLoc[2] < draggerData.top then myActiveZone = GetBarZone (me, draggerData, #up) else myActiveZone = GetBarZone (me, draggerData, #down) end if end case UpdateScroll me if myMultiThreading then exit -- Use a tight repeat loop to scroll as fast as possible repeat while the mouseDown UpdateScroll me end repeat end StartScroll on UpdateScroll me -- sent by prepareFrame, StartScroll, ResumeScroll -- Determines whether to pause or to scroll (and how to) case myState of #active: if inside (the mouseLoc, myActiveZone) then case myScrollRole of #dragger: MoveDragger me #upArrow: Move me, TRUE #downArrow: Move me, FALSE #bar: MoveBar me end case else PauseScroll me end if #paused: ResumeScroll me end case end UpdateScroll on GetBarZone me, draggerData, pageScroll -- Determines zone in which the mouse makes the text scroll by page if not voidP (pageScroll) then myPageScroll = pageScroll end if barZone = mySprite.rect if myPageScroll = #up then barZone [2] = myScrolledSprite.top barZone [4] = draggerData.top else barZone [2] = draggerData.bottom barZone [4] = myScrolledSprite.bottom end if return barZone end GetBarZone on MoveDragger me -- Checks if the dragger has moved, and if so modifies scroll newScroll = myDraggerShift + the mouseV - myClickV newScroll = max (0, min (newScroll, myMaxDraggerShift)) if myInterimShift = newScroll then exit myInterimShift = newScroll SetTextScroll me, myInterimShift end MoveDragger on Move me, up -- Scrolls one line up or down if the ticks < myNextUpdate then exit if voidP (myNextUpdate) then -- Allow time for a quick single-line click myNextUpdate = the ticks + 10 else -- Don't scroll too fast myNextUpdate = the ticks + 1 end if if up then if not myScrolledMember.scrollTop then exit scrollByLine myScrolledMember, -1 else maxScroll = ourMaxScroll[1] if myScrolledMember.scrollTop = maxScroll then exit scrollByLine myScrolledMember, 1 if myScrolledMember.scrollTop > maxScroll then myScrolledMember.scrollTop = maxScroll end if end if call (#SetDraggerShift, ourControlList.dragger) end MoveUp on MoveBar me, up -- Scrolls one page up or down, leaving one previous line visible if the ticks < myNextUpdate then exit myNextUpdate = the ticks + 10 -- So as not to scroll too fast if myPageScroll = #up then scrollByPage (myScrolledMember, -1) if myScrolledMember.scrollTop then -- Leave previous top line visible at bottom scrollByLine (myScrolledMember, 1) end if else -- down maxScroll = ourMaxScroll[1] if myScrolledMember.scrollTop = maxScroll then exit scrollByPage (myScrolledMember, 1) if myScrolledMember.scrollTop > maxScroll then myScrolledMember.scrollTop = maxScroll else -- Leave previous bottom line visible at top scrollByLine (myScrolledMember, -1) end if end if call (#SetDraggerShift, ourControlList.dragger) -- The area of the bar where mouse should active just got smaller draggerData = call (#GetDraggerData, ourControlList.dragger) myActiveZone = GetBarZone (me, draggerData) end MoveBar on PauseScroll me -- Pauses the behavior when the mouse leaves myActiveZone myState = #paused case myScrollRole of #dragger: -- Revert to original scroll SetTextScroll me, myDraggerShift #upArrow, #downArrow: -- Revert to original button image mySprite.member = myStandardMember if not myMultiThreading then updateStage end if end case end PauseScroll on ResumeScroll me -- Reactivates the behavior when the mouse returns to myActiveZone myState = #active case myScrollRole of #upArrow, #downArrow: mySprite.member = myActiveMember end case UpdateScroll me end ResumeScroll on EndScroll me -- Tidies up after scroll is over, ready for the next one case myScrollRole of #dragger: if not voidP (myInterimShift) then if ourMaxScroll[1] then myDraggerShift = myInterimShift else myDraggerShift = 0 ShiftDragger me, myDraggerShift end if end if myInterimShift = void #upArrow, #downArrow: mySprite.member = myStandardMember myNextUpdate = void end case myState = #done end EndScroll on SetTextScroll me, draggerShift -- Calculates the scrollTop of the text then sets it and moves thumb if ourMaxScroll[1] then textScroll = (draggerShift * ourMaxScroll[1]) / myMaxDraggerShift myScrolledMember.scrollTop = textScroll else -- draggerShift = 0 end if ShiftDragger me, draggerShift end SetTextScroll on SetDraggerShift me -- Calculates the position of the dragger from -- myScrolledMember.scrollTop maxScroll = ourMaxScroll[1] if maxScroll then textScroll = myScrolledMember.scrollTop myDraggerShift = (textScroll * myMaxDraggerShift) / maxScroll else myDraggerShift = 0 end if ShiftDragger me, myDraggerShift end SetDraggerShift on ShiftDragger me, draggerShift if not myMaxDraggerShift then -- Don't move dragger if there is no space to move it exit end if mySprite.loc = myZeroLoc + [0, draggerShift] updateStage end ShiftDragger -- PUBLIC METHODS (responses to #sendSprite, #sendAllSprites, #call) -- on CustomScrollbar_SetScroll me, theScroll -- Allows an editable field to update its scrolltop via the #dragger -- behavior -- Error check case ilk (theScroll) of #integer: -- nothing #float: theScroll = integer (theScroll) otherwise return #invalidTypeError end case -- End of error check theScroll = max (0, min (theScroll, ourMaxScroll[1])) myScrolledMember.scrollTop = theScroll call (#SetDraggerShift, ourControlList.dragger, theScroll) end CustomScrollbar_SetScroll on CustomScrollbar_SwapMember me, newMember, currentMemberOrSprite -- Allows you to change the member of the scrolled sprite at runtime -- newMember is a member reference, name or number. If you have a -- number of custom scrollbars in the frame, then -- currentMemberOrSprite identifies which member to change if you -- use a sendAllSprites messsage. You can leave the -- currentMemberOrSprite parameter empty if you call a specific -- behavior. -- -- The following examples both set the member of sprite 1 to member -- 2. The first example will actually do this four times, once for -- each behavior that makes up the scrollbar: -- -- sendAllSprites(#CustomScrollbar_SwapMember, member 2, sprite 1) -- OR -- scrollBehavior = sendAllSprites (#CustomScrollbar_GetReference, -- sprite 1) -- call (#CustomScrollbar_SwapMember, scrollBehavior, member 2) -- Error check case ilk (newMember) of #member: -- do nothing #integer, #string: memberExists = (the number of member (newMember) > 0) if memberExists then newMember = member (newMember) else return #memberInexistant end if otherwise return #invalidType end case case newMember.type of #text, #field: -- do nothing otherwise return #invalidMemberType end case -- End of error check case ilk (currentMemberOrSprite) of #sprite: if currentMemberOrSprite <> myScrolledSprite then exit #member: if currentMemberOrSprite <> myScrolledMember then exit -- otherwise ignore currentMemberOrSprite end case myScrolledMember = newMember idealRect = myRect if myScrolledMember.type = #field then hBorder = myScrolledMember.border vBorder = hBorder - (hBorder <> 0) hMargin = myScrolledMember.margin vMargin = hMargin / 2 --- hMargin - (hMargin mod 2) idealRect = inflate(idealRect, -hBorder-hMargin, -hBorder-vMargin) if myRect[1] then -- 0riginal meber had a border... if hBorder then -- ... and so does this member idealRect[1] = idealRect[1] - 1 end if else -- Original member had NO border... if hBorder then -- ... but this one has. idealRect[1] = idealRect[1] - 1 end if end if end if myScrolledMember.rect = idealRect myScrolledSprite.member = myScrolledMember myScrolledMember.boxType = #fixed i = ourControlList.count() repeat while i call (#NewMember, ourControlList[i]) i = i - 1 end repeat updateStage call (#InstallElement, ourControlList.dragger) end CustomScrollbar_SwapMember on CustomScrollbar_GetReference me, memberOrSprite, controlOrList -- Returns a reference to the behavior for Lingo calls. -- The parameters are optional: use them to find a specific -- behavior. Use 'theElement' to find the scrollbar of a particular -- sprite or member Use 'controlOrList' to find a specific behavior -- (up|downArrow|dragger|bar) or to return a property list of all -- behaviors. -- Examples: -- -- put sendAllSprites(#CustomScrollbar_GetReference, sprite 1, --- #dragger) -- -- -- -- put sendAllSprites (#CustomScrollbar_GetReference, sprite 1, [:]) -- -- [#bar:, #upArrow:, #dragger:, #downArrow:] -- -- You can leave the controlOrList parameter empty. If you do, the -- behavior reference on the highest sprite in the scrollbar will be -- returned. case ilk (memberOrSprite) of #sprite: if memberOrSprite <> myScrolledSprite then exit #member: if memberOrSprite <> myScrolledMember then exit otherwise exit end case if not voidP (controlOrList) then if ilk (controlOrList) = #propList then controlOrList.addProp(myScrollRole, me) return controlOrList else if controlOrList = myScrollRole then return me end if else return me end if end CustomScrollbar_GetReference -- INTER-SPRITE COMMUNICATION -- -- (responses to #sendAllSprites) -- on CustomScrollbar_RollCall me, scrolledElement, controlList, maxScrollList -- sent on StartInstallation by this and other sprites with the same -- behavior if scrolledElement <> ourScrolledElement then exit ourControlList = controlList ourControlList.addProp(myScrollRole, me) ourMaxScroll = maxScrollList end CustomScrollbar_RollCall -- (responses to #call) -- on SpriteHeight me -- Called by InstallElement in behaviors on the bar and the dragger -- Dealt with by behavior on the upArrow return mySprite.height end on SpriteWidth me -- Called by InstallElement in behaviors on the bar and the dragger -- Dealt with by behavior on the upArrow return mySprite.width end on GetDraggerData me -- Called by StartScroll, MoveBar in behavior on the bar -- Dealt with by behavior on the dragger return [#top: mySprite.top, #bottom: mySprite.bottom] end on NewMember me -- sent by _SwapMember from the behavior that received the call myScrolledMember = myScrolledSprite.member end NewMember -- ERROR CHECKING -- on ErrorAlert me, theError, data -- sent by getPropertyDescriptionList, StartInstallation case theError of #getPDLError: permittedTypes = PermittedMemberTypes(me) alert \ "Error: This behavior works only with the following member types: "&\ permittedTypes&RETURN&RETURN&\ "Hit OK and then delete this behavior from the sprite."&RETURN&\ "For more information on deleting Behaviors, see the Help system." if the optionDown then return \ [ \ #getPDLError: \ [ \ #comment: "ERROR: Click 'Cancel'. Wrong member type.", \ #format: #string, \ #range: [""], \ #default: "" \ ] \ ] end if #noScrollableSprites: alert \ "Error: Please place a Field or Text member on the Stage before using this \ behavior to create a scroll bar."&RETURN&RETURN&\ "Hit OK and then delete this behavior from the sprite."&RETURN&\ "For more information on deleting Behaviors, see the Help system." if the optionDown then return \ [ \ #getPDLError: \ [ \ #comment: "ERROR: Click 'Cancel'. Place a Text or Field"&RETURN&\ "member on Stage before creating scroll bar.", \ #format: #string, \ #range: [""], \ #default: "" \ ] \ ] end if otherwise -- Determine the behavior's name behaviorName = string (me) delete word 1 of behaviorName delete the last word of behaviorName delete the last word of behaviorName -- Convert #data to useful value case data.ilk of #void: data = "" #symbol: data = "#"&data end case if theError <> #getPDL_Invalid then -- Determine the name and type of myScrolledMember memberName = myScrolledMember.name if memberName = EMPTY then memberName = myScrolledMember else memberName = QUOTE&memberName"E end if memberName = myScrolledMember.type&&memberName end if case theError of #getPDL_Invalid: alert \ "BEHAVIOR ERROR: Frame "&the frame&", Sprite "&me.spriteNum&RETURN&RETURN&\ "Parameters for the "&behaviorName&"behavior have not been set."&\ RETURN&RETURN&\ "Please reopen the Behavior Parameters dialog and choose again." halt #notScrollable: alert \ "BEHAVIOR ERROR: Frame "&the frame&", Sprite "&me.spriteNum&RETURN&\ "Behavior "&behaviorName&RETURN&RETURN&\ "Sprite "&myScrolledSprite.spriteNum&" does not \ contain a Field or Text member."&RETURN&\ "Choose again in the Behavior Parameters dialog."&\ RETURN&RETURN&\ "Member type = "&data halt #extraControl: if the runMode = "Author" then alert \ "BEHAVIOR ERROR: Frame "&the frame&", Sprite "&me.spriteNum&RETURN&\ "Behavior "&behaviorName&RETURN&RETURN&\ "There is more than one "&data&" sprite defined for the scroll bar for \ sprite "&myScrolledSprite.spriteNum&", "&memberName&"." end if #missingControls: alert \ "BEHAVIOR ERROR: Frame "&the frame&", Sprite "&me.spriteNum&RETURN&\ "Behavior "&behaviorName&RETURN&RETURN&\ "The following elements of the scroll bar for sprite "&\ myScrolledSprite.spriteNum&", "&memberName&" are missing:"&RETURN&RETURN&\ data halt end case end case end ErrorAlert -- UTILITIES -- on GetScrollableSprites me, permittedTypes -- Returns a list of sprites containing Field or Text members, in -- the format: [", ",...] scrollableSprites = [] repeat with theSprite = 1 to the lastChannel theMember = sprite(theSprite).member case theMember.type of #field, #text: memberName = theMember.name if memberName = EMPTY then memberName = theMember else memberName = QUOTE&memberName"E end if memberName = theMember.type&&memberName scrollableSprites.append ("sprite "&theSprite&": "&memberName) end case end repeat return scrollableSprites end GetScrollableSprites on PermittedMemberTypes me -- sent by: -- getBehaviorDescription -- getPropertyDescriptionList -- ErrorAlert return [#animgif, #bitmap, #filmLoop, #flash, #movie, #picture, #shape] end PermittedMemberTypes -- AUTHOR-DEFINED PARAMETERS -- on getPropertyDescriptionList me if not the currentSpriteNum then exit -- Error check: does current sprite contain appropriate member type? theMember = sprite(the currentSpriteNum).member memberType = theMember.type permittedTypes = PermittedMemberTypes(me) if not permittedTypes.getPos(memberType) then return ErrorAlert (me, #getPDLError, permittedTypes) end if -- Find sprites with field or text members scrollableList = GetScrollableSprites (me) if not scrollableList.count then return ErrorAlert (me, #noScrollableSprites) end if return \ [ \ #myScrollRole: \ [ \ #comment: "Current sprite acts as:", \ #format: #symbol, \ #range: [#upArrow, #downArrow, #dragger, #bar], \ #default: 1 \ ], \ #ourScrolledElement: \ [ \ #comment: "Scroll the member of", \ #format: #string, \ #range: scrollableList, \ #default: scrollableList[1] \ ], \ #myStandardMember: \ [ \ #comment: "Standard member:", \ #format: #graphic, \ #default: theMember \ ], \ #myActiveMember: \ [ \ #comment: "(Arrows only) mouseDown member:", \ #format: #graphic, \ #default: theMember \ ], \ #myMultiThreading: \ [ \ #comment: "Allow animations to continue (slower)?", \ #format: #boolean, \ #default: TRUE \ ] \ ] end getPropertyDescriptionList on getBehaviorTooltip me return \ "Use 4 graphic members (dragger, bar, up and down arrows)"&RETURN&\ "to create a dynamic scrollbar for Text or Field members."\ &RETURN&RETURN&\ "Drop this behavior on each sprite separately."&RETURN&RETURN&\ " Sprites can pass over the scrollbar and animations can"&RETURN&\ "continue while the user scrolls."&RETURN&RETURN&\ "The behavior accepts Lingo calls to:"&RETURN&\ "+ set the scrolltop of the scrolled member to a given value."&\ RETURN&\ "+ swap the current member of the sprite." end getBehaviorTooltip on getBehaviorDescription me return \ "CUSTOM SCROLL BAR"&RETURN&RETURN&\ "Create dynamic scrollbars with your own artwork. Such scrollbars are not Direct-To-Stage, so other sprites can appear over the top of them."&RETURN&RETURN&\ "You will need to create four graphic members:"&RETURN&\ "+ up arrow"&RETURN&\ "+ down arrow"&RETURN&\ "+ dragger"&RETURN&\ "+ backing bar"&RETURN&RETURN&\ "You may wish to use two additional members, to indicate that the arrow buttons have been pressed:"&RETURN&\ "+ up arrow (pressed state)"&RETURN&\ "+ down arrow (pressed state)"&RETURN&RETURN&\ "Place the four standard members on the Stage, and drop this behavior onto each of them. Choose how the current sprite is to act in the appropriate pop-up menu in the Behavior Parameters dialog."&RETURN&RETURN&\ "For each element you can choose whether animations should continue in the background. This option will tend to slow both the animations and the scrolling process, especially if applied to the arrow buttons."&RETURN&RETURN&\ "The various sprites will position themselves automatically to the right of the sprite-to-be-scrolled when the movie runs. They revert to their original positions when it stops. To avoid flashes, it would be a good idea to position them by hand in their intended positions."&RETURN&RETURN&\ "If you use a border on your field members, the scrollbar will sit outside the border. Field box shadows are not recommended. You can always fake external borders and box shadows with shape members (this even gives you a choice of colors)."&RETURN&RETURN&\ "To make authoring easier, this behavior will continue to work if you change EITHER the sprite channel OR the member in the chosen channel. If you change both, the behavior will no longer know what to scroll."&RETURN&RETURN&\ "If you do change either the sprite or the member, and then reopen the Behavior Parameters dialog for one of the elements, this will put that behavior out of synch with the others. Simply reopen and close the Parameters dialogs for each of the other elements. If you do not do so, you will receive multiple alerts."&RETURN&RETURN&\ "This behavior can be used to scroll both editable and non-editable Fields and Text members. For editable members, however, it does not automatically update the dragger position when the length of the text changes, nor does it make the editable member scroll automatically when the user drags the mouse to create a selection."&RETURN&RETURN&\ "PERMITTED MEMBER TYPES:"&RETURN&PermittedMemberTypes (me)&RETURN&RETURN&\ "PARAMETERS:"&RETURN&\ "* Current sprite acts as (up|down arrow | dragger | bar)"&RETURN&\ "* Scroll the member of : "&RETURN&\ "* Standard member (this should not need to be set)"&RETURN&\ "* Member to display when arrow buttons are pressed"&RETURN&\ "* Allow animations to continue"&RETURN&RETURN&\ "PUBLIC METHODS"&RETURN&\ "=> Scroll the text/field member to a given position"&RETURN&\ "=> Swap the text/field member to be scrolled"&RETURN&\ "=> Get the behavior reference" end getBehaviorDescription