-- MENU FIELD -- -- -- © August 2000, James Newton -- Revised 26 September 2000 -- PROPERTY DECLARATIONS -- property spriteNum -- author-defined parameters property reaction -- #mouseUp | #doubleClick property handler -- #handlerName to call in ... property location -- movie | behaviors | sendAllSprites | actorList -- internal properties property mySprite -- sprite(me.spriteNum) property myMember -- mySprite.member property myTextTop -- locV of top of text (adjusted for sprite.loc, -- border and margin) property myMouseLine -- line currently under the mouse property myMouseOver -- TRUE if rollover(spriteNum) on last exitFrame -- SPRITE EVENTS -- on beginSprite(me) me.initialize() end beginSprite on endSprite(me) end endSprite -- EVENT HANDLERS -- on mouseUp(me) if the doubleClick = (reaction = #doubleClick) then me.selectLine() end if end mouseUp on exitFrame(me) call(#popUpMode, [me]) -- Optional popUpMode handler if myMouseOver then if not rollover(spriteNum) then -- The mouse has just left the sprite myMouseOver = FALSE end if else if rollover(spriteNum) then -- The mouse has just rolled onto the sprite myMouseOver = TRUE else -- The mouse is still elsewhere exit end if me.updateHilite(myMouseOver) end exitFrame -- PUBLIC METHOD -- on setItems(me, itemData, ID) ---------------------------------------- -- Sent by a handler in a different script -- -- * Shows the sprite at the chosen loc with the appropriate text -------------------------------------------------------------------- case ID of void, handler: -- continue otherwise -- This instance is not the intended target of the call exit end case -- if myDownTime or myUpTime then -- exit -- end if if listP(itemData) then itemData = listToString(itemData) end if if stringP(itemData) then myMember.text = itemData myMember.scrollTop = 0 end if end setItems -- PRIVATE METHODS -- on initialize(me) ---------------------------------------------------- -- Sent by beginSprite() -------------------------------------------------------------------- mySprite = sprite(me.spriteNum) myMember = mySprite.member edgeWidth = myMember.border + (myMember.margin / 2) myTextTop = mySprite.top + edgeWidth end initialize on updateHilite(me, mouseOver) --------------------------------------- -- Sent by exitFrame() -- -- * Hilites the line under the mouse, or removes the hilite if the -- mouse is not directly over a line. -------------------------------------------------------------------- if mouseOver then currentLine = the mouseLine theText = myMember.text -- The mouse may be before the first line or after the last line, -- in which case the last line should not appear selected if currentLine = 1 then if the mouseV < myTextTop then -- The mouse is above the first line currentLine = 0 end if else if currentLine = theText.line.count then charCount = theText.char.count -- Where is the bottom (-left corner) of the last character? vLoc = (charPosToLoc(myMember, charCount)).locV + myTextTop vLoc = vLoc - myMember.scrollTop if the mouseV > vLoc then -- The mouse is below the last character currentLine = 0 end if end if else -- The mouse is no longer over the field currentLine = 0 end if if myMouseLine = currentLine then -- Status quo exit end if -- The mouse has just moved over a new line myMouseLine = currentLine if myMouseLine then -- Hilite the current line if myMouseLine = 1 then firstChar = 1 else firstChar = (theText.line[1..myMouseLine - 1]).char.count + 2 end if finalChar = firstChar + theText.line[myMouseLine].char.count hilite char firstChar to finalChar of field myMember else -- Remove the hilite hilite char the maxInteger of field myMember end if end updateHilite on selectLine(me) ---------------------------------------------------- -- Sent by mouseUp() -- -- * Sends the chosen handler to the chosen scripts/instances, with -- the parameters , . -------------------------------------------------------------------- if myMouseLine < 1 then exit end if selectedText = myMember.line[myMouseLine] case location of "a movie script": sendSprite(0, handler, selectedText, myMouseLine) "a behavior on the same sprite": behaviors = mySprite.scriptInstanceList call(handler, behaviors, selectedText, myMouseLine) "behaviors on other sprites": sendAllSprites(handler, selectedText, myMouseLine) "an instance on the actorList": call(handler, the actorList, selectedText, myMouseLine) end case end selectLine -- -- property myDownTime -- the value of the ticks when this instance -- received a #showMenu message (This was -- presumably sent on mouseDown). property myUpTime -- the value of the ticks when the mouse was -- first perceived to be up after being down. -- Public Method -- on showMenu(me, spriteLoc, itemData) --------------------------------- -- Sent by a mouseDown() handler in a different script -- -- * Shows the sprite at the chosen loc with the appropriate text -------------------------------------------------------------------- if myDownTime or myUpTime then exit end if if not ilk(spriteLoc, #point) then exit end if if listP(itemData) then itemData = listToString(itemData) end if if stringP(itemData) then me.fitMemberToText(itemData) end if me.adjustForEdges(spriteLoc) mySprite.loc = spriteLoc mySprite.locZ = the maxInteger myDownTime = the ticks myUpTime = 0 edgeWidth = myMember.border + (myMember.margin / 2) myTextTop = spriteLoc.locV + edgeWidth end showMenu -- Private Methods -- on fitMemberToText(me, theText) -------------------------------------- -- Sent by showMenu() -- -- * Sets the width of the member to the width of the longest line -- of text. -------------------------------------------------------------------- myMember.text = theText repeat while the last char of theText = RETURN delete the last char of theText end repeat memberRect = myMember.rect myMember.rect = rect (0, 0, 8000, 0) maxWidth = 0 charCount = theText.char.count + 1 -- forces charPosToLoc() to end lineCount = theText.line.count repeat while lineCount lineWidth = charPosToLoc(myMember, charCount).locH if lineWidth > maxWidth then maxWidth = lineWidth end if charsInLine = theText.line[lineCount].char.count + 1 -- ""&RETURN charCount = charCount - charsInLine lineCount = lineCount - 1 end repeat memberRect.right = maxWidth myMember.rect = memberRect end fitMemberToText on adjustForEdges(me, spriteLoc) hLoc = spriteLoc.locH vLoc = spriteLoc.locV vAdjust = (myMember.border * 2) + myMember.margin vAdjust = vAdjust + myMember.boxDropShadow spriteHeight = myMember.height + vAdjust spriteWidth = myMember.width + vAdjust + myMember.margin maxH = (the activeWindow).rect.width - spriteWidth maxV = (the activeWindow).rect.height - spriteHeight spriteLoc.locH = max(0, min(hLoc, maxH)) spriteLoc.locV = max(0, min(vLoc, maxV)) end adjustForEdges on popUpMode(me) ----------------------------------------------------- -- Sent by exitFrame() -- -- * Determines when the user makes (or cancels) a selection after a -- #showMenu message. -- -- If this sprite is displayed using a #showMenu message then it may -- not receive a #mouseUp or #mouseUpOutide message when the mouse -- is released. We have to use a different technique to determine -- how the mouse is used. -------------------------------------------------------------------- if myDownTime then -- Check if the user has clicked on or outside the sprite if myUpTime then if the mouseUp then -- The mouse was released quickly then clicked again... if rollover(spriteNum) then -- ... over the menu to make a selction me.selectLine() end if me.hideMenu() end if else if the mouseUp then -- The mouse has just been released for the first time myUpTime = the ticks if myUpTime - myDownTime > 20 then -- The mouse was held down for more than 20 ticks... if rollover(spriteNum) then -- ... and released over the menu to make a selection me.selectLine() end if me.hideMenu() else -- The initial click was short: hold the menu open myDownTime = 0 end if end if else if myUpTime then -- but myDownTime has been reset to 0 -- The initial click was short... if the mouseDown then -- ... so this is the second mouseDown myDownTime = 1 end if end if end popUpMode on hideMenu(me) ------------------------------------------------------ -- Sent by popUpMode() when the mouse is released -- -- * Moves the sprite off-stage and resets its properties so that -- as little time as possible is wasted on exitFrame(). -------------------------------------------------------------------- myDownTime = 0 myUpTime = 0 mySprite.locZ = spriteNum mySprite.loc = point(-myMember.width - 20, 0) end hideMenu -- -- -- UTILITY HANDLERS -- on listToString(aList) aString = "" i = aList.count() repeat while i aString = RETURN & aList[i] & aString i = i - 1 end repeat delete aString.char[1] -- RETURN return aString end listToString -- BEHAVIOR DESCRIPTION AND PARAMETERS -- on isOKToAttach(me, spriteType, spriteNumber) if spriteType = #graphic then return sprite(spriteNumber).member.type = #field else return FALSE end if end isOKToAttach on getPropertyDescriptionList(me) propertiesList = [:] propertiesList[ \ #reaction] = [ \ #comment: "on", \ #format: #symbol, \ #range: [ \ #mouseUp, \ #doubleClick \ ], \ #default: #mouseUp \ ] propertiesList[ \ #handler] = [ \ #comment: "call the handler", \ #format: #symbol, \ #default: #selectFromField \ ] propertiesList[ \ #location] = [ \ #comment: "which appears in", \ #format: #string, \ #range: [ \ "a movie script", \ "a behavior on the same sprite", \ "behaviors on other sprites", \ "an instance on the actorList" \ ], \ #default: "a movie script" \ ] return propertiesList end getPropertyDescriptionList on getBehaviorTooltip(me) return \ "Use with #field members."&RETURN&RETURN&\ "Hilites the line under the mouse on rollover"&RETURN&\ "and sends a customizable call when a line"&RETURN&\ "is clicked or double-clicked."&RETURN&RETURN&\ "Used with a showMenu() call, the field"&RETURN&\ "appears as a Pop-Up or Contextual menu." end getBehaviorTooltip on getBehaviorDescription(me) return \ "CONTEXTUAL MENU (FIELD)"&RETURN&RETURN&\ "Use with field members only."&RETURN&RETURN&\ "Hilites the line of text under the mouse. (A line terminates with a hard RETURN character: a section of softwrapped text counts as one line). When the user clicks on the field, this behavior sends a customizable message to a Movie Script handler, to other behaviors or to the actorList. You can choose between a single- and a double-click. "&RETURN&RETURN&\ "Example call:"&RETURN&RETURN&\ " sendAllSprites(#lineSelected, selectedText, selectedLine)"&\ RETURN&RETURN&\ "This behavior will work with non-scrolling fields but will not scroll automatically. You can change the text of the member on the fly without taking any special precautions."&RETURN&RETURN&RETURN&\ "POP-UP MODE"&RETURN&\ "You can turn the sprite into a pop-up menu by calling the showMenu() handler. Syntax:"&RETURN&RETURN&\ " sendSprite(, #showMenu, {, menuData})"&\ RETURN&RETURN&\ " indicates the position of the topLeft corner of the sprite. The optional parameter allows you to set the items of the menu before it appears: use either a list or a RETURN-delimited string."&RETURN&RETURN&\ "If you use the #showMenu method, the sprite will disappear when the user makes a selection or clicks outside the sprite to dismiss the menu. The sprite will automatically react to a single-click, regardless of the 'reaction' setting."&RETURN&RETURN&\ "If you do not need to use the Pop-Up Mode feature, you can simply delete the section."&\ RETURN&RETURN&RETURN&\ "PARAMETERS:"&RETURN&\ " * React to a single- or to a double-click"&RETURN&\ " * Handler to call"&RETURN&\ " * Location of handler to call"&RETURN&\ " (movie script | behaviors on sprite | other sprites | actorList)" end getBehaviorDescription