Important: The information in this document is obsolete and should not be used for new development.
Spinning the Cursor
Some VBL tasks need access only to global variables that they do not share with the main program. For example, you might wish to design a VBL task that animates the beachball or watch cursor to indicate that the user must wait while the computer finishes some lengthy processing. The main application might use the application-defined proceduresStartSpinning
andStopSpinning
to install and remove the VBL task, but the application might not need to know, for example, which beachball or watch cursor the VBL task is displaying at any given time. The VBL task itself would need to know this information, because it must know which cursor to display when it is time to change the cursor.One way to implement such a VBL task is to use application global variables and set up the A5 register properly, as described in the previous section, "Accessing Application Global Variables in a VBL Task." An alternate method, however, is simply to store the information that the VBL task needs directly after the task record in memory, just as you can store information about the program's A5 value there. Then, because the VBL task has access to all of the information it needs, it does not need to set up and restore the A5 register.
The listings that follow use that strategy to implement cursor spinning. This cursor spinning task implements simple animation of any number of cursor frames stored in contiguous resources in the program's resource fork.
Listing 4-11 provides a type definition for a cursor information record. This record holds the task record and information specific to cursor spinning. Listing 4-11 also defines several constants and a global variable to hold a cursor information record.
Listing 4-11 Defining a cursor information record
CONST kInterval = 4; {frequency in interrupts} kNumberOfCursors = 4; {total number of frames} kInitialResID = 128; {ID of first cursor resource} TYPE CursorsList = ARRAY[1..kNumberOfCursors] OF CursHandle; CursorTask = RECORD myVBLTask: VBLTask; {the actual VBLTask} myCursors: CursorsList; {handles to the cursors} myFrame: Integer; {cursor frame to display next} END; CursorTaskPtr = ^CursorTask; VAR gMyCursTask: CursorTask; {global cursor info. record}Listing 4-12 shows the VBL task itself. The task changes the cursor and resets the task record'svblCount
field so that the Vertical Retrace Manager executes the task again.Listing 4-12 Changing the cursor within a VBL task
PROCEDURE ChangeCursor; TYPE BooleanPtr = ^Boolean; {to check a low-memory global} VAR recPtr: CursorTaskPtr; BEGIN recPtr := CursorTaskPtr(GetVBLRec); {get cursor information} {If the cursor is busy, we should not change it.} IF NOT BooleanPtr(CrsrBusy)^ THEN WITH recPtr^ DO {update cursor information} BEGIN SetCursor(myCursors[myFrame]^^); {display the next cursor} myFrame := myFrame + 1; {advance to next cursor frame} IF myFrame > kNumberOfCursors THEN myFrame := 1; {wrap around to first frame} END; recPtr^.myVBLTask.vblCount := kInterval; {set task to run again} END;TheChangeCursor
procedure retrieves the address of the VBL task record. If the cursor isn't already being changed, thenChangeCursor
changes the cursor to the next one in sequence and resets the index of the next cursor to display. Finally,ChangeCursor
sets itself to run again after the appropriate number of interrupts have occurred.
Listing 4-13 defines the procedure
- Note
- It is permissible to call
SetCursor
at interrupt time, provided that the cursor handle is locked and that some other routine is not currently modifying the cursor. The system global variableCrsrBusy
has the valueTRUE
if the cursor is busy; in that case, you should not callSetCursor
. Listing 4-12 illustrates the proper way to change the cursor at interrupt time. ·StartSpinning
, which you can call before beginning some lengthy operation. Because VBL tasks cannot depend on the validity of unlocked handles, theStartSpinning
procedure must lock the cursor handles in memory beforeSetCursor
is called in theChangeCursor
procedure.Listing 4-13 Installing the cursor-spinning task into a vertical retrace queue
PROCEDURE StartSpinning; CONST kInitialDelay = 120; {initial delay before starting to spin} VAR myErr: OSErr; count: Integer; BEGIN {Initialize cursor information.} FOR count := 1 TO kNumberOfCursors DO BEGIN {Load cursor into memory.} gMyCursTask.myCursors[count] := GetCursor(kInitialResID + count - 1); {Lock cursor so that we can call SetCursor at interrupt time.} HLockHi(Handle(gMyCursTask.myCursors[count])); END; gMyCursTask.myFrame := 1; {display cursor with kInitialResID first} WITH gMyCursTask.myVBLTask DO {initialize the VBL task record} BEGIN qType := ORD(vType); {set queue type} vblAddr := @ChangeCursor; {get address of VBL task} vblCount := kInitialDelay; {set task frequency} vblPhase := 0; {no phase} END; myErr := VInstall(@gMyCursTask.myVBLTask); END;Notice that the initial delay (specified by thekInitialDelay
constant in thevblCount
field) is much larger than the number of interrupts between subsequent cursor changes (specified by thekInterval
constant). This prevents the cursor from starting to spin until a reasonable time (about 2 seconds) has elapsed.Listing 4-14 shows how to remove the cursor-spinning task from the vertical retrace queue.
Listing 4-14 Removing the cursor-spinning task from its vertical retrace queue
PROCEDURE StopSpinning; VAR myErr: OSErr; count: Integer; BEGIN {Remove the task record from its queue.} myErr := VRemove(@gMyCursTask.myVBLTask); {Free memory occupied by the cursors.} FOR count := 1 TO kNumberOfCursors DO ReleaseResource(Handle(gMyCursTask.myCursors[count])); InitCursor; {restore the arrow cursor} END;Depending on the needs of your application, you might want to load the cursors into memory at application-launch time and release them when your application quits. If so, you need to modify theStartSpinning
andStopSpinning
procedures accordingly.