Articles Archive
Articles Search
Director Wiki
 

Efficient button rollovers

January 1, 1998
by Alex Zavatone

Over my last project, I've had to create an entire user interface prototype with draggable groups of sprites and pseudo windows, toolbars with multiple rollover states and a windowing system complete with graphics, UI elements and z ordering. While last week's article covered the draggable groups part of this project, I had so much fun remembering how to do all this in Director 5, sans behaviours mind you, I felt compelled to share the procedures with you crazy kids (and your little dog too).

While creating the multiple rollover states in Dir 5, I was once again faced with the eternal argument, "do I make every object/button/thing self aware or do I use one state manager for all the buttons and their states." Since I was in a behaviourless environment, the self aware objects/buttons/things approach would require a lot of thought and for some reason I felt that it would be rather ineffecient. Six self aware buttons would require servicing six button objects rather than servicing just one manager which determined which of the six buttons to examine. Got that? I went with a button manager. So we're in Director 5 and I've got to write some code to do the following:

  1. Determine button states from a set list of "N" buttons
  2. Determine which button to monitor
  3. Determine if that button has changed state. If it has, then and only then, set a flag which will update the state of that button
  4. Repeat with the rest of the buttons if necessary
  5. If an updateflag was set, update the buttons

Now after dealing with netlingo operations, I'm a big proponent of NOT using repeat loops. They hog the processor and exclude events like netDone and keyDown. That is bad when you expect to use those events or most any event for that matter. They are fast because of that. That is good. My first approach was totally state based and contained no repeat loops. The code which is listed below iterated through the list of supplied buttons on enterframe and its performance was not acceptable. Cranking the tempo could have helped out here, but the flag went off in my head that said "If you want to get this project done in a timely manner, try a more standard method."

code:
Parent Script: Button mgr

property pButtonList 
-- list of button sprites
property pCurrentButton 
-- current button whose state we are checking
property pButtonRolloverState 
-- list of rollover states
property pButtonDepressedState 
-- list of depressed states (not implimented)
property pButtonNames 
-- Uhhh the button names
property pRolloverSuffix 
-- name of the rollover member suffix
property pLastRollover 
-- last sprite rolled over
on new me
   init me
   return me
end
on Init me
   set pCurrentButton = 1
   set pButtonList = [33,34,35,36,37,38,39]
   set pButtonRolloverState = [0,0,0,0,0,0,0]
   set pButtonDepressedState = [0,0,0,0,0,0,0]
   set pButtonNames = ["1", "2", "3", "4", "5", "6", "7"]
   set pRolloverSuffix = "over"
end
on EnterFrame me
   
   if rollover (getAt( pButtonList, pCurrentButton)) then
      setAt(pButtonRolloverState, pCurrentButton,1)
      set rolloverFlag = 1
   else
      setAt(pButtonRolloverState, pCurrentButton,0)
   end if
   if rolloverFlag then
      set mySprite = getAt( pButtonList, pCurrentButton)
      UpdateDisplay me, mySprite
      set pLastRollover = mySprite
   else
      -- UpdateDisplay me
   end if
   if pCurrentButton < count(pButtonList) then
      set pCurrentButton = pCurrentButton + 1
   else
      set pCurrentButton = 1
   end if
end
on UpdateDisplay me, mySprite
   set the member of sprite mySprite = the number of member ¬
    (getAt( pButtonNames, pCurrentButton) && pRolloverSuffix)
   if not pLastRollover or pLastRollover = ¬
     mySprite then return set the member of sprite ¬
     pLastRollover = the number of member ¬
     (getAt( pButtonNames,getPos(pButtonList, pLastRollover)))
end

As you drag your mouse over the folders in the above movie, notice that the speed of updating rollovers is not optimal. The display below the little sample windoid is my object inspector which reports on the status of the button rollover object every .25 seconds. Rest assured that the updating speed was slow even before I added the object property display code.

So with state-based rollover detection not fast enough, I decided to switch to a rollover manager that would check the rollover state of all buttons within the button set. In the interests of effeciency, the following approach was taken in the designing of the code:

When checking for rollover of a sprite, store the rollover state in a variable. The next time you check the sprite for rollover if the new rollover state is different from the previous state variable, only then should you set the variable and a flag indicating to update the display.

That resulted in an easy code change and also an easy addition of checking for the depressed state of a button. Here, values of 0 indicate no rollover, 1 indicates rollover and 2 indicates depressed. Since rollover and depressed states can happen on the same button at the same time, the if statement was modified to prevent rollover from kicking in if the button sprite was already depresssed. See, this code and the above code works by storing the states of all the buttons and then after checking all the buttons, run the display routine if any of the button's states have changed.

code:
Parent Script: Button mgr


property pButtonList
property pButtonState
property pButtonNames
property pRolloverSuffix
property pDepressedSuffix 
        
on new me
   Init me
   return me
end
        
on Init me
   set pButtonList = [42,43,44,45,46,47]
   set pButtonState = [ 0, 0, 0, 0, 0, 0]
   set pButtonNames = ["Active", "Tools", "Help", ¬
      "Prefs", "Comm", "Web"]
   set pRolloverSuffix = "Over"
   set pDepressedSuffix = "Down"
end
on Service me
   repeat with mySprite in pButtonList
      set mySpriteState = getAt(pButtonState, ¬
        getPos(pButtonList, mySprite))
      if rollover ( mySprite ) then
         if not the mouseDown then 
         -- if rolledover and the mouse Up
            if mySpriteState = 0 then 
            -- here we want to preserve the mousedownstate
               set updateFlag = 1
               setAt(pButtonState, getPos(pButtonList,¬
                mySprite),1)
            end if
         else
            if mySpriteState <> 2 then 
            -- if rolledover and the mouse Down
               set updateFlag = 1
               setAt(pButtonState, getPos(pButtonList, ¬
                 mySprite),2)
            end if
         end if
      else
         if mySpriteState <> 0 then 
         -- if not rolled over
            set updateFlag = 1
            setAt(pButtonState, getPos(pButtonList, ¬
              mySprite),0)
         end if
      end if
