home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Add-Ons / After Dark / ScreenFlip 1.5 / Source / ScreenFlip.c < prev    next >
Encoding:
Text File  |  1995-12-05  |  17.4 KB  |  555 lines  |  [TEXT/R*ch]

  1. /*
  2.  *    ScreenFlip 1.5 — a screensaver module by Leo Breebaart, Kronto Software 1994-95.
  3.  *
  4.  *      For non-technical information about this module and for the credits,
  5.  *       see the README file.
  6.  *       
  7.  *       This module keeps flipping your screen’s contents horiontally and vertically. 
  8.  *       The flips can be instantaneous or ‘animated’, i.e. with a column- or row-wise
  9.  *      ripple effect.
  10.  *       
  11.  *       I have commented the code in a way that experienced programmers may find
  12.  *       overkill, but it was done in the hope that beginning After Dark programmers
  13.  *       will find ScreenFlip a useful starting point for writing their own 
  14.  *       modules. If you want to look at some more documented code, check out The Swarm, 
  15.  *      another freeware screensaver module I wrote.
  16.  *       
  17.  *       For general information about how to write an After Dark-compatible module, 
  18.  *       see the After Dark Programmer’s Manual, but make sure you have the most recent
  19.  *       version (released when After Dark 3.0 was released). The comments in this
  20.  *       module do assume you are at least *aware* of the basic After Dark mechanisms.
  21.  *
  22.  *      Here we go...
  23.  */
  24.  
  25.      // GWorlds.
  26. #include <QDoffscreen.h>
  27.  
  28.     // Include file for After Dark type definitions.
  29. #include "GraphicsModule_Types.h"
  30.  
  31.     // Prototypes for After Dark functions.
  32. #include "ModuleFunctions.h"
  33.  
  34.     // Utility functions.
  35. #include "HandyStuff.h"
  36.  
  37.     // Data structures.
  38. #include "ScreenFlip.h"
  39.  
  40.  
  41.     // After Dark's way of showing an error to the user.
  42.     // '##' is an ANSI-ism meaning meta-concatenation.
  43. #define ErrorMsg(m) BlockMove(CtoPstr(m), params->errorMessage, 1 + m##[0]);
  44.  
  45.     // QuickDraw color constants used throughout the program.
  46. RGBColor gWhite = { 0xFFFF, 0xFFFF, 0xFFFF };
  47. RGBColor gBlack = { 0, 0, 0 };
  48. RGBColor gGold  = { 0xFFFF, 0x9999, 0x0000 };
  49.  
  50.     // This array is for mapping delay values received from the AD control panel
  51.     // to values in ticks (60 ticks == 1 second).
  52. static short gDelayMap[6] = { 0, 1*60, 5*60, 10*60, 30*60, 60*60 };
  53.  
  54.     // Prototypes for local functions.
  55. static void SetRects (TFlipDataPtr fs, int movement);
  56. static void    SwapStrips (TFlipDataPtr fs, int movement);
  57. static OSErr InitStructures (TFlipDataPtr fs, int movement);
  58.  
  59.  
  60.     // DoInitialize allocates the Flip data structure as defined
  61.     // above, initializes the variables in that structure, and checks
  62.     // for possible problems.
  63.  
  64. OSErr
  65. DoInitialize(Handle *storage, GMParamBlockPtr params)
  66. {
  67.     TFlipDataPtr fs;        // The flip structure, obviously.
  68.     RgnHandle tmpRgn = NewRgn();
  69.     GrafPtr screenPort;
  70.  
  71.          // Our offscreen graphics worlds need Color QuickDraw.
  72.     if (!params->colorQDAvail)
  73.     {
  74.         DisposHandle(*storage);
  75.         ErrorMsg("ScreenFlip:  Sorry, I need Color QuickDraw to run!");
  76.         return ModuleError;
  77.     }
  78.  
  79.         // Allocate 'master' handle to the storage struct.
  80.     if ((*storage = BestNewHandle(sizeof(TFlipData))) == nil)
  81.     {     
  82.         ErrorMsg("ScreenFlip:  Couldn't allocate enough memory!");
  83.         return ModuleError;
  84.     }
  85.  
  86.         // Lock down the storage so we can refer to it by pointer. 
  87.         HLockHi(*storage);
  88.         fs = (TFlipDataPtr) **storage;
  89.     
  90.         // Get the delay-between-flips value from the slider in the module interface
  91.         // and the instantFlip boolean from the checkbox.
  92.         fs->delay = params->controlValues[0];
  93.     fs->instantFlip = params->controlValues[1];
  94.  
  95.     // Sanity check. This should never happen.
  96.     if (fs->delay < 0 || fs->delay > 100)
  97.     {
  98.             ErrorMsg("ScreenFlip:  Internal Error — insane delay value!");
  99.             return ModuleError;
  100.     }
  101.  
  102.     // Initialize the random number generator.
  103.     params->qdGlobalsCopy->qdRandSeed = TickCount();
  104.     fs->demoMode = !EmptyRect((¶ms->demoRect));
  105.  
  106.     // Initialize the globals variables that describe the
  107.     // real, 'physical' screen.
  108.     GetPort(&screenPort);
  109.     fs->realMap = GetGWorldPixMap((GWorldPtr) screenPort);
  110.  
  111.         // The CopyBits function expect these back- and foreground values.
  112.     RGBBackColor(&gWhite);
  113.     RGBForeColor(&gBlack);
  114.  
  115.         // Find the screen's bounding rectangle, as well as the coordinates 
  116.         // of a single column and row.
  117.          fs->r = params->monitors->monitorList[0].bounds;    
  118.     SetRect(&(fs->bufRect[kVertical]), 0, 0, 1, fs->r.bottom);
  119.     SetRect(&(fs->bufRect[kHorizontal]), 0, 0, fs->r.right, 1);
  120.  
  121.         // Initialize data structures for row/column buffers.
  122.     (void) InitStructures(fs, kVertical);
  123.     (void) InitStructures(fs, kHorizontal);
  124.     
  125.     fs->maxStep[kVertical] = fs->r.right / 2;
  126.     fs->maxStep[kHorizontal] = fs->r.bottom / 2;
  127.         
  128.         // We start our flipping in a random direction, moving either
  129.         // inwards or outwards, at time (or step) t = 0.    
  130. //    fs->movement  = (TFlipStates)RangedRdm(0,1);
  131. //    fs->direction = (TDirectionStates)RangedRdm(0,1);
  132.  
  133.     fs->movement  = (RangedRdm(0,1) == 0) ? kVertical : kHorizontal;
  134.     fs->direction = (RangedRdm(0,1) == 0) ? kInwards : kOutwards;
  135.  
  136. //    fs->movement = (Random() > 0) ? kVertical : kHorizontal;
  137. //    fs->direction = (Random() > 0) ? kInwards : kOutwards;
  138.     fs->t = 0;
  139.     
  140.         // If instantFlip is true, we do not show the animation 'ripples' on the
  141.         // screen (caused by successive swaps of pairs of rows/columns), but instead
  142.         // perform the swapping in an offscreen buffer copy of the entire screen, 
  143.         // which we blit onto the 'real' screen when the flip is completed.
  144.     if (fs->instantFlip)
  145.     {
  146.         if (NewGWorld(&(fs->offWorld), 0, &(fs->r), nil, nil, noNewDevice+useTempMem) != noErr)      
  147.         {     
  148.             DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  149.             ErrorMsg("ScreenFlip:  Not enough memory for offscreen graphics world!");
  150.             return ModuleError;            
  151.         }
  152.         fs->screenMap = GetGWorldPixMap((GWorldPtr) fs->offWorld);
  153.         
  154.             // Copy the real screen contents to the offscreen pixmap.
  155.         CopyBits((BitMap *) (*fs->realMap), (BitMap *) (*fs->screenMap), 
  156.                   &fs->r, &fs->r,
  157.                   srcCopy, nil);
  158.     }
  159.     else
  160.             // If we do want ripple animation, the algorithm stays exactly the
  161.             // same, only we make screenMap refer to the physical screen, so that
  162.             // any changes to it will be reflected on the monitor immediately,
  163.             // thus causing the ripples to appear.
  164.         fs->screenMap = fs->realMap;
  165.             
  166.     return noErr;
  167. }
  168.  
  169.  
  170.     // Initialize small offscreen buffers for storing a column or row.
  171.  
  172. static OSErr
  173. InitStructures (TFlipDataPtr fs, int movement)
  174. {
  175.      if (NewGWorld(&fs->bufWorld[movement], 0, &fs->bufRect[movement], nil, nil, noNewDevice+useTempMem) != noErr)      
  176.         return ModuleError;
  177.  
  178.     fs->bufMap[movement] = GetGWorldPixMap(fs->bufWorld[movement]);
  179.     
  180.     return noErr;
  181. }
  182.  
  183.  
  184.     // The DoBlank function blanks out all available screens, *except* for the
  185.     // main screen. If we are in 'instantFlip' mode, care should be taken to
  186.     // switch to the 'real' screen world (and back to offscreen afterwards) before blanking.
  187.  
  188. OSErr
  189. DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  190. {
  191.     RgnHandle otherScreens, mainScreenRgn;
  192.     Rect r = params->monitors->monitorList[0].bounds;    
  193.     
  194.     TFlipDataPtr fs = (TFlipDataPtr) *storage;
  195.     
  196.         // If just one monitor, then we don't need to bother at all...
  197.     if (params->monitors->monitorCount != 1)
  198.     {        
  199.         otherScreens = NewRgn();
  200.         mainScreenRgn = NewRgn();
  201.         
  202.         RGBBackColor(&gBlack);
  203.     
  204.         SetRectRgn(mainScreenRgn, r.left, r.top, r.right, r.bottom);
  205.         DiffRgn(blankRgn, mainScreenRgn, otherScreens);
  206.         EraseRgn(otherScreens);
  207.     
  208.             // In order for CopyBits calls to work, the destination
  209.             // world's backgroundworld *has* to be white (and the foreground
  210.             // black).
  211.         RGBBackColor(&gWhite);
  212.     
  213.         DisposeRgn(mainScreenRgn);
  214.         DisposeRgn(otherScreens);
  215.     }
  216.     
  217.     return noErr;
  218. }
  219.  
  220.  
  221.     // At any given step 't' of the animation process, this function
  222.     // calculates which two rows or columns are to be swapped, taking
  223.     // into account that the animation can move inwards or outwards.
  224.     
  225. static void
  226. SetRects(TFlipDataPtr fs, int movement)
  227. {
  228.     int step;
  229.     
  230.     if (fs->direction == kInwards)
  231.         step = fs->t;
  232.     else /* kOutwards */
  233.         step = fs->maxStep[movement] - fs->t - 1;
  234.     
  235.     if (movement == kHorizontal)
  236.     {
  237.         SetRect(&fs->strip[0], 0, step, fs->r.right, step+1);
  238.         SetRect(&fs->strip[1], 0, fs->r.bottom-step-1, fs->r.right, fs->r.bottom-step);
  239.     }
  240.     else /* kVertical */
  241.     {
  242.         SetRect(&fs->strip[0], step, 0, step+1, fs->r.bottom);
  243.         SetRect(&fs->strip[1], fs->r.right-step-1, 0, fs->r.right-step, fs->r.bottom);
  244.     }
  245. }
  246.  
  247.     
  248.     // The true bottleneck of this module. Three CopyBits calls are necessary to
  249.     // swap the two columns or rows calculated in SetRects. If you look closely
  250.     // at the parameters, you'll notice that these calls are just a bitmap-moving
  251.     // way of doing the well-known "temp = a; a = b; b = temp;" variable swap.
  252.     
  253. static void 
  254. SwapStrips(TFlipDataPtr fs, int movement)
  255. {
  256.     CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*fs->bufMap[movement]), 
  257.               &fs->strip[0], &fs->bufRect[movement],
  258.               srcCopy, nil);
  259.     CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*fs->screenMap), 
  260.                 &fs->strip[1], &fs->strip[0],
  261.                 srcCopy, nil);
  262.     CopyBits((BitMap *) (*fs->bufMap[movement]), (BitMap *) (*fs->screenMap), 
  263.                 &fs->bufRect[movement], &fs->strip[1],
  264.                 srcCopy, nil);
  265. }
  266.  
  267.     // How many ticks have passed since we started counting?
  268. #define TicksPassed (TickCount() - fs->startTick)
  269.  
  270.     // The main animation routine. Each call to DoDrawFrame causes (when in 
  271.     // an animation state) two rows or columns to exchange places.
  272. OSErr
  273. DoDrawFrame(Handle storage, GMParamBlockPtr params)
  274. {    
  275.     TFlipDataPtr fs = (TFlipDataPtr) *storage;
  276.     
  277.         // If we are in After Dark demo mode, the user may have changed
  278.         // the slider value, so we must re-read it.
  279.      if (fs->demoMode)
  280.        {
  281.             if (fs->delay != params->controlValues[0]) 
  282.             fs->delay = params->controlValues[0];
  283.        }
  284.  
  285.     switch (fs->movement)
  286.     {            
  287.         case kHorizontal:
  288.         case kVertical:
  289.     
  290.             if (fs->instantFlip)
  291.             {
  292.                     // If we are instant-flipping there is no need to swap just
  293.                     // one pair of columns/rows for each call of this function.
  294.                     // Instead, we can avoid many seconds of delay by flipping
  295.                     // the entire screen in one go. This of course at the price
  296.                     // of a slightly less responsive attitude towards module
  297.                     // interruptions -- we now spend much more time in DoDrawFrame.
  298.                     
  299.                     // Delay period includes offscreen drawing time, so we start here.
  300.                 fs->startTick = TickCount();
  301.                 
  302.                 while (fs->t < fs->maxStep[fs->movement])
  303.                 {
  304.                     SetRects(fs, fs->movement);
  305.                     SwapStrips(fs, fs->movement);
  306.                     fs->t++;
  307.                 }
  308.             
  309.                     // Blit the completed flip to the real screen.
  310.                 CopyBits((BitMap *) (*fs->screenMap), (BitMap *) (*fs->realMap), 
  311.                           &fs->r, &fs->r,
  312.                           srcCopy, nil);
  313.                 
  314.                     // Start of (possible) extra delay period.
  315.                 fs->movement = kNone;
  316.             }
  317.             else
  318.             {
  319.                 SetRects(fs, fs->movement);
  320.                 SwapStrips(fs, fs->movement);
  321.     
  322.                     // Increment time variable and test for stop criteria.
  323.                 if (++fs->t >= fs->maxStep[fs->movement])
  324.                 {    
  325.                         // Start (possible) delay period.
  326.                         // In this case the delay period does *not* include on-screen drawing time.
  327.                     fs->movement = kNone;
  328.                     fs->startTick = TickCount();
  329.                 }
  330.             }
  331.             break;
  332.  
  333.         case kNone:
  334.         
  335.                 // Do nothing until user-specified delay has passed.
  336.             if (TicksPassed < gDelayMap[fs->delay / 20])
  337.                 return noErr;
  338.             else
  339.                 fs->movement = kSwitch;
  340.             break;
  341.                 
  342.         case kSwitch:
  343.                 // Change to a random new flip type.
  344.             //fs->movement = (TFlipStates)RangedRdm(0,1);
  345.             fs->movement = (RangedRdm(0,1) == 0) ? kVertical : kHorizontal;
  346.             //fs->movement = (Random() < 0) ? kVertical : kHorizontal;
  347.             
  348.                 // Animation direction always goes in-out-in-out-in-out...
  349.             if (fs->direction == kInwards)
  350.                 fs->direction = kOutwards;
  351.             else
  352.                 fs->direction = kInwards;
  353.                 
  354.                 // Re-initialize time variable.
  355.             fs->t = 0;
  356.             break;
  357.     }
  358.     return noErr;
  359. }
  360.  
  361.  
  362.     // ScreenFlip features a funky About Box, which has a miniature logo flip
  363.     // going on inside of it. Creating that animation is mostly straightforward: I
  364.     // just call all the previous functions, i.e. DoInitialize,
  365.     // DoBlank, and DoDrawFrame -- and that's it. The tricky part lies in getting
  366.     // some correct variables in place, and setting up the right graphics port.
  367.     //
  368.     // One thing you should realize about DoAboutBox: when After Dark calls this
  369.     // function, it will already have set the currPort and the blankRgn to the help rectangle. 
  370.     // So you are at this point no longer drawing to the entire screen.
  371.     //
  372.     // Final note: if you want to use a DoAboutBox function in your own module, realize  
  373.     // that (a) you'll need to tell After Dark you want to 'take over' (see the Cals resource in
  374.     // the Programmer's Manual), and (b) 'storage' is *not* initialized in DoHelp,
  375.     // because After Dark calls DoAboutBox without calling DoInitialize first, in contrast
  376.     // to e.g. DoDrawFrame. Unfortunately, a lot of code shows example 'DoAbout' or
  377.     // 'DoAboutBox' functions with the 'storage' handle as a parameter. But this parameter
  378.     // will *not* be intialized, and if you use it, you will crash horribly. So in my
  379.     // my code, I don't even include it.
  380.  
  381. OSErr
  382. DoAboutBox (GMParamBlockPtr params)
  383. {
  384.     TFlipData **miniFlip;
  385.  
  386.     long dummy;
  387.     GrafPtr helpGraf;
  388.     short oldInstantFlip, oldDelay, oldCount;
  389.  
  390.     Rect oldBounds, r;
  391.     RgnHandle miniBlankRgn = NewRgn();
  392.        PicHandle helpPict;                
  393.     Rect helpRect, miniRect;
  394.     short helpRectHeight;
  395.     Boolean runningAD30;
  396.         
  397.         // Get the real screen.
  398.     GetPort(&helpGraf);
  399.  
  400.     helpRect = helpGraf->portRect;
  401.     helpRectHeight = helpRect.bottom - helpRect.top;
  402.     
  403.         // AD 3.0 has this really weird bug: the portRect field of the helpPort
  404.         // doesn't correspond to what the portRect area *really* is. The following
  405.         // hack tries to determine if we are running under 3.0 , and if so it
  406.         // adjusts the portRect to what it really should be.
  407.     runningAD30 = (helpRectHeight > 270);
  408.     if (runningAD30)
  409.     {
  410.         helpRect.left++; 
  411.         helpRect.top++;
  412.     }
  413.     
  414.     RGBBackColor(&gBlack);
  415.     EraseRect(&helpRect);
  416.     r = helpRect;
  417.  
  418.     if (runningAD30)
  419.         InsetRect(&r, 3, 3);
  420.     else
  421.         InsetRect(&r, 2, 2);
  422.         
  423.     RGBForeColor(&gWhite);
  424.     FrameRect(&r);
  425.     
  426.     SetRect(&r, 0, 0, 174, 89);
  427.     CenterRectHorizontal(&r, helpRectHeight/3);
  428.     miniRect = r; 
  429.     if ((helpPict = GetPicture(2000)) == nil) 
  430.     {
  431.         RedAlert("\pCouldn’t load PICT resource for help picture!");
  432.         return ModuleError;
  433.     }
  434.     DrawPicture(helpPict, &r);
  435.     ReleaseResource((Handle) helpPict);
  436.  
  437.     RGBForeColor(&gGold);
  438.     InsetRect(&r, -1, -1);
  439.     FrameRect(&r);
  440.  
  441.     TextFont(geneva); TextSize(9); TextFace(bold);
  442.     RGBForeColor(&gWhite);
  443.     CenterString(helpRectHeight*2/3, "\pA Screensaver Module");
  444.     CenterString(helpRectHeight*2/3 + 12, "\pby Leo Breebaart");
  445.     
  446.     RGBForeColor(&gGold);    
  447.     CenterString(helpRectHeight*11/12, "\pKronto Software 1995");
  448.  
  449.         // We now manually change the graphics environment
  450.         // from the entire help area to the subarea where we want the
  451.         // mini animation to take place (look at the about box in action if this
  452.         // is not clear to you). The current Port is moved and made smaller.
  453.         // The Toolbox calls take care of all the nasty details.
  454.     LocalToGlobal(&topLeft(miniRect));
  455.     LocalToGlobal(&botRight(miniRect));
  456.     MovePortTo(miniRect.left, miniRect.top);
  457.     PortSize(174, 89);
  458.  
  459.         // Save some relevant parameters that the 'real' After Dark
  460.         // animation will need to have restored later on.
  461.     oldCount  = params->monitors->monitorCount;
  462.     oldBounds = params->monitors->monitorList[0].bounds;
  463.     oldDelay = params->controlValues[0];
  464.     oldInstantFlip = params->controlValues[1];
  465.  
  466.         // Change the parameters temporarily to values appropriate for
  467.         // the miniFlip.
  468.     params->monitors->monitorCount = 1;
  469.     params->monitors->monitorList[0].bounds = helpGraf->portRect;
  470.     params->controlValues[0] = 0;
  471.     params->controlValues[1] = 0;
  472.     
  473.     RectRgn(miniBlankRgn, &helpGraf->portRect);    
  474.     
  475.         // Initialize the miniFlip struct. Note that we have a problem
  476.         // with error management here: I can use ErrorMsg all I want,
  477.         // but After Dark will do nothing with the return value of
  478.         // the DoAboutBox function, for some reason. So I just
  479.         // a generic Show-An-Alert error procedure here.
  480.     if (DoInitialize((Handle *) &miniFlip, params) != noErr) 
  481.     {
  482.         RedAlert("\pInitialization of miniFlip failed!");
  483.         return ModuleError;
  484.     }
  485.         
  486.         // Notice that DoBlank() does not need to be called for this miniFlip.
  487.     
  488.         // Wait for the user to release the mouse button, if necessary.
  489.     while (Button())
  490.         ;
  491.         
  492.         // Animate, until the user presses the mouse button.
  493.     while (!Button())
  494.     {
  495.         if (DoDrawFrame((Handle) miniFlip, params) != noErr)
  496.         {
  497.             RedAlert("\pDoDrawFrame of miniFlip failed!");
  498.             return ModuleError;
  499.         }
  500.  
  501.             // MiniFlip animation is too fast for such a small area!        
  502.         Delay(1, &dummy);
  503.     }
  504.     
  505.         // Close it all up.
  506.     DoClose((Handle) miniFlip, miniBlankRgn, params);
  507.  
  508.         // Restore parameters.
  509.     params->monitors->monitorCount = oldCount;
  510.     params->monitors->monitorList[0].bounds = oldBounds;
  511.     params->controlValues[0] = oldDelay;
  512.     params->controlValues[1] = oldInstantFlip;
  513.  
  514.     DisposeRgn(miniBlankRgn);
  515.     DisposeHandle((Handle) miniFlip);
  516.  
  517.      FlushEvents(everyEvent, 0);    
  518.  
  519.     return noErr;
  520. }
  521.  
  522.  
  523.     // The DoClose function merely disposes of all those handles and 
  524.     // offscreen worlds. Nothing interesting here.
  525.  
  526. OSErr
  527. DoClose (Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  528. {
  529.     TFlipData **fs = (TFlipData **) storage;
  530.     
  531.     if (fs)
  532.     {
  533.         DisposeGWorld((**fs).bufWorld[0]);
  534.         DisposeGWorld((**fs).bufWorld[1]);
  535.         if ((**fs).instantFlip)
  536.             DisposeGWorld((**fs).offWorld);
  537.  
  538.         DisposeHandle(storage);
  539.     }
  540.  
  541.     return noErr;
  542. }
  543.  
  544.  
  545.     // These functions are not used in this module, but we include them
  546.     // here so the linker won't complain.
  547. OSErr DoModuleSelected (GMParamBlockPtr params) { return noErr; }
  548. OSErr DoButton (short message, GMParamBlockPtr params) { return noErr; }
  549.  
  550.  
  551.     // This is THE END.
  552.     // If you've learned anything from this code, or found errors in it, or
  553.     // have questions about it, or whatever: feel free to drop me a note.
  554.     // My e-mail address is: leo@cp.tn.tudelft.nl
  555.