home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: WPS_PM / WPS_PM.zip / xfld085s.zip / helpers / animate.c next >
C/C++ Source or Header  |  1999-02-23  |  27KB  |  708 lines

  1.  
  2. /*
  3.  *@@sourcefile animate.c:
  4.  *      contains helper functions to animate a static icon control.
  5.  *      These functions do not rely on the XFolder code, but could
  6.  *      be used in any program.
  7.  *
  8.  *      This is a new file with V0.81. Most of this code used to reside
  9.  *      in common.c with previous versions.
  10.  *
  11.  *      Function prefixes (new with V0.81):
  12.  *      --  anm*   Animation helper functions
  13.  *
  14.  *      To animate a static icon control, you can simply create it
  15.  *      with the dialog editor. Then do the following in your code:
  16.  *
  17.  *      1) load the dlg box template (using WinLoadDlg);
  18.  *
  19.  *      2) load all the icons for the animation in an array of
  20.  *         HPOINTERs (check xsdLoadAnimation in xshutdwn.c for an
  21.  *         example);
  22.  *
  23.  *      3) call anmPrepareAnimation, e.g.:
  24.  +             anmPrepareAnimation(WinWindowFromID(hwndDlg, ID_ICON), // get icon hwnd
  25.  +                             8,                   // no. of icons for the anim
  26.  +                             &ahptr[0],           // ptr to first icon in the array
  27.  +                             150,                 // delay
  28.  +                             TRUE);               // start animation now
  29.  *
  30.  *      4) call WinProcessDlg(hwndDlg);
  31.  *
  32.  *      5) stop the animation, e.g.:
  33.  +              anmStopAnimation(WinWindowFromID(hwndDlg, ID_ICON));
  34.  *
  35.  *      6) destroy the dlg window;
  36.  *
  37.  *      7) free all the HPOINTERS loaded above (check xsdFreeAnimation in
  38.  *         xshutdwn.c for an example).
  39.  *
  40.  *@@include #define INCL_WINWINDOWMGR
  41.  *@@include #define INCL_WINPOINTERS
  42.  *@@include #include <os2.h>
  43.  *@@include #include "animate.h"
  44.  */
  45.  
  46. /*
  47.  *      Copyright (C) 1997-99 Ulrich Möller.
  48.  *      This file is part of the XFolder source package.
  49.  *      XFolder is free software; you can redistribute it and/or modify
  50.  *      it under the terms of the GNU General Public License as published
  51.  *      by the Free Software Foundation, in version 2 as it comes in the
  52.  *      "COPYING" file of the XFolder main distribution.
  53.  *      This program is distributed in the hope that it will be useful,
  54.  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
  55.  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  56.  *      GNU General Public License for more details.
  57.  */
  58.  
  59. #define INCL_DOS
  60. #define INCL_DOSERRORS
  61. #define INCL_DOSDEVICES
  62. #define INCL_DOSDEVIOCTL
  63.  
  64. #define INCL_WIN
  65. #define INCL_WINSYS
  66.  
  67. #define INCL_GPILOGCOLORTABLE
  68. #define INCL_GPIPRIMITIVES
  69.  
  70. #include <os2.h>
  71.  
  72. #include "animate.h"
  73.  
  74. // #define _PMPRINTF_
  75. #include "pmprintf.h"
  76.  
  77. #include "dosh.h"
  78. #include "winh.h"
  79. #include "gpih.h"
  80.  
  81. #include <stdlib.h>
  82. #include <string.h>
  83.  
  84. /*
  85.  *@@ fnwpAnimatedIcon:
  86.  *      subclassed wnd proc for static icons subclassed
  87.  *      with anmPrepareStaticIcon (below). This func
  88.  *      intercepts a few messages to improve icon painting
  89.  *      and also handles the timer for animations, if this
  90.  *      has been enabled.
  91.  */
  92.  
  93. MRESULT EXPENTRY fnwpAnimatedIcon(HWND hwndStatic, ULONG msg, MPARAM mp1, MPARAM mp2)
  94. {
  95.     PANIMATIONDATA pa = (PANIMATIONDATA)WinQueryWindowULong(hwndStatic, QWL_USER);
  96.                 // animation data which was stored in window words
  97.     PFNWP   OldStaticProc = NULL;
  98.     MRESULT mrc = NULL;
  99.  
  100.     if (pa) {
  101.         OldStaticProc = pa->OldStaticProc;
  102.  
  103.         switch(msg) {
  104.  
  105.             /*
  106.              * WM_TIMER:
  107.              *      this timer is started by the anm* funcs
  108.              *      below. Proceed to the next animation step.
  109.              */
  110.  
  111.             case WM_TIMER: {
  112.                 pa->usAniCurrent++;
  113.                 if (pa->usAniCurrent >= pa->usAniCount)
  114.                     pa->usAniCurrent = 0;
  115.  
  116.                 WinSendMsg(hwndStatic,
  117.                         SM_SETHANDLE,
  118.                         (MPARAM)pa->ahptrAniIcons[pa->usAniCurrent],
  119.                         (MPARAM)NULL);
  120.             break; }
  121.  
  122.             /*
  123.              * SM_SETHANDLE:
  124.              *      this is the normal message sent to a static
  125.              *      icon control to change its icon. Unfortunately,
  126.              *      the standard static control paints garbage if
  127.              *      the icons contain transparent areas, and the
  128.              *      display often flickers too.
  129.              *      We improve this by creating one bitmap from
  130.              *      the icon that we were given which we can then
  131.              *      simply copy to the screen in one step in
  132.              *      WM_PAINT.
  133.              */
  134.  
  135.             case SM_SETHANDLE: {
  136.                 HDC hdcMem;
  137.                 PSZ pszData[4] = { "Display", NULL, NULL, NULL };
  138.                 HPS hpsMem;
  139.                 SIZEL sizlPage = {0, 0};
  140.                 BITMAPINFOHEADER2 bmp;
  141.                 PBITMAPINFO2 pbmi = NULL;
  142.                 // HBITMAP hbm = NULLHANDLE;
  143.                 POINTL aptl[3];
  144.                 LONG alData[2];
  145.  
  146.                 LONG lBkgndColor = winhQueryPresColor(
  147.                                 WinQueryWindow(hwndStatic, QW_PARENT),
  148.                                 PP_BACKGROUNDCOLOR,
  149.                                 "DialogBackground",
  150.                                 "255 255 255");
  151.  
  152.                 HAB hab = WinQueryAnchorBlock(hwndStatic);
  153.  
  154.                 HPS hps = WinGetPS(hwndStatic);
  155.  
  156.                 // store new icon in our own structure
  157.                 pa->hptr = (HPOINTER)mp1;
  158.  
  159.                 // switch to RGB mode
  160.                 GpiCreateLogColorTable(hps, 0,
  161.                     LCOLF_RGB,
  162.                     0, 0, NULL);
  163.  
  164.                 // if we have created a bitmap previously,
  165.                 // delete it
  166.                 if (pa->hbm)
  167.                     GpiDeleteBitmap(pa->hbm);
  168.  
  169.                 // create a memory PS
  170.                 if (gpihCreateMemPS(hab, &hdcMem, &hpsMem))
  171.                 {
  172.                     // switch to RGB mode
  173.                     GpiCreateLogColorTable(hpsMem, 0,
  174.                         LCOLF_RGB,
  175.                         0, 0, NULL);
  176.  
  177.                     // create a suitable bitmap w/ the size of the
  178.                     // static control
  179.                     if (pa->hbm = gpihCreateBitmap(hpsMem, &(pa->rclIcon)))
  180.                     {
  181.                         // associate the bit map with the memory PS
  182.                         if (GpiSetBitmap(hpsMem, pa->hbm) != HBM_ERROR)
  183.                         {
  184.                             POINTL ptl = {0, 0};
  185.                             POINTERINFO pi;
  186.  
  187.                             // fill the bitmap with the current static
  188.                             // background color
  189.                             GpiMove(hpsMem, &ptl);
  190.                             ptl.x = pa->rclIcon.xRight;
  191.                             ptl.y = pa->rclIcon.yTop;
  192.                             GpiSetColor(hpsMem,
  193.                                     lBkgndColor);
  194.                             GpiBox(hpsMem,
  195.                                     DRO_FILL, // interior only
  196.                                     &ptl,
  197.                                     0, 0);    // no corner rounding
  198.  
  199.                             // now get the bitmaps for the new icon
  200.                             // that we were given in mp1.
  201.                             // Each icon consists of two (really three)
  202.                             // bitmaps, which are stored in the POINTERINFO
  203.                             // structure:
  204.                             //   pi.hbmColor    is the actual bitmap to be
  205.                             //                  drawn. The parts that are
  206.                             //                  to be transparent or inverted
  207.                             //                  are black in this image.
  208.                             //   pi.hbmPointer  has twice the height of
  209.                             //                  hbmColor. The upper bitmap
  210.                             //                  contains an XOR mask (for
  211.                             //                  inverting parts), the lower
  212.                             //                  bitmap an AND mask (for
  213.                             //                  transparent parts).
  214.                             if (WinQueryPointerInfo(pa->hptr, &pi))
  215.                             {
  216.                                 POINTL  aptl[4];
  217.  
  218.                                 memset(aptl, 0, sizeof(POINTL) * 4);
  219.  
  220.                                 aptl[1].x = pa->ulSysIconSize;
  221.                                 aptl[1].y = pa->ulSysIconSize;
  222.  
  223.                                 aptl[3].x = pa->ulSysIconSize + 1;
  224.                                 aptl[3].y = pa->ulSysIconSize + 1;
  225.  
  226.                                 GpiSetColor(hpsMem, CLR_WHITE);
  227.                                 GpiSetBackColor(hpsMem, CLR_BLACK);
  228.  
  229.                                 // GpiErase(hpsMem);
  230.  
  231.                                 // work on the AND image
  232.                                 GpiWCBitBlt(hpsMem,
  233.                                         pi.hbmPointer,
  234.                                         4L,         // point count, always 4
  235.                                         &aptl[0],   // point array
  236.                                         ROP_SRCAND,
  237.                                         BBO_OR);
  238.  
  239.                                 // paint the real image
  240.                                 if (pi.hbmColor)
  241.                                     GpiWCBitBlt(hpsMem,
  242.                                             pi.hbmColor,
  243.                                             4L,
  244.                                             &aptl[0],   // point array
  245.                                             ROP_SRCPAINT,
  246.                                             BBO_OR);
  247.  
  248.                                 GpiSetColor(hpsMem, lBkgndColor);
  249.                                 // work on the XOR image
  250.                                 aptl[2].y = pa->ulSysIconSize;
  251.                                 aptl[3].y = (pa->ulSysIconSize * 2) + 1;
  252.                                 GpiWCBitBlt(hpsMem,
  253.                                         pi.hbmPointer,
  254.                                         4L,         // point count, always 4
  255.                                         &aptl[0],   // point array
  256.                                         ROP_SRCINVERT,
  257.                                         BBO_OR);
  258.                             }
  259.                         }
  260.                     }
  261.                     GpiDestroyPS(hpsMem);
  262.                     DevCloseDC(hdcMem);
  263.                 } // end if (hdcMem = DevOpenDC(
  264.  
  265.                 // enforce WM_PAINT
  266.                 WinInvalidateRect(hwndStatic, NULL, FALSE);
  267.  
  268.                 /* WinFillRect(hps, &(pa->rclIcon),
  269.                     pa->lBkgndColor);
  270.  
  271.                 if (pa->hptr) {
  272.                     WinDrawPointer(hps, 0, 0, pa->hptr, DP_NORMAL);
  273.                 } */
  274.  
  275.                 WinReleasePS(hps);
  276.             break; }
  277.  
  278.             /*
  279.              * WM_PAINT:
  280.              *      "normal" paint; this only arrives here if the
  281.              *      icon needs to be repainted for reasons other
  282.              *      than changing the icon.
  283.              */
  284.  
  285.             case WM_PAINT: {
  286.                 RECTL rcl;
  287.                 POINTL ptl = {0, 0};
  288.                 POINTERINFO pi;
  289.                 HPS hps = WinBeginPaint(hwndStatic, NULLHANDLE, &rcl);
  290.                 // draw the bitmap that've previously created
  291.                 WinDrawBitmap(hps,
  292.                         pa->hbm,
  293.                         NULL,     // whole bmp
  294.                         &ptl,
  295.                         0, 0,     // no colors
  296.                         DBM_NORMAL);
  297.                 WinEndPaint(hps);
  298.             break; }
  299.  
  300.             /*
  301.              * WM_DESTROY:
  302.              *      clean up.
  303.              */
  304.  
  305.             case WM_DESTROY: {
  306.                 // undo subclassing in case more WM_TIMERs come in
  307.                 WinSubclassWindow(hwndStatic, OldStaticProc);
  308.  
  309.                 if (pa->hbm)
  310.                     GpiDeleteBitmap(pa->hbm);
  311.  
  312.                 // clean up ANIMATIONDATA struct
  313.                 WinSetWindowULong(hwndStatic, QWL_USER, (ULONG)NULL);
  314.                 free(pa);
  315.  
  316.                 mrc = OldStaticProc(hwndStatic, msg, mp1, mp2);
  317.             break; }
  318.  
  319.             default:
  320.                 mrc = OldStaticProc(hwndStatic, msg, mp1, mp2);
  321.        }
  322.     }
  323.     return (mrc);
  324. }
  325.  
  326. /*
  327.  *@@ anmPrepareStaticIcon:
  328.  *      turns a static control into one which properly
  329.  *      displays icons when given a SM_SETHANDLE msg.
  330.  *      This is done by subclassing the static control
  331.  *      with fnwpAnimatedIcon.
  332.  *      This function gets called by anmPrepareAnimation,
  333.  *      so you need not call it independently.
  334.  *      This func does _not_ start an animation yet.
  335.  *      Returns a PANIMATIONDATA if subclassing succeeded or
  336.  *      NULL upon errors. If you only call this function, you
  337.  *      do not need this structure; it is needed by
  338.  *      anmPrepareAnimation though.
  339.  *      The subclassed static icon func above automatically
  340.  *      cleans up resources, so you don't have to worry about
  341.  *      that either.
  342.  */
  343.  
  344. PANIMATIONDATA anmPrepareStaticIcon(HWND hwndStatic,
  345.                     USHORT usAnimCount) // needed for allocating extra memory;
  346.                                         // this must be at least 1
  347. {
  348.     PANIMATIONDATA pa = NULL;
  349.     PFNWP OldStaticProc = WinSubclassWindow(hwndStatic, fnwpAnimatedIcon);
  350.     if (OldStaticProc) {
  351.         pa = malloc(
  352.                         sizeof(ANIMATIONDATA)
  353.                       + ((usAnimCount-1) * sizeof(HPOINTER))
  354.                    );
  355.         pa->OldStaticProc = OldStaticProc;
  356.         WinQueryWindowRect(hwndStatic, &(pa->rclIcon));
  357.         pa->hbm = NULLHANDLE;
  358.         pa->ulSysIconSize = WinQuerySysValue(HWND_DESKTOP, SV_CXICON);
  359.  
  360.         WinSetWindowULong(hwndStatic, QWL_USER, (ULONG)pa);
  361.     }
  362.     return (pa);
  363. }
  364.  
  365. /*
  366.  *@@ anmPrepareAnimation:
  367.  *      this function makes a regular static icon
  368.  *      control an animated one. This is the one-shot
  369.  *      function for animating static icon controls.
  370.  *      It calls anmPrepareStaticIcon first, then uses
  371.  *      the passed parameters to prepare an animation:
  372.  *      pahptr must point to an array of HPOINTERs
  373.  *      which must contain usAnimCount icon handles.
  374.  *      If (fStartAnimation == TRUE), the animation
  375.  *      is started already. Otherwise you'll have
  376.  *      to call anmStartAnimation yourself.
  377.  *      When the icon control is destroyed, the
  378.  *      subclassed window proc (fnwpAnimatedIcon)
  379.  *      automatically cleans up resources. However,
  380.  *      the icons in *pahptr are not freed.
  381.  */
  382.  
  383. BOOL anmPrepareAnimation(HWND hwndStatic,   // icon hwnd
  384.                 USHORT usAnimCount,         // no. of anim steps
  385.                 HPOINTER *pahptr,           // array of HPOINTERs
  386.                 ULONG ulDelay,              // delay per anim step (in ms)
  387.                 BOOL fStartAnimation)       // TRUE: start animation now
  388. {
  389.     PANIMATIONDATA paNew = anmPrepareStaticIcon(hwndStatic, usAnimCount);
  390.     if (paNew) {
  391.         paNew->ulDelay = ulDelay;
  392.         // paNew->OldStaticProc already set
  393.         paNew->hptr = NULLHANDLE;
  394.         // paNew->rclIcon already set
  395.         paNew->usAniCurrent = 0;
  396.         paNew->usAniCount = usAnimCount;
  397.         memcpy(&(paNew->ahptrAniIcons), pahptr,
  398.                         (usAnimCount * sizeof(HPOINTER)));
  399.  
  400.         if (fStartAnimation) {
  401.             WinStartTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic,
  402.                     1, ulDelay);
  403.             WinPostMsg(hwndStatic, WM_TIMER, NULL, NULL);
  404.         }
  405.     }
  406.     return (paNew != NULL);
  407. }
  408.  
  409. /*
  410.  *@@ anmStartAnimation:
  411.  *      starts an animation that not currently running. You
  412.  *      must prepare the animation by calling anmPrepareAnimation
  413.  *      first.
  414.  */
  415.  
  416. BOOL anmStartAnimation(HWND hwndStatic)
  417. {
  418.     BOOL brc = FALSE;
  419.     PANIMATIONDATA pa = (PANIMATIONDATA)WinQueryWindowULong(hwndStatic, QWL_USER);
  420.     if (pa)
  421.         if (WinStartTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic,
  422.                         1, pa->ulDelay))
  423.         {
  424.             brc = TRUE;
  425.             WinPostMsg(hwndStatic, WM_TIMER, NULL, NULL);
  426.         }
  427.     return (brc);
  428. }
  429.  
  430. /*
  431.  *@@ anmStopAnimation:
  432.  *      stops an animation that is currently running.
  433.  *      This does not free any resources.
  434.  */
  435.  
  436. BOOL anmStopAnimation(HWND hwndStatic)
  437. {
  438.     return (WinStopTimer(WinQueryAnchorBlock(hwndStatic), hwndStatic, 1));
  439. }
  440.  
  441. /*
  442.  *@@ anmBlowUpBitmap:
  443.  *      this displays an animation based on a given bitmap.
  444.  *      The bitmap is "blown up" in that it is continually
  445.  *      increased in size until the original size is reached.
  446.  *      The animation is calculated so that it lasts exactly
  447.  *      ulAnimation milliseconds, no matter how fast the
  448.  *      system is.
  449.  *      You should run this routine in a thread with higher-
  450.  *      than-normal priority, because otherwise the kernel
  451.  *      might interrupt the display.
  452.  *      Returns the count of animation steps that were drawn.
  453.  *      This is dependent on the speed of the system.
  454.  */
  455.  
  456. BOOL anmBlowUpBitmap(HPS hps,               // in: from WinGetPS(HWND_DESKTOP)
  457.                      HBITMAP hbm,           // in: bitmap to be displayed
  458.                      ULONG ulAnimationTime) // in: total animation time (ms)
  459. {
  460.     ULONG   ulrc = 0;
  461.     ULONG   ulInitialTime,
  462.             ulNowTime;
  463.             // ulCurrentSize = 10;
  464.  
  465.     if (hps) {
  466.         POINTL  ptl = {0, 0};
  467.         RECTL   rtlStretch;
  468.         ULONG   ul,
  469.                 ulSteps = 20;
  470.         BITMAPINFOHEADER bih;
  471.         GpiQueryBitmapParameters(hbm, &bih);
  472.         /* ptl.y = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN)
  473.                         - BMPSPACING
  474.                         - bih.cy; */
  475.         ptl.x = (WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN)
  476.                         - bih.cx) / 2;
  477.         ptl.y = (WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN)
  478.                         - bih.cy) / 2;
  479.  
  480.         // we now use ul for the current animation step,
  481.         // which is a pointer on a scale from 1 to ulAnimationTime;
  482.         // ul will be recalculated after each animation
  483.         // step according to how much time the animation
  484.         // has cost on this display so far. This has the
  485.         // following advantages:
  486.         // 1) no matter how fast the system is, the animation
  487.         //    will always last ulAnimationTime milliseconds
  488.         // 2) since large bitmaps take more time to calculate,
  489.         //    the animation won't appear to slow down then
  490.         ulInitialTime = doshGetULongTime();
  491.         ul = 1;
  492.         ulSteps = 1000;
  493.         do {
  494.             LONG cx = (((bih.cx-20) * ul) / ulSteps) + 20;
  495.             LONG cy = (((bih.cy-20) * ul) / ulSteps) + 20;
  496.             rtlStretch.xLeft   = ptl.x + ((bih.cx - cx) / 2);
  497.             rtlStretch.yBottom = ptl.y + ((bih.cy - cy) / 2);
  498.             rtlStretch.xRight = rtlStretch.xLeft + cx;
  499.             rtlStretch.yTop   = rtlStretch.yBottom + cy;
  500.  
  501.             WinDrawBitmap(hps, hbm, NULL, (PPOINTL)&rtlStretch,
  502.                     0, 0,       // we don't need colors
  503.                     DBM_STRETCH);
  504.  
  505.             ulNowTime = doshGetULongTime();
  506.  
  507.             // recalculate ul: rule of three based on the
  508.             // time we've spent on animation so far
  509.             ul = (ulSteps
  510.                     * (ulNowTime - ulInitialTime)) // time spent so far
  511.                     / ulAnimationTime;      // time to spend altogether
  512.  
  513.             ulrc++;                         // return count
  514.  
  515.         } while (ul < ulSteps);
  516.  
  517.         // finally, draw the 1:1 version
  518.         WinDrawBitmap(hps, hbm, NULL, &ptl,
  519.                 0, 0,       // we don't need colors
  520.                 DBM_NORMAL);
  521.  
  522.     } // end if (hps)
  523.  
  524.     return (ulrc);
  525. }
  526.  
  527. #define LAST_WIDTH 2
  528. #define LAST_STEPS 50
  529. #define WAIT_TIME  10
  530.  
  531. /*
  532.  *@@ anmPowerOff:
  533.  *      displays an animation that looks like a
  534.  *      monitor being turned off; hps must have
  535.  *      been acquired using WinGetScreenPS,
  536.  *      ulSteps should be around 40-50.
  537.  */
  538.  
  539. VOID anmPowerOff(HPS hps, ULONG ulSteps)
  540. {
  541.     RECTL       rclScreen, rclNow, rclLast, rclDraw;
  542.     ULONG       ul = ulSteps;
  543.     ULONG       ulPhase = 1;
  544.  
  545.     WinQueryWindowRect(HWND_DESKTOP, &rclScreen);
  546.  
  547.     WinShowPointer(HWND_DESKTOP, FALSE);
  548.  
  549.     memcpy(&rclLast, &rclScreen, sizeof(RECTL));
  550.  
  551.     // In order to draw the animation, we tell apart three
  552.     // "phases", signified by the ulPhase variable. While
  553.     // ulPhase != 99, we stay in a do-while loop.
  554.     ul = 0;
  555.     ulPhase = 1;
  556.  
  557.     do {
  558.         ULONG ulFromTime = doshGetULongTime();
  559.  
  560.         if (ulPhase == 1)
  561.         {
  562.             // Phase 1: "shrink" the screen by drawing black
  563.             // rectangles from the edges towards the center
  564.             // of the screen. With every loop, we draw the
  565.             // rectangles a bit closer to the center, until
  566.             // the center is black too. Sort of like this:
  567.  
  568.             //          ***************************
  569.             //          *       black             *
  570.             //          *                         *
  571.             //          *      .............      *
  572.             //          *      . rclNow:   .      *
  573.             //          *  ->  . untouched .  <-  *
  574.             //          *      .............      *
  575.             //          *            ^            *
  576.             //          *            !            *
  577.             //          ***************************
  578.  
  579.             // rclNow contains the rectangle _around_ which
  580.             // the black rectangles are to be drawn. With
  581.             // every iteration, rclNow is reduced in size.
  582.             rclNow.xLeft = ((rclScreen.yTop / 2) * ul / ulSteps );
  583.             rclNow.xRight = (rclScreen.xRight) - rclNow.xLeft;
  584.             rclNow.yBottom = ((rclScreen.yTop / 2) * ul / ulSteps );
  585.             rclNow.yTop = (rclScreen.yTop) - rclNow.yBottom;
  586.  
  587.             if (rclNow.yBottom > (rclNow.yTop - LAST_WIDTH) ) {
  588.                 rclNow.yBottom = (rclScreen.yTop / 2) - LAST_WIDTH;
  589.                 rclNow.yTop = (rclScreen.yTop / 2) + LAST_WIDTH;
  590.             }
  591.  
  592.             // draw black rectangle on top of rclNow
  593.             rclDraw.xLeft = rclLast.xLeft;
  594.             rclDraw.xRight = rclLast.xRight;
  595.             rclDraw.yBottom = rclNow.yTop;
  596.             rclDraw.yTop = rclLast.yTop;
  597.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  598.  
  599.             // draw black rectangle left of rclNow
  600.             rclDraw.xLeft = rclLast.xLeft;
  601.             rclDraw.xRight = rclNow.xLeft;
  602.             rclDraw.yBottom = rclLast.yBottom;
  603.             rclDraw.yTop = rclLast.yTop;
  604.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  605.  
  606.             // draw black rectangle right of rclNow
  607.             rclDraw.xLeft = rclNow.xRight;
  608.             rclDraw.xRight = rclLast.xRight;
  609.             rclDraw.yBottom = rclLast.yBottom;
  610.             rclDraw.yTop = rclLast.yTop;
  611.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  612.  
  613.             // draw black rectangle at the bottom of rclNow
  614.             rclDraw.xLeft = rclLast.xLeft;
  615.             rclDraw.xRight = rclLast.xRight;
  616.             rclDraw.yBottom = rclLast.yBottom;
  617.             rclDraw.yTop = rclNow.yBottom;
  618.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  619.  
  620.             // remember rclNow for next iteration
  621.             memcpy(&rclLast, &rclNow, sizeof(RECTL));
  622.  
  623.             // done with "shrinking"?
  624.             if ( rclNow.xRight < ((rclScreen.xRight / 2) + LAST_WIDTH) ) {
  625.                 ulPhase = 2; // exit
  626.             }
  627.  
  628.         } else if (ulPhase == 2) {
  629.             // Phase 2: draw a horizontal white line about
  630.             // where the last rclNow was. This is only
  631.             // executed once.
  632.  
  633.             //          ***************************
  634.             //          *       black             *
  635.             //          *                         *
  636.             //          *                         *
  637.             //          *        -----------      *
  638.             //          *                         *
  639.             //          *                         *
  640.             //          *                         *
  641.             //          ***************************
  642.  
  643.             rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
  644.             rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
  645.             rclDraw.yBottom = (rclScreen.yTop * 1 / 4);
  646.             rclDraw.yTop = (rclScreen.yTop * 3 / 4);
  647.             WinFillRect(hps, &rclDraw, CLR_WHITE);
  648.  
  649.             ulPhase = 3;
  650.             ul = 0;
  651.  
  652.         } else if (ulPhase == 3) {
  653.             // Phase 3: make the white line shorter with
  654.             // every iteration by drawing black rectangles
  655.             // above it. These are drawn closer to the
  656.             // center with each iteration.
  657.  
  658.             //          ***************************
  659.             //          *       black             *
  660.             //          *                         *
  661.             //          *                         *
  662.             //          *       ->  ----  <-      *
  663.             //          *                         *
  664.             //          *                         *
  665.             //          *                         *
  666.             //          ***************************
  667.  
  668.             rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
  669.             rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
  670.             rclDraw.yTop = (rclScreen.yTop * 3 / 4);
  671.             rclDraw.yBottom = (rclScreen.yTop * 3 / 4) - ((rclScreen.yTop * 1 / 4) * ul / LAST_STEPS);
  672.             if (rclDraw.yBottom < ((rclScreen.yTop / 2) + LAST_WIDTH))
  673.                 rclDraw.yBottom = ((rclScreen.yTop / 2) + LAST_WIDTH);
  674.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  675.  
  676.             rclDraw.xLeft = (rclScreen.xRight / 2) - LAST_WIDTH;
  677.             rclDraw.xRight = (rclScreen.xRight / 2) + LAST_WIDTH;
  678.             rclDraw.yBottom = (rclScreen.yTop * 1 / 4);
  679.             rclDraw.yTop = (rclScreen.yTop * 1 / 4) + ((rclScreen.yTop * 1 / 4) * ul / LAST_STEPS);
  680.             if (rclDraw.yTop > ((rclScreen.yTop / 2) - LAST_WIDTH))
  681.                 rclDraw.yBottom = ((rclScreen.yTop / 2) - LAST_WIDTH);
  682.  
  683.             WinFillRect(hps, &rclDraw, CLR_BLACK);
  684.  
  685.             ul++;
  686.             if (ul > LAST_STEPS) {
  687.                 ulPhase = 99;
  688.             }
  689.         }
  690.  
  691.         ul++;
  692.  
  693.         while (doshGetULongTime() < ulFromTime + WAIT_TIME) {
  694.             // PSZ p = NULL; // keep compiler happy
  695.         }
  696.     } while (ulPhase != 99);
  697.  
  698.     // sleep a while
  699.     DosSleep(500);
  700.     WinFillRect(hps, &rclScreen, CLR_BLACK);
  701.     DosSleep(500);
  702.  
  703.     WinShowPointer(HWND_DESKTOP, TRUE);
  704. }
  705.  
  706.  
  707.  
  708.