end repeat
        
if updateFlag then UpdateDisplay me
end
on UpdateDisplay me
   repeat with mySprite in pButtonList
      set myButtonState = getAt(pButtonState, ¬
         getPos(pButtonList, mySprite))
      set myButtonName = getAt( pButtonNames, ¬
         getPos(pButtonList, mySprite)) 
       
      case myButtonState of
         0 : set the member of sprite mySprite = ¬
            the number of member myButtonName
         1 : set the member of sprite mySprite = ¬
            the number of member (myButtonName ¬
            && pRolloverSuffix)
         2 : set the member of sprite mySprite = ¬
            the number of member (myButtonName && ¬
              pDepressedSuffix)
      end case
   end repeat
updatestage
end

In the sample below, try rolling over the sprites and clicking on them as well.

This script forces all buttons in the group to be checked for rollover. Though in Director 6 this would be easier, if 5 or 4's all ya got, this'll work like a charm. Here the changes in the code are evident first in that enterframe has been changed to Service. EnterFrame is supposed to be called from objects in the actorlist but for some reason this was not working for me. The rollover code is called from this exitframe script:


on exitFrame
   Service (getAt( the actorList, 3)) 
   -- bug enterFrame should be automatically executed
   go the frame
end

The actorlist is used here because while monitoring objects with my object browser, the object browser parses any list of objects and displays all the properties of all objects within the list. Because of this, I normally throw an additional reference to the object into the actorlist for monitoring purposes. If you have several groups of rollovers to check, and they are assigned to happy little globals like gButtonObj1, gButtonObj2, gButtonObj3 then the exitFrame script would look like this:

on exitFrame
   Service (gButtonObj1)
   Service (gButtonObj2)
   Service (gButtonObj3)
   go the frame
end 

Back to the structure of the code, the repeat loop loops through all the buttons within the list pButtonList and checks the button's state. If the current condition is different than the current state then the proper state for the button is set and the updateflag is set. This way, if you keep rolling over the same sprite, the code doesn't waste time and continue updating the display for no reason. The UpdateDisplay routine then draws the entire button list with the proper graphics with another repeat loop. Here the pRolloverSuffix and pDepressedSuffix variables are used to find the proper cast member graphic to display.

Even though this is more efficient, it would seem that UpdateDisplay should be called with the number of the sprite to be updated and then when done, with the repeat loop, updateStage should be called. Since my code was "good and fast enough" for the contract, I didn't bother to further optimize it but in this example it makes perfect sense. Actually, it's about 30 seconds of hard work and it is much faster. Simply remove the repeat loop from the UpdateDisplay handler, add mySprite as a parameter to UpdateDisplay, call UpDateDisplay after you set the button's state and perform an updateStage if updateFlag is non zero. Piece o cake. In case you're wondering, it looks like this:

Code identical to the previous example. Only modified handlers are displayed:


on Service me
   repeat with mySprite in pButtonList
      set mySpriteState = getAt(pButtonState, ¬
       getPos(pButtonList, mySprite))
      if rollover ( mySprite ) then
         if not the mouseDown then 
         -- if rolledover and the mouse Up
            if mySpriteState = 0 then 
            -- here we want to preserve the mousedownstate
               set updateFlag = 1
               setAt(pButtonState, getPos(pButtonList, ¬
               mySprite),1)
               UpdateDisplay me, mySprite
            end if
         else
            if mySpriteState <> 2 then 
            -- if rolledover and the mouse Down
               set updateFlag = 1
               setAt(pButtonState, getPos(pButtonList, ¬
               mySprite),2)
               UpdateDisplay me, mySprite
            end if
         end if
      else
         if mySpriteState <> 0 then -- if not rolled over
            set updateFlag = 1
            setAt(pButtonState, getPos(pButtonList, ¬
             mySprite),0)
            UpdateDisplay me, mySprite
         end if
      end if
   end repeat
   if updateFlag then UpdateStage
   
end
on UpdateDisplay me, mySprite
   set myButtonState = getAt(pButtonState, 
   getPos(pButtonList, mySprite))
   set myButtonName = getAt( pButtonNames, getPos¬
     (pButtonList, mySprite)) 
       
   case myButtonState of
      0 : set the member of sprite mySprite = ¬
         the number of member myButtonName
      1 : set the member of sprite mySprite = ¬
        the number of member (myButtonName 
         && pRolloverSuffix)
      2 : set the member of sprite mySprite = ¬
      the number of member (myButtonName && pDepressedSuffix)
   end case
end

Here's another Shockwave for ya with the final code. Check out the speed in rollovers and faster click response here:

And finally here's a downloadable simple sample for you to play with. Dir 5 file (HQX or ZIP). All I ask is if you use this code, please give me and DOUG credit.

A Director user since 1987, Alex (Zav) Zavatone has worked on the engineering teams for both Director and Shockwave, 4, 5, 6 and 7. Recent investigations find him developing foundation classes for Director with asynchronous process management and other life threatening activities. In its early days, he had something to do with this Internet thing known as "DOUG". A noted ne'erdowell, slacker and laggard, Mr. Zavatone is nonetheless is trusted by children, small animals and the elderly. In his spare time, Mr. Zavatone rehabilitates lame desert trout.

Copyright 1997-2017, Director Online. Article content copyright by respective authors.