-- HISTORY BUTTON -- -- -- © July 2000, James Newton -- -- This behavior continually surveys the movement of the playback -- head. If the playback head jumps, either forwards or backwards, -- it checks whether it has arrived at a new marker or a new movie. -- If so, it records its previous position in the -- property. -- -- A mouseUp on the current sprite will jump the playback head back to -- this previous position. A series of mouseUps will retrace the -- user's course through the movie(s). -- -- You may not wish to provide a "History Button" in all sections of -- your movie. However, this behavior will remain active even when -- the sprite it was attached to is no longer present. It achieves -- this by placing itself on the actorList. -- -- When a new instance of this behavior is created on a new sprite, it -- first checks if an instance already exists on the actorList. If -- so, the new instance will detach itself from the current sprite and -- attach the existing instance to the current sprite in its place. -- -- Only one sprite at a time should have this behavior. Only one -- instance of the behavior will ever be created. Though this -- instance may be attached to several sprites at a time, it will only -- send messages to the sprite it was most recently attached to. -- -- If the "Mouse States" behavior is attached to the same sprite, the -- "History Button" instance will activate and disactivate the -- "Mouse States" instance, depending on whether there is a marker to -- jump back to. -- -- NOTE: You may not want the user to be able to jump back to a -- particular marker. In this case, the marker name should include -- the asterisk character (*). If an asterisk appears in a marker -- name, this behavior will not record the marker. -- PROPERTY DECLARATIONS -- property spriteNum property mySprite -- sprite(me.spriteNum) -- The following linear lists contain elements of the format: -- [#frame: , #label: "string", #movie: "absolute path"] property myBackList -- list of markers previously visited property myResumeList -- list of markers revisited using History property myVisitedList -- list of all markers visited, in the -- order of the first visit. property myMarkerList -- [: "marker name", ...] property myPreviousFrame -- value the frame on previous stepFrame property myFrame -- marker(0) property myLabel -- string marker name of marker(0) property myMovie -- absolute path to this movie property myResumeInstance -- instance of Resume behavior property myBugFlag -- Due to a bug in D7.02 and beyond, the -- scriptInstanceList of higher-numbered -- channels may be set to 0 until the -- playback head moves back a frame, if -- linked #movie members of a certain type -- are present. If this occurs, myBugFlag -- is set to TRUE and the setButtonStates() -- will be nagged until it can respond. If -- you are not using Linked Movie members, -- myBugFlag will always be FALSE. -- EVENT HANDLERS -- on beginSprite(me) me.initialize() end beginSprite on endSprite(me) mySprite = VOID end endSprite on stepFrame(me) if myBugFlag then myBugFlag = FALSE me.setButtonStates() -- may set myBugFlag back to TRUE end if me.checkMarker() end stepFrame on mouseUp(me) me.goBack() end mouseUp -- INTER-SPRITE COMMUNICATION -- on findHistoryInstance(me, returnList) ------------------------------- -- Called by the initialize() handler of a different instance of -- this behavior, to detect if an existing instance is present on -- the actorList. -- -- This may also be called by a different script (on a "Go back to -- the beginning" button, for instance), in order to recuperate a -- pointer to the History instance. -------------------------------------------------------------------- returnList.append(me) end findHistoryInstance on subscribe(me, ID, returnList, resumeInstance) --------------------- -- Called by the findAncestor() handler of the "Resume Button" -- behavior, which seeks to set its ancestor to the "History Button" -- instance on the actorList. -------------------------------------------------------------------- if ID <> #history then exit else if not ilk(returnList, #list) then exit end if returnList.append(me) if ilk(resumeInstance, #instance) then myResumeInstance.append(resumeInstance) end if end subscribe on unsubscribe(me, resumeInstance) ---------------------------------- -- Called by the endSprite() handler of the "Resume Button" -- behavior. -------------------------------------------------------------------- myResumeInstance.deleteOne(resumeInstance) end unsubscribe -- PUBLIC METHOD -- on getList(me, listName, returnList) --------------------------------- -- Called from a different script -- -- * Provides the current list of markers visited. This could be -- used to display the labels in a dropdown list, for instance. -------------------------------------------------------------------- case listName of #history: theList = myBackList #resume: theList = myResumeList #visited: theList = myVisitedList otherwise -- Provide the entire list theList = [\ #history: myBackList, \ #resume: myResumeList, \ #visited: myVisitedList \ ] end case if ilk(returnList, #list) then returnList.append(theList) else return theList end if end getList -- PRIVATE METHODS -- on initialize(me) ---------------------------------------------------- -- Sent by beginSprite() -- -- * Places this instance on the actorList (if there no instance of -- this behavior is there already), or replaces this instance with -- the one copied already on the actorList -------------------------------------------------------------------- mySprite = sprite(spriteNum) existingInstance = [] call(#findHistoryInstance, the actorList, existingInstance) existingInstance = existingInstance.getLast() if ilk(existingInstance, #instance) then -- An instance of this behavior has already been created: -- adopt it in the place of the current instance on this sprite. behaviors = mySprite.scriptInstanceList position = behaviors.getPos(me) behaviors[position] = existingInstance existingInstance.mySprite = mySprite -- Update button displays existingInstance.setButtonStates() else -- This is the first time the behavior has been instanciated: -- initialize its properties (the actorList).append(me) myBackList = [] myResumeList = [] myVisitedList = [] myMarkerList = me.getMarkerList() -- "the markerList" in D8 myFrame = marker(0) myLabel = myMarkerList.getaProp(myFrame) if not stringP(myLabel) then -- There are no markers in this movie myLabel = "" end if myMovie = the moviePath&the movieName -- Locate a "Resume Button" behavior, if one exists myResumeInstance = [] sendAllSprites(#subscribe, #resume, myResumeInstance, me) -- Update button displays me.setButtonStates() end if end initialize on setButtonStates(me) ------------------------------------------------ -- Sent by initialize(), mouseUp() -- -- * Informs any "Mouse States" behaviors on the same sprite that -- there are, or are not, any markers to jump back to. -------------------------------------------------------------------- -- There is a "Resume Button" which will also need to update call(#setButtonState, myResumeInstance) -- "state", not "states" if not ilk(mySprite, #sprite) then -- This instance is not currently attached to a sprite exit end if behaviors = mySprite.scriptInstanceList if not ilk(behaviors, #list) then -- See the description of myBugFlag in the Property Declarations -- for details. myBugFlag = TRUE exit end if call(#toggleActive, mySprite.scriptInstanceList, myBackList.count()) end setButtonStates on checkMarker(me) --------------------------------------------------- -- Sent by stepFrame() -- -- * Surveys the movement of the playback head, and calls -- treatJump() if the playback head is jumped outside the current -- section. -- -- A "section" is considered to be a sequence of frames starting -- with a marker (or frame 1 if there are no markers in the movie), -- and ending at a frame script with a "go the frame" or "go loop" -- command: -- -- Marker1 --> {Marker2} --> "go the frame" script -- Marker1 --> "go loop" script -- -- In the first example above, Marker 2 will be ignored, unless the -- playback head is jumped to a point between Marker 2 and the frame -- script. In other words, if the user *navigates* to Marker 2, it -- will be recorded. If the user navigates to Marker 1, then passes -- automatically through the Marker 2, Marker 2 will not be -- recorded. -- -- Note that the command "go the frame + 1" is not considered to be -- a jump by this handler. If you release the playback head from a -- "go the frame" loop, you will need to use "go the frame + 2" if -- you want the "go the frame" marker to be recorded. In a similar -- fashion, if you use "go next", be sure that the next marker is at -- least two frames away. -- -- Conversely, use "go the frame + 1" jumps to ensure that the -- current marker is ignored. -------------------------------------------------------------------- -- Ignore any change of marker if the playback head has not jumped -- outside the current section if myMovie = the moviePath&the movieName then case the frame of myPreviousFrame: -- The playback head is being held on the current frame by a -- "go the frame" script. exit (myPreviousFrame + 1): -- The playback head is running forward freely myPreviousFrame = the frame exit end case if myFrame = marker(0) then -- The playback head jumped, but it's still in the same section -- of the same movie. This is probably due to a "go loop" -- command. myPreviousFrame = the frame exit end if else -- The playback head has jumped to a different movie: update the -- markers list. myMarkerList = me.getMarkerList() end if -- If we get here, the playback head has jumped other than by +1 me.treatJump() end checkMarker on treatJump(me) ----------------------------------------------------- -- Sent by checkMarker() -- -- * Adds an item to myBackList when the playback head jumps outside -- the current section. -------------------------------------------------------------------- myPreviousFrame = the frame -- We've arrived at a new section or a new movie. Remember where -- we were... unless there's an asterisk in the marker name if not (myLabel contains "*") then previousSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] if myBackList.count() then if myBackList[1] <> previousSection then -- Only add the previous section if it is different from the -- first section in myBackList if the user has visited one -- or more asterisk markers. myBackList.addAt(1, previousSection) end if else myBackList.addAt(1, previousSection) end if -- Don't forget any of the places where we have already been if not myVisitedList.getPos(previousSection) then myVisitedList.append(previousSection) end if end if -- Update frame data myFrame = marker(0) myLabel = myMarkerList.getaProp(myFrame) if not stringP(myLabel) then -- There are no markers in this movie myLabel = "" end if myMovie = the moviePath&the movieName if myResumeList.count() then if not (myLabel contains "*") then thisSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] if myResumeList[1] = thisSection then -- The user could have used the forward button, but used some -- other means of navigation instead. Don't wantonly destroy -- all the data in myResumeList myResumeList.deleteAt(1) else -- We didn't use the Forward button to get this new location: -- consider this as a new excursion... myResumeList.deleteAll() end if end if end if -- Inform any "Mouse States" behaviors associated with this sprite -- and the (hypothetical) "Resume Button" that the marker lists have -- changed me.setButtonStates() end treatJump on goBack(me) -------------------------------------------------------- -- Sent by mouseUp() -- -- * Returns to the previous section visited. -------------------------------------------------------------------- if not myBackList.count() then exit end if previousSection = myBackList[1] myBackList.deleteAt(1) -- Remember where to go forward to if not (myLabel contains "*") then thisSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] myResumeList.addAt(1, thisSection) end if -- Prepare myFrame and myMovie so that the checkFrame() handler -- considers the playback head to be where it is expected to be. me.returnTo(previousSection) end goBack on goForward(me) ----------------------------------------------------- -- Sent by the mouseUp() handler of the "Go Forward Button" -- behavior which adopts this instance as its ancestor -- -- * Returns to the next section visited the first time the user -- passed this way (before retracing his or her steps with the -- "History" button). -------------------------------------------------------------------- if not myResumeList.count() then exit end if nextSection = myResumeList[1] myResumeList.deleteAt(1) -- Remember where to go back to if not (myLabel contains "*") then thisSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] myBackList.addAt(1, thisSection) end if me.returnTo(nextSection) end goForward on goFirst(me) ------------------------------------------------------- -- Called from a different script -- -- * Moves the entire history to myResumeList and returns the -- playback head to the marker where this behavior was first -- instanciated. -------------------------------------------------------------------- i = myBackList.count() if not i then exit end if -- Transfer entire history to myResumeList if not (myLabel contains "*") then thisSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] myResumeList.addAt(1, thisSection) end if repeat while i i = i - 1 if i then myResumeList.addAt(1, myBackList[1]) myBackList.deleteAt(1) else exit repeat end if end repeat -- Go back to the beginning firstSection = myBackList[1] myBackList.deleteAt(1) me.returnTo(firstSection) end goFirst on goLast(me) ------------------------------------------------------- -- Called from a different script -- -- * Moves the entire history to myBackList and returns the -- playback head to the marker most recently navigated to via some -- other means (not via the History behavior. -------------------------------------------------------------------- i = myResumeList.count() if not i then exit end if -- Transfer entire history to myBackList if not (myLabel contains "*") then thisSection = [#frame:myFrame, #label:myLabel, #movie:myMovie] myBackList.addAt(1, thisSection) end if repeat while i i = i - 1 if i then myBackList.addAt(1, myResumeList[1]) myResumeList.deleteAt(1) else exit repeat end if end repeat -- Go to the newest section lastSection = myResumeList[1] myResumeList.deleteAt(1) me.returnTo(lastSection) end goLast on returnTo(me, visitedSection) -- Prepare myFrame and myMovie so that the checkFrame() handler -- considers the playback head to be where it is expected to be. myFrame = visitedSection.frame myLabel = visitedSection.label myMovie = visitedSection.movie if myMovie = the moviePath&the movieName then go myFrame else go myFrame of movie myMovie end if -- Inform any "Mouse States" behaviors associated with this -- "Resume Button" and the ancestor's "History Button" that -- the marker lists have changed if ilk(me[#ancestor], #instance) then -- This handler is in the ancestor of "Resume Button" call(#setButtonStates [me.ancestor]) else -- The "History Button" instance was called directly me.setButtonStates() end if end returnTo -- UTILITY HANDLERS -- on getMarkerList(me) ------------------------------------------------- -- Called by initialize(), checkMarker() -- -- * Creates a property list of all marked frames of the format -- [: "string marker name", ...] -- -- Director 8 includes a movie property, "the markerList", which -- makes this handler obsolete in versions after D7.0.2 -------------------------------------------------------------------- markerList = [:] markerList.sort() lastCheckedMarker = 0 if marker(1) <> marker(-16000) then -- We're after the first marker: work back from the nearest one i = 0 repeat while TRUE checkMarker = marker(i) if checkMarker = lastCheckedMarker then exit repeat end if lastCheckedMarker = checkMarker markerList.addProp(checkMarker, 0) i = i - 1 end repeat end if if marker(0) <> marker(16000) then -- We're before the last marker: work forward from the next one i = 1 repeat while i checkMarker = marker(i) if checkMarker = lastCheckedMarker then exit repeat end if lastCheckedMarker = checkMarker markerList.addProp(checkMarker, 0) i = i + 1 end repeat end if theLabels = the labelList i = markerList.count() if not i then if theLabels <> "" then -- There is a marker, but only one markerList.addProp(marker(0), 0) i = 1 end if end if -- Determine the name of each marker repeat while i markerList[i] = line i of theLabels i = i - 1 end repeat return markerList end getMarkerList on getMarkerName(me, theFrame) --------------------------------------- -- Not used in this behavior but included for completeness -- -- * Returns the name of the marker of frame , or, if this -- frame has no marker of its own, the name of the first marker to -- the left. -------------------------------------------------------------------- markerNumber = myMarkerList.findPos(theFrame) if not markerNumber then -- The given frame has no marker of its own: find previous marker markerNumber = myMarkerList.findPosNear(theFrame) - 1 if not markerNumber then -- The given frame is before the first marker markerNumber = 1 end if end if return myMarkerList[markerNumber] end getMarkerName on getFrameLabel(me, theFrame) --------------------------------------- -- Not used in this behavior but included for completeness -- -- * Returns the marker name of an arbitrary frame, or zero if the -- frame has no marker. This is an extension of the built-in -- "the framelabel" property... which only works with the current -- frame. -------------------------------------------------------------------- markerNumber = myMarkerList.findPos(theFrame) if not markerNumber then return 0 end if return myMarkerList[markerNumber] end getFrameLabel -- BEHAVIOR DESCRIPTION -- on isOKToAttach(me, spriteType, spriteNumber) return spriteType = #graphic end isOKToAttach --on getBehaviorTooltip(me) -- return \ --"A utiliser avec les acteurs graphiques."&RETURN&RETURN&\ --"Lorsqu'un utilisateur clique sur une"&RETURN&\ --"image-objet avec ce comportement, la"&RETURN&\ --"tête de lecture retourne au dernier"&RETURN&\ --"repère visité." --end getBehaviorTooltip -- -- -- --on getBehaviorDescription(me) -- return \ --"HISTORIQUE"&RETURN&RETURN&\ --"Cliquer sur cette image-objet avec ce comportement renvoie "&\ --"l'utilisateur aux repères déjà visités dans toutes les "&\ --"animations."&RETURN&RETURN&\ --"Pendant la période de création de votre animation, le bouton "&\ --"'Historique' peut vous ramener vers d'autres animations (sans "&\ --"aucun rapport avec l'animation en cours) dans lesquelles vous "&\ --"avez utilisé le même comportement. Si vous trouvez cela "&\ --"déconcertant, tapez 'clearGlobals' dans la fenêtre des Messages "&\ --"avant de lancer votre animation courante."&RETURN&RETURN&\ --"Ce comportement sert aussi d'ancêtre (ancestor) pour le "&\ --"comportement 'Poursuivre l'historique'."&RETURN&RETURN&\ --"TYPES D'ACTEURS AUTORISES"&RETURN&"Acteurs graphiques"&\ --RETURN&RETURN&\ --"PARAMETRES: Aucun"&RETURN&RETURN&\ --"COMPORTEMENTS ASSOCIES :"&RETURN&\ --" + Poursuivre l'historique"&RETURN&\ --" + Bouton poussoir (pour altérer états survol / bouton souris "&\ --"enfoncé)" --end getBehaviorDescription on getBehaviorTooltip(me) return \ "Use with graphic members."&RETURN&RETURN&\ "When the user clicks on a sprite with"&RETURN&\ "this behavior the playback will return"&RETURN&\ "to the markers/movies previously visited." end getBehaviorTooltip on getBehaviorDescription(me) return \ "HISTORY BUTTON"&RETURN&RETURN&\ "© July 2000, James Newton "&RETURN&RETURN&\ "Clicking on a sprite with this behavior will take the user "&\ "back to previously visited markers in all movies."&RETURN&RETURN&\ "This behavior places a copy of itself on the actorList. All new "&\ "instances of this behavior will be replaced by the copy on the "&\ "actorList: only one instance will ever be created. This means "&\ "that you do not have to have a 'History' button in all frames "&\ "of all your movies. The instance on the actorList will survey "&\ "the playback head continuously, even when it is not present in a "&\ "sprite. Every time the playback head jumps to a new marker, this "&\ "instance will automatically record the jump."&RETURN&RETURN&\ "If you do not wish the user to return to a given marker using "&\ "this behavior, include an asterisk in the marker's name. "&\ "Example: ""E&"*Secret marker*""E&"."&RETURN&RETURN&\ "This instance also serves as ancestor for the "&\ "'Resume Button' behavior. The goForward() handler included "&\ "in the current script is triggered by the 'Resume Button'. "&\ "(It could also be called from some other script)."&RETURN&RETURN&\ "PERMITTED MEMBER TYPES"&RETURN&"Graphic members"&RETURN&RETURN&\ "PARAMETERS: None"&RETURN&RETURN&\ "ASSOCIATED BEHAVIORS:"&RETURN&\ " + Resume Button"&RETURN&\ " + Mouse States (to alter rollover / mouseDown states)" end getBehaviorDescription