Irregular Slider
Slider behavior that can follow a curve path and do various tasks.
property spriteNum -- The usual.
property myLocs -- The list of all slider positions.
property myCurrentPos -- The current position in this list.
property myCurrentValue -- The normalized value of the slider.
property myMin, myMax -- The low and high slider values.
property myNotificationStyle -- Set by BI. How often are updates broadcast?
property myNotificationTarget -- Set by BI. Where do these messages go?
property myVideoTarget -- Optional property, when controlling video.
property myTempoChange -- Optional property, when controlling tempo.
--=========== PUBLIC METHODS =========================
--
-- The following three methods are those that interact with
-- the outside world.
--
-- BroadcastNewValue is called internally and dispatches appropriate
-- messages outward.
--
-- IrregularSlider_GetCurrentValue and _SetCurrentValue will return and
-- assign the slider's current value, respectively. They use the
-- BehaviorName_MethodName convention to avoid namespace collisions with
-- other behaviors.
--
-- IrregularSlider_GetCurrentValue: Call to retrieve slider's current value.
-- IrregularSlider_SetCurrentValue: Call to assign slider's current value.
-- IrregularSlider_NewValue: Handle for custom events in movie or score script.
on BroadcastNewValue me
-- You can automatically notify other behaviors when the slider
-- value changes. This choice is set by the getPropDesc dialog,
-- and is not changed by calls from the outside world. This handler
-- does affect the outside world, however.
--
-- Note the "IrregularSlider_NewValue" message which can be sent
-- out to movie scripts, frame scripts, or other behaviors. If
-- choosing such a recipient for this message then you'd need to
-- write a handler of this name in the movie or score script.
case (myNotificationTarget) of
#movieScript: IrregularSlider_NewValue myCurrentValue
#allSprites: sendAllSprites #IrregularSlider_NewValue, myCurrentValue
#frameScript: sendSprite -1, #IrregularSlider_NewValue, myCurrentValue
#tempo: set myTempoChange to integer(myCurrentValue)
#soundLevel: set the soundLevel to integer(myCurrentValue)
#sound1: set the volume of sound 1 to myCurrentValue
#sound2: set the volume of sound 2 to myCurrentValue
#videoSpeed: set the movieRate of sprite myVideoTarget to myCurrentValue
#videoTime: set the movieTime of sprite myVideoTarget to myCurrentValue
#null: nothing
otherwise: Error me, #badNotificationTarget
end case
end
on IrregularSlider_GetCurrentValue me, collectionList
-- Other behaviors can query this sprite's current value
-- through this exposed method.
-- Note the BehaviorName_BehaviorMethod syntax to avoid
-- namespace collisions with broadcast methods.
-- The sendAllSprites or sendSprite or call commands work
-- upon multiple objects and so a regular "return" value
-- will not be available. Instead we pass in a reference to a
-- list object, and the called behavior adds its desired value
-- to this list for ready retrieval by the calling behavior.
-- Example:
-- set theList to []
-- sendAllSprites #IrregularSlider_GetCurrentValue, theList
-- set theNewValue to getAt(theList, 1)
add collectionList, myCurrentValue
end
on IrregularSlider_SetCurrentValue me, newValue
-- Other behaviors can set the slider's value through
-- this exposed method. The range of newValue must be between
-- zero and one. It automatically changes the slider's position
-- and then changes its controlled value.
-- Example:
-- sendAllSprites #IrregularSlider_SetCurrentValue, 0.75
if min(max(0, newValue), 1) <> newValue then Error me, #setValueOutofBounds
set myCurrentPos to integer(newValue * count(myLocs))
set the loc of sprite spriteNum to getAt(myLocs, myCurrentPos)
set myCurrentValue to myMin + (myMax - myMin) * newValue
BroadcastNewValue(me)
end
--================== STANDARD EVENTS ===================
--
-- beginSprite, exitFrame, mouseDown, stepFrame
on beginSprite me
InitProperties me
-- Get list of stored slider positions from the castmember
-- script of the slider sprite:
if the scriptText of the member of sprite spriteNum = "" then Error(me, #noData)
set myLocs to GetInitialData(script (the member of sprite spriteNum))
if myLocs = void then Error(me, #noData)
-- This handling assumes that the slider starts at its minimum value.
-- Scripters may wish to change the following three lines:
set myCurrentPos to 1
set the loc of sprite spriteNum to getAt(myLocs, 1)
set myCurrentValue to myMin
end
on exitFrame me
-- This is an odd handler. The only time it's needed is if
-- setting the tempo. From what I've seen we cannot issue a
-- puppetTempo from within a stepFrame handler. Instead, the
-- stepFrame sets the 'myTempoChange' property, and then this
-- exitFrame handler will later act upon this instruction.
if voidP(myTempoChange) then exit
if the frameTempo <> myTempoChange then puppetTempo myTempoChange
set myTempoChange to VOID
end
on mouseDown me
-- This is a common way to handle asynchronous operations...
-- once the mouse is clicked then this behavior starts to receive
-- regular stepFrame events until the mouse is released. This
-- approach lets other animations continue in the background, etc.
add the actorList, me
end
on stepFrame me
if the mouseUp then
deleteOne(the actorList, me)
if myNotificationStyle = #notifyonMouseUp then BroadcastNewValue(me)
end if
set mouseLoc to point(the mouseH, the mouseV)
set currentDistance to GetDistance(mouseLoc, getAt(myLocs, myCurrentPos))
set tempPos to myCurrentPos
-- Find the nearby slider position which is closest to the mouse:
if myCurrentPos > 1 then set leftPos to myCurrentPos - 1
else set leftPos to myCurrentPos
if myCurrentPos < count(myLocs) then set rightPos to myCurrentPos + 1
else set rightPos to myCurrentPos
if GetDistance(mouseLoc, getAt(myLocs, leftPos)) < currentDistance then
set myCurrentPos to leftPos
else if GetDistance(mouseLoc, getAt(myLocs, rightPos)) < currentDistance then
set myCurrentPos to rightPos
end if
set the loc of sprite spriteNum to getAt(myLocs, myCurrentPos)
set currentRatio to (float(myCurrentPos - 1) / (count(myLocs) - 1))
set myCurrentValue to myMin + (myMax - myMin) * currentRatio
if myNotificationStyle = #notifyDuringDrag then BroadcastNewValue(me)
end
on GetDistance ptA, ptB
-- Utility routine. Given two points, returns the square
-- of the distance between them. (sqrt calcs are avoided
-- for speed reasons... gives relative magnitude only.)
-- Called by stepFrame method.
set deltaH to the locH of ptA - the locH of ptB
set deltaV to the locV of ptA - the locV of ptB
return deltaH * deltaH + deltaV * deltaV
end
on InitProperties me
-- Routine to turn string-based configurable properties to
-- faster-evaluating symbols. Some believe that input values should
-- never be changed, but this sure beats having duplicate sets of
-- properties. Called by beginSprite method.
case (myNotificationStyle) of
"Notify during drag": set myNotificationStyle to #notifyDuringDrag
"Notify on mouseUp": set myNotificationStyle to #notifyonMouseUp
"Don't notify; will call": set myNotificationStyle to #null
end case
case (myNotificationTarget) of
"Notify movie script": set myNotificationTarget to #movieScript
"Notify all sprites": set myNotificationTarget to #allSprites
"Notify frame script": set myNotificationTarget to #frameScript
"Change Tempo": set myNotificationTarget to #tempo
"Change the sound level": set myNotificationTarget to #soundLevel
"Adjust sound channel 1": set myNotificationTarget to #sound1
"Adjust sound channel 2": set myNotificationTarget to #sound2
"No target; will call": set myNotificationTarget to #null
"Control video's speed": set myNotificationTarget to #videoSpeed
"Control video's time": set myNotificationTarget to #videoTime
otherwise: put "bad notification target: " & myNotificationTarget
end case
-- Finally set special-case min/max values and other initializations:
case (myNotificationTarget) of
#soundLevel:
set myMin to 0
set myMax to 7
#sound1, #sound2:
set myMin to 0
set myMax to 255
#videoSpeed:
set myVideoTarget to FindFirstVideoChannel()
#videoTime:
set myVideoTarget to FindFirstVideoChannel()
set myMin to 0
set myMax to the duration of member (the member of sprite myVideoTarget)
end case
end
on RecordSpriteLocs me
-- Routine to record all locations of the current sprite over its
-- lifetime, and then to store this data in a function in the
-- castmember script for ready retrieval at runtime.
-- Called by getPropertyDescriptionList method.
if count(the scoreSelection) < 1 then exit
if count(the scoreSelection) > 1 then Error me, #multipleSprites
set theSelection to getAt(the scoreSelection, 1)
set spriteNum to getAt(theSelection, 1)
set firstFrame to getAt(theSelection, 3)
set lastFrame to getAt(theSelection, 4)
set origFrame to the frame
-- March through the Score and record slider positions:
go firstFrame
set myFirstMember to the member of sprite spriteNum
set theLocs to []
repeat while the frame < lastFrame
add theLocs, the loc of sprite spriteNum
go the frame + 1
end repeat
-- Write these sprite positions to the castmember script for storage.
-- First check that there is actually a script to call.
-- Note that scripts that cannot handle a "GetInitialData" message will
-- pass it back to this script's own void-returning handler, below.
if the scriptText of myFirstMember = "" then set the scriptText of myFirstMember to " "
set oldData to GetInitialData(script myFirstMember)
set theScript to "on GetInitialData" & RETURN
put "return " & theLocs & RETURN after theScript
put "end" & RETURN & RETURN after theScript
-- The following stores the previous position data as a comment within
-- the member script. You could instead set this routine up so that it
-- adds "return newData" at line 2 of the scriptText, giving you a
-- (potentially very long) history list of editing changes.
put "-- Previous data: " & oldData & RETURN & RETURN after theScript
set the scriptText of myFirstMember to theScript
go origFrame
end
on FindFirstVideoChannel
-- Find if there are any videos in the current frame.
-- Return the channel if so, or FALSE if not.
-- Called by getPropDescList and InitProperties.
--
-- (I believe that in later versions of Director you may be able to
-- replace the "string(member)" test with "objectP(member)", although
-- I've not tested this phrase across all current versions of
-- Director yet, myself.)
repeat with i = 1 to 120
if string(the member of sprite i) <> "(member 0 of castLib 0)" then
if the type of the member of sprite i = #digitalVideo then return i
end if
end repeat
return FALSE
end
on GetInitialData me
-- The beginSprite method calls a GetInitialData function in
-- the castmember script. If for some reason that script has
-- been altered since it was created then an error can result.
-- By having a GetInitialData handler in this calling script
-- here, we'll fall back to this routine if the castmember
-- script cannot handle this message. The beginSprite method
-- will test for whether it receives the following VOID result.
-- Tricky, but it eliminates one way to achieve an error.
return VOID
end
--=================== STANDARD BEHAVIOR FUNCTIONS ================
--
-- getPropDescList, getBehaviorDescription, Error
on getPropertyDescriptionList me
if the currentSpriteNum > 0 then
RecordSpriteLocs me
set videoChan to FindFirstVideoChannel()
set theProps to [:]
set c to "What to do when slider changes?"
set f to #string
set r to ["Notify movie script", "Notify all sprites", "Notify frame script", "Change Tempo", "Change the sound level", "Adjust sound channel 1", "Adjust sound channel 2", "No target; will call"]
if videoChan <> 0 then
add r, "Control video's time"
add r, "Control video's speed"
end if
set d to "Notify all sprites"
addProp theProps, #myNotificationTarget, [#comment:c, #format:f, #range:r, #default:d]
set c to "When to notify?"
set f to #string
set r to ["Notify during drag", "Notify on mouseUp", "Don't notify; will call"]
set d to "Notify during drag"
addProp theProps, #myNotificationStyle, [#comment:c, #format:f, #range:r, #default:d]
set c to "What's the minimum value for this slider?"
set f to #float
set d to 0.0
addProp theProps, #myMin, [#comment: c, #format:f, #default:d]
set c to "What's the maximum value for this slider?"
set f to #float
set d to 1.0
addProp theProps, #myMax, [#comment: c, #format:f, #default:d]
return theProps
end if
end
on getBehaviorDescription me
set line1 to " This is a generic slider routine. It is self-contained and does not rely on other sprites. "
set line2 to "You can use it for slider paths which are not straight lines... works well for curved slider paths, circles, etc." & RETURN
set line3 to " You can have this behavior send messages to other sprites, or to the frame script, or to the movie script when the slider is dragged or released. "
set line4 to "You can also use it to control the overall sound level, the volumes of the first two channels, or the video speed or time of another video in the Score. "
set line5 to "Or you could have the behavior not send any messages at all, but instead have other behaviors call into this slider to request its current value." & RETURN & RETURN
set line6 to "TO USE:" & RETURN
set line7 to " Animate your slider in the Score along the path you wish to drag it. The more frames you animate over, the smoother-yet-slower the drag. The animation path will be stored and will determine the path of the drag." & RETURN
set line8 to " Apply the behavior. You'll see the sprite animate along its path. This data will then be stored in the sprite's castmember script. (Open up the castmember script to see how the slider positions are stored.)" & RETURN
set line9 to " At this point you can choose your options from the Parameter dialog. The top popup describes what happens, the second describes when it happens, and the two lower fields are optional for those uses where you need to set a minimum and maximum value for the slider. (Won't need it for video scrubbing or soundlevel, for instance.)" & RETURN & RETURN
set line10 to "Tip: The initial number of frames of the slider's animation will determine how smoothly the slider moves... the more steps, the slower. Each time you click the Parameters Dialog in the Behavior Inspector then this list of sprite positions will be recalculated. "
set line11 to "The previous values will still be stored in the script too... if you open the slider's castmember script you'll see them commented out beneath the main handler. You can replace these back into the function if you wish. " & RETURN
set line12 to " Once you've recorded the slider positions you can drag the slider out over an arbitrary period of time... the rhythm is to determine the number of steps, configure the behavior, and then drag out the slider sprite so it's on Stage as long as you wish." & RETURN & RETURN
set line13 to "(Note that the options to control video will only appear if there is a video sprite in the frame in which you assign and configure this behavior. Also note that each sliding castmember can be used for only one path, because path data is stored in the member script.)" & RETURN
on Error me, whatError
-- This is just a regular error-handling routine. By calling it
-- you can keep all the alert messages in one place, and reduce
-- clutter within the scripts themselves.
case (whatError) of
#noData:
alert "The sprite in channel " & spriteNum & " seems to have changed since it was initialized... you may wish to reset its slider behavior."
#multipleSprites:
alert "This behavior can be applied to only one sprite at a time."
#badNotificationTarget:
alert "Scripting error -- I don't know how to handle an event sent to " & myNotificationTarget
#setValueOutofBounds:
alert "Error when trying to set slider value from outside handler."
otherwise:
alert "Unknown error: " & whatError
end case
halt
end
Contact
MMI
36 South Court Sq
Suite 300
Newnan, GA 30263
USA