home *** CD-ROM | disk | FTP | other *** search
- /*
- * ScreenFlip 1.0 — a screensaver module by Leo Breebaart, Kronto Software 1994.
- *
- * For non-technical information about this module and for the credits,
- * see the README file.
- *
- * This module keeps flipping your screen’s contents horiontally and vertically.
- * The flips can be instantaneous or ‘animated’ column/row-wise.
- *
- * I have commented the code in a way that experienced programmers may find
- * overkill, but it was done in the hope that beginning After Dark programmers
- * will find ScreenFlip a useful starting point for writing their own
- * modules. If you want to look at some more documented code, check out The Swarm,
- * another freeware screensaver module I wrote.
- *
- * For general information about how to write an After Dark-compatible module,
- * see the After Dark Programmer’s Manual, but make sure you have the most recent
- * version (released with After Dark 2.0u and later). The comments in this
- * module do assume you are at least *aware* of the basic After Dark mechanisms.
- *
- * Here we go...
- */
-
- #include <QDoffscreen.h>
- #include "AfterDarkTypes.h"
-
- // Flipping the screen is implemented as a kind of rudimentary state machine.
- // At any time the module is in one of these four FlipStates. HORIZONTAL and
- // VERTICAL mean we're in the processing of doing a horizontal resp. vertical
- // screen flip. SWITCH means we've just completed one entire flip, and will now
- // determine what the next flip will be. NONE means we are in a state of rest
- // between flips.
- typedef enum { HORIZONTAL, VERTICAL, SWITCH, NONE } FlipStates;
-
- // This is a secondary state, which controls the direction the ripples of
- // the current animation are moving in (for the VERTICAL and HORIZONTAL flip states.
- typedef enum { INWARDS, OUTWARDS } DirectionStates;
-
-
- // QuickDraw color constants used throughout the program.
- RGBColor whiteRGB = { 0xFFFF, 0xFFFF, 0xFFFF };
- RGBColor blackRGB = { 0, 0, 0 };
-
-
- // In the following data structure most two-element arrays store information
- // pertaining horizontal flips in the first element, and the same information
- // for the vertical case in the second element. This makes it possible to
- // write things like 'bufworld[VERTICAL]' or 'maxStep[HORIZONTAL]', which is
- // not only very clear, but also allows us to eliminate a lot of code duplication.
-
- typedef struct FlipStorage // All data we use is stored in this struct.
- {
- GWorldPtr bufWorld[2]; // Small buffers for storing a row resp. column of pixels.
- GWorldPtr offWorld; // Large buffer for storing a copy of the entire screen.
-
- PixMapHandle screenMap; // Bitmap for an entire screen.
- PixMapHandle bufMap[2]; // Bitmaps associated with the bufWorld array.
-
- Rect strip[2]; // Which two columns/rows to swap this step.
- Rect bufRect[2]; // Bounding rectangles associated with bufWorld.
- Rect r; // Bounding rectangle of the screen.
-
- FlipStates movement; // Current flip state.
- DirectionStates direction; // Current ripple movement.
- int t; // The current step (each steps, two columns/rows get switched).
-
- long startTick, delay; // Variables for keeping track of delays between flips.
- int maxStep[2]; // After this many steps before flip is finished.
-
- Boolean demoMode; // Are we currently in After Dark demo mode?
- Boolean instantFlip; // Corresponds to check box in module interface.
- }
- FlipStorage, *FlipStoragePtr;
-
-
- // After Dark's way of showing an error to the user.
- // '##' is an ANSI-ism meaning meta-concatenation.
- #define ErrorMsg(m) BlockMove(CtoPstr(m), params->errorMessage, 1 + m##[0]);
-
- // Let's be good ANSI citizens, and define some function prototypes.
- // These functions will be called from the AfterDarkShell.c code.
- OSErr DoInitialize(Handle *storage, RgnHandle blankRgn, GMParamBlockPtr params);
- OSErr DoClose(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
- OSErr DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
- OSErr DoDrawFrame(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params);
- OSErr DoHelp(RgnHandle blankRgn, GMParamBlockPtr params);
-
- // Local functions.
- OSErr AboutBoxError(Rect graf);
- int RangedRdm(int min, int max);
- Handle BestNewHandle(Size s);
- void SetRects(FlipStoragePtr fs, int movement);
- void SwapStrips(FlipStoragePtr fs, int movement);
- OSErr InitStructures(FlipStoragePtr fs, int movement);
-
-
- GWorldPtr currPort; // The 'real' screen consists of two components
- GDHandle currDev; // which we'll save in these global variables.
- PixMapHandle realMap; // And this is the associated BitMap.
-
-
- // DoInitialize allocates the Flip data structure as defined
- // above, initializes the variables in that structure, and checks
- // for possible problems.
-
- OSErr
- DoInitialize(Handle *storage, RgnHandle blankRgn, GMParamBlockPtr params)
- {
- FlipStoragePtr fs; // The flip structure, obviously.
- RgnHandle tmpRgn = NewRgn();
-
- // Our offscreen graphics worlds need Color QuickDraw.
- if (!params->colorQDAvail)
- {
- DisposHandle(*storage);
- ErrorMsg("ScreenFlip: Sorry, I need Color QuickDraw to run!");
- return ModuleError;
- }
-
- // Allocate 'master' handle to the storage struct.
- if ((*storage = BestNewHandle(sizeof(FlipStorage))) == NULL)
- {
- ErrorMsg("ScreenFlip: Couldn't allocate enough memory!");
- return ModuleError;
- }
-
- // Lock down the storage so we can refer to it by pointer.
- HLockHi(*storage);
- fs = (FlipStoragePtr) **storage;
-
- // Get the delay-between-flips value from the slider in the module interface
- // and the instantFlip boolean from the checkbox.
- fs->delay = params->controlValues[0];
- fs->instantFlip = params->controlValues[1];
-
- // Sanity check. This should never happen.
- if (fs->delay < 0 || fs->delay > 100)
- {
- ErrorMsg("ScreenFlip: Internal Error — insane delay value!");
- return ModuleError;
- }
-
- // Initialize the random number generator.
- params->qdGlobalsCopy->qdRandSeed = TickCount();
- fs->demoMode = !EmptyRect((¶ms->demoRect));
-
- // Initialize the globals variables that describe the
- // real, 'physical' screen.
- GetGWorld(&currPort,&currDev);
- realMap = GetGWorldPixMap((GWorldPtr) currPort);
-
- // The CopyBits function expect these back- and foreground values.
- RGBBackColor(&whiteRGB);
- RGBForeColor(&blackRGB);
-
- // Find the screen's bounding rectangle, as well as the coordinates
- // of a single column and row.
- fs->r = params->monitors->monitorList[0].bounds;
- SetRect(&(fs->bufRect[VERTICAL]), 0, 0, 1, fs->r.bottom);
- SetRect(&(fs->bufRect[HORIZONTAL]), 0, 0, fs->r.right, 1);
-
- // Initialize data structures for row/column buffers.
- (void) InitStructures(fs, VERTICAL);
- (void) InitStructures(fs, HORIZONTAL);
-
- fs->maxStep[VERTICAL] = fs->r.right / 2;
- fs->maxStep[HORIZONTAL] = fs->r.bottom / 2;
-
- // We start our flipping in a random direction, moving either
- // inwards or outwards, at time (or step) t = 0.
- fs->movement = RangedRdm(0,1);
- fs->direction = RangedRdm(0,1);
- fs->t = 0;
-
- // If instantFlip is true, we do not show the animation 'ripples' on the
- // screen (caused by successive swaps of pairs of rows/columns), but instead
- // perform the swapping in an offscreen buffer copy of the entire screen,
- // which we blit onto the 'real' screen when the flip is completed.
- if (fs->instantFlip)
- {
- if (NewGWorld(&(fs->offWorld), 0, &(fs->r), nil, nil, noNewDevice+useTempMem) != noErr)
- {
- DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
- ErrorMsg("ScreenFlip: Not enough memory for offscreen graphics world!");
- return ModuleError;
- }
- fs->screenMap = GetGWorldPixMap((GWorldPtr) fs->offWorld);
-
- // Copy the real screen contents to the offscreen pixmap.
- CopyBits((BitMap *) (*realMap), (BitMap *) (*fs->screenMap),
- &fs->r, &fs->r,
- srcCopy, nil);
- }
- else
- // If we do want ripple animation, the algorithm stays exactly the
- // same, only we make screenMap refer to the physical screen, so that
- // any changes to it will be reflected on the monitor immediately,
- // thus causing the ripples to appear.
- fs->screenMap = realMap;
-
- return noErr;
- }
-
-
- // Initialize small offscreen buffers for storing a column or row.
-
- OSErr
- InitStructures (FlipStoragePtr fs, int movement)
- {
- if (NewGWorld(&fs->bufWorld[movement], 0, &fs->bufRect[movement], nil, nil, noNewDevice+useTempMem) != noErr)
- return ModuleError;
-
- fs->bufMap[movement] = GetGWorldPixMap(fs->bufWorld[movement]);
-
- return noErr;
- }
-
-
- // The DoBlank function blanks out all available screens, *except* for the
- // main screen. If we are in 'instantFlip' mode, care should be taken to
- // switch to the 'real' screen world (and back to offscreen afterwards) before blanking.
-
- OSErr
- DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
- {
- RgnHandle otherScreens, mainScreenRgn;
- Rect r = params->monitors->monitorList[0].bounds;
-
- FlipStoragePtr fs = (FlipStoragePtr) *storage;
-
- // If just one monitor, then we don't need to bother at all...
- if (params->monitors->monitorCount != 1)
- {
- otherScreens = NewRgn();
- mainScreenRgn = NewRgn();
-
- RGBBackColor(&blackRGB);
-
- SetRectRgn(mainScreenRgn, r.left, r.top, r.right, r.bottom);
- DiffRgn(blankRgn, mainScreenRgn, otherScreens);
- EraseRgn(otherScreens);
-
- // In order for CopyBits calls to work, the destination
- // world's backgroundworld *has* to be white (and the foreground
- // black.
- RGBBackColor(&whiteRGB);
-
- DisposeRgn(mainScreenRgn);
- DisposeRgn(otherScreens);
- }
-
- return noErr;
- }
-
-
- // At any given step 't' of the animation process, this function
- // calculates which two rows or columns are to be swapped, taking
- // into account that the animation can move inwards or outwards.
-
- void
- SetRects(FlipStoragePtr fs, int movement)
- {
- int step;
-
- if (fs->direction == INWARDS)
- step = fs->t;
- else /* OUTWARDS */
- step = fs->maxStep[movement] - fs->t - 1;
-
- if (movement == HORIZONTAL)
- {
- SetRect(&fs->strip[0], 0, step, fs->r.right, step+1);
- SetRect(&fs->strip[1], 0, fs->r.bottom-step-1, fs->r.right, fs->r.bottom-step);
- }
- else /* VERTICAL */
- {
- SetRect(&fs->strip[0], step, 0, step+1, fs->r.bottom);
- SetRect(&fs->strip[1], fs->r.right-step-1, 0, fs->r.right-step, fs->r.bottom);
- }
- }
-
-
- // The true bottleneck of this module. Three CopyBits calls are necessary to
- // swap the two columns or rows calculated in SetRects. If you look closely
- // at the parameters, you'll notice that these calls are just a bitmap-moving
- // way of doing the well-known "temp = a; a = b; b = temp;" variable swap.
-
- void
- SwapStrips(FlipStoragePtr fs, int movement)
- {
- CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*fs->bufMap[movement]),
- &fs->strip[0], &fs->bufRect[movement],
- srcCopy, nil);
- CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*fs->screenMap),
- &fs->strip[1], &fs->strip[0],
- srcCopy, nil);
- CopyBits((BitMap *) (*fs->bufMap[movement]), (BitMap *) (*fs->screenMap),
- &fs->bufRect[movement], &fs->strip[1],
- srcCopy, nil);
- }
-
- // How many ticks have passed since we started counting?
- #define TicksPassed (TickCount() - fs->startTick)
-
- // The main animation routine. Each call to DoDrawFrame causes (when in
- // an animation state) two rows or columns to exchange places.
- OSErr
- DoDrawFrame(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
- {
- FlipStoragePtr fs = (FlipStoragePtr) *storage;
-
- // If we are in After Dark demo mode, the user may have changed
- // the slider value, so we must re-read it.
- if (fs->demoMode)
- {
- if (fs->delay != params->controlValues[0])
- fs->delay = params->controlValues[0];
- }
-
- switch (fs->movement)
- {
-
- case HORIZONTAL:
- case VERTICAL:
-
- if (fs->instantFlip)
- {
- // If we are instant-flipping there is no need to swap just
- // one pair of columns/rows for each call of this function.
- // Instead, we can avoid many seconds of delay by flipping
- // the entire screen in one go. This of course at the price
- // of a slightly less responsive attitude towards module
- // interruptions -- we now spend much more time in DoDrawFrame.
-
- // Delay period includes offscreen drawing time.
- fs->startTick = TickCount();
-
- while (fs->t < fs->maxStep[fs->movement])
- {
- SetRects(fs, fs->movement);
- SwapStrips(fs, fs->movement);
- fs->t++;
- }
-
- // Blit the completed flip to the real screen.
- CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*realMap),
- &fs->r, &fs->r,
- srcCopy, nil);
-
- // Start of (possible) delay period.
- fs->movement = NONE;
- }
- else
- {
- SetRects(fs, fs->movement);
- SwapStrips(fs, fs->movement);
-
- // Increment time variable and test for stop criteria.
- if (++fs->t >= fs->maxStep[fs->movement])
- {
- // Start (possible) delay period.
- // Delay period does *not* include on-screen drawing time.
- fs->movement = NONE;
- fs->startTick = TickCount();
- }
- }
- break;
-
- case NONE:
-
- // Do nothing until user-specified delay has passed.
- if (TicksPassed < 10 * (fs->delay / 25) * 60)
- return noErr;
- else
- fs->movement = SWITCH;
- break;
-
- case SWITCH:
- // Change to a random new flip type.
- fs->movement = RangedRdm(0,1);
-
- // Animation direction always goes in-out-in-out-in-out...
- if (fs->direction == INWARDS)
- fs->direction = OUTWARDS;
- else
- fs->direction = INWARDS;
-
- // Re-initialize time variable.
- fs->t = 0;
- break;
- }
- return noErr;
- }
-
-
- // ScreenFlip features a funky About Box, which has a miniature logo flip
- // going on inside of it. Creating that animation is mostly straightforward: I
- // just call all the previous functions, i.e. DoInitialize,
- // DoBlank, and DoDrawFrame -- and that's it. The tricky part lies in getting
- // some correct variables in place, and setting up the right graphics port.
- //
- // One thing you should realize about DoHelp: when After Dark calls this
- // function, it will already have set the currPort and the blankRgn to the help rectangle.
- // So you are at this point no longer drawing to the entire screen.
- //
- // Final note: if you want to use a DoHelp function yourself, realize that (a)
- // you need to tell After Dark you want to 'take over' (see the Cals resource in
- // the Programmer's Manual), and (b) 'storage' is *not* initialized in DoHelp,
- // because After Dark calls DoHelp without calling DoInitialize first, in contrast
- // to e.g. DoDrawFrame. Unfortunately, a lot of code shows example 'DoAbout' or
- // 'DoHelp' functions with the 'storage' handle as a parameter. But this parameter
- // will *not* be intialized, and if you use it, you will crash horribly.
-
- OSErr
- DoHelp(RgnHandle blankRgn, GMParamBlockPtr params)
- {
- FlipStorage **miniFlip;
-
- long dummy;
- GrafPtr helpGraf;
- short oldInstantFlip;
- short oldCount;
- Rect oldBounds;
- RgnHandle miniBlankRgn = NewRgn();
-
- PicHandle helpPict;
- Rect picRect, helpRect, miniRect;
-
- // Get the real screen.
- GetPort(&helpGraf);
-
- // Get the real screen rectangle. miniRect is the version
- // we’ll use in the rest of this function, helpRect is the
- // ‘original’ version we’ll need to pass to the error handling
- // routine. That last bit is somewhat of an ugly hack, and may be
- // cleaned up in the next version.
- helpRect = miniRect = helpGraf->portRect;
- LocalToGlobal(&topLeft(helpRect));
- LocalToGlobal(&botRight(helpRect));
-
- // Load the 'about' PICT resource.
- if ((helpPict = GetPicture(2000)) == nil)
- {
- ErrorMsg("ScreenFlip: Couldn’t load PICT resource for help picture!");
- return AboutBoxError(helpRect);
- }
-
- // Draw the PICT, and release the resource.
- picRect = (**helpPict).picFrame;
- DrawPicture(helpPict, &picRect);
- ReleaseResource((Handle) helpPict);
-
- // We now manually change the graphics environment
- // from the entire help area to the subarea where we want the
- // mini animation to take place (look at the about box in action if this
- // is not clear to you). The current Port is moved and made smaller.
- // The Toolbox calls take care of all the nasty details.
- LocalToGlobal(&topLeft(miniRect));
- LocalToGlobal(&botRight(miniRect));
- MovePortTo(miniRect.left+24, miniRect.top+36);
- PortSize(174, 89);
-
- // Save some relevant parameters that the 'real' After Dark
- // animation will need to have restored later on.
- oldCount = params->monitors->monitorCount;
- oldBounds = params->monitors->monitorList[0].bounds;
- oldInstantFlip = params->controlValues[1];
-
- // Change the parameters temporarily to values appropriate for
- // the miniFlip.
- params->monitors->monitorCount = 1;
- params->monitors->monitorList[0].bounds = helpGraf->portRect;
-
- // We want ripple effects regardless of current checkbox setting.
- params->controlValues[1] = 0;
-
- RectRgn(miniBlankRgn, &helpGraf->portRect);
-
- // Initialize the miniFlip struct. Note that we have a problem
- // with error management here: I can use ErrorMsg all I want,
- // but After Dark will do nothing with the return value of
- // the DoHelp function, for some reason. So I have implemented
- // a separate AboutBoxError() function of my own to handle
- // errors.
- if (DoInitialize((Handle *) &miniFlip, miniBlankRgn, params) != noErr)
- {
- ErrorMsg("ScreenFlip: Initialization of miniFlip failed!");
- return AboutBoxError(helpRect);
- }
-
- // Notice that DoBlank() does not need to be called for this miniFlip.
-
- // Wait for the user to release the mouse button, if necessary.
- while (Button())
- ;
-
- // Animate, until the user presses the mouse button.
- while (!Button())
- {
- if (DoDrawFrame((Handle) miniFlip, miniBlankRgn, params) != noErr)
- {
- ErrorMsg("ScreenFlip: DoDrawFrame of miniFlip failed!");
- return AboutBoxError(helpRect);
- }
-
- // MiniFlip animation is too fast!
- Delay(1, &dummy);
- }
-
- // Close it all up.
- DoClose((Handle) miniFlip, miniBlankRgn, params);
-
- // Restore parameters.
- params->monitors->monitorCount = oldCount;
- params->monitors->monitorList[0].bounds = oldBounds;
- params->controlValues[1] = oldInstantFlip;
-
- DisposeRgn(miniBlankRgn);
- DisposeHandle((Handle) miniFlip);
-
- FlushEvents(everyEvent, 0);
-
- return noErr;
- }
-
-
- // The DoClose function merely disposes of all those handles and
- // offscreen worlds. Nothing interesting here.
-
- OSErr
- DoClose(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
- {
- FlipStorage **fs = (FlipStorage **) storage;
-
- if (fs)
- {
- DisposeGWorld((**fs).bufWorld[0]);
- DisposeGWorld((**fs).bufWorld[1]);
- if ((**fs).instantFlip)
- DisposeGWorld((**fs).offWorld);
-
- DisposeHandle(storage);
- }
-
- return noErr;
- }
-
-
- // Random functions yield a number between min and max. The function
- // originated from Think Reference, but this version is an adaptation
- // by Joseph "Peek-a-Boo" Judge.
-
- int RangedRdm(int min, int max)
- {
- unsigned qdRdm;
- long range, t;
-
- qdRdm = Random();
- range = (max - min) + 1;
- t = ((long)qdRdm * range) / 65536; // now 0 <= t <= range
- return( t+min );
- }
-
-
- // This function tries to allocate a handle from temporary memory
- // first, and only if that fails from the reserved memory.
- // This function is taken from the DarkSide of the Mac example code.
- // Note that the return value can be nil and should be checked.
-
- Handle
- BestNewHandle(Size s)
- {
- Handle theHandle;
- OSErr anErr;
-
- if ((theHandle = TempNewHandle(s, &anErr)) == nil)
- theHandle = NewHandle(s);
-
- return(theHandle);
- }
-
-
- // Give some user feedback if anything goes wrong during the
- // About Box animation.
-
- OSErr
- AboutBoxError(Rect r)
- {
- // These tedious calls simply have the total effect of moving
- // the drawing area back to exactly the part of the screen
- // corresponding with the original About Box contents.
-
- Rect currRect = thePort->portRect;
-
- MovePortTo(r.left, r.top);
- PortSize(r.right-r.left, r.bottom-r.top);
-
- ForeColor(blackColor);
- BackColor(whiteColor);
-
- GlobalToLocal(&topLeft(r));
- GlobalToLocal(&botRight(r));
-
- FillRect(&r, white);
-
- TextFont(geneva);
- TextSize(9);
-
- MoveTo(15,20);
- DrawString("\pI'm sorry — an error has occurred.");
- MoveTo(15,30);
- DrawString("\pNothing serious, mind you. The module");
- MoveTo(15,40);
- DrawString("\pprobably just ran out of memory.");
- MoveTo(15,60);
- DrawString("\pYou see, I have not implemented decent");
- MoveTo(15,70);
- DrawString("\perror management routines for this");
- MoveTo(15,80);
- DrawString("\pAbout Box yet. Next version, I promise.");
- MoveTo(15,100);
- DrawString("\pIf you *do* have lots of memory, but you");
- MoveTo(15,110);
- DrawString("\pstill see this message, then something");
- MoveTo(15,120);
- DrawString("\p*is* probably very wrong, and I would");
- MoveTo(15,130);
- DrawString("\preally appreciate an e-mail bug report.");
- MoveTo(15,150);
- DrawString("\pThanks!");
- SysBeep(1); SysBeep(1);
-
- MovePortTo(currRect.left, currRect.top);
- PortSize(currRect.right-currRect.left, currRect.bottom-currRect.top);
-
- while (Button())
- ;
- while (!Button())
- ;
-
- FlushEvents(everyEvent, 0);
- return ModuleError;
- }
-
- // This is THE END.
- // If you've learned anything from this code, or found errors in it, or
- // have questions about it, or whatever: feel free to drop me a note.
- // My e-mail address is: leo@cp.tn.tudelft.nl
-