/** dropshadow.c **/

/* this program copyright 1987, james mackraz.  may not be distributed
 * for profit.  copies of the source may be made for not-for-profit
 * distribution, but must include this notice.
 *
 * james mackraz, 4021 Second Street, Palo Alto, CA, 94306
 */

#include "ds.h"
#include <tools/debug.h>

#define printf  kprintf

UBYTE	*wtitle	= (UBYTE *) "  DropShadow -- ver 2, rev 0  ";
struct  Window      *getNewWind();

struct RastPort	*rport2 = NULL;	/* for rendering into bmap2	*/
struct Window   *window = NULL;

struct	Task	*mytask;
UBYTE			mysig = 0;

struct  IntuitionBase   *IntuitionBase;
struct  GfxBase         *GfxBase;
struct	LayersBase		*LayersBase;

ULONG   flg = ACTIVATE | WINDOWCLOSE | NOCAREREFRESH | WINDOWDRAG
	    | WINDOWDEPTH | SIMPLE_REFRESH;

ULONG   iflg = MOUSEMOVE | CLOSEWINDOW | GADGETDOWN | GADGETUP;

/* for each layer, front to back, not incl. backdrops	*/
#define FORLAYERS(l, linfo)	for (l = (linfo)->top_layer; \
	 l && !(l->Flags & LAYERBACKDROP);l=l->back)

#define BACKTOFRONT(l)	 for (l = window->WLayer->LayerInfo->top_layer;	\
l && l->back; l = l->back) ; \
for (; l; l = l->front)

#define PREDLAYERS(pred, l) for (pred = l->front; pred; pred = pred->front)

#define MYPRI			 7L	/* a little advantage						*/
#define INPUTPRI_PLUS	25L	/* i guess i could findtask("input.device")	*/

USHORT	hdrop = 7;
USHORT	vdrop = 3;

main()
{
    struct  IntuiMessage    *msg;

    /* hold data from *msg  */
    ULONG   class;
    UBYTE   code;
	struct Gadget *gaddress;

    struct Screen	*wbscreen;
	struct BitMap	*save_bitmap= NULL;	/* original WB RasInfo bitmap	*/
	struct BitMap	*ribitmap	= NULL;		/* my 3-deep replacement		*/
    struct BitMap  	*bmap2		= NULL;

	int		i;
    int		it_is_done = 0;		/* success flag			*/
    WORD	exitval = 0;
	ULONG	sigmask;
	ULONG	sigtaken;

	extern struct PropInfo twxtinfo;
	USHORT	twxtpot;

    if (!(IntuitionBase = (struct IntuitionBase *)
		OpenLibrary("intuition.library", 0L)))
    {
		D( printf("NO INTUITION LIBRARY\n") );
		exitval = 1;
		goto EXITING;
    }

    if (!(GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 0L)))
    {
		D( printf("NO GRAPHICS LIBRARY\n") );
		exitval = 2;
		goto EXITING;
    }

    if (!(LayersBase=(struct LayersBase *) OpenLibrary("layers.library", 0L)))
    {
		D( printf("NO LAYERS LIBRARY\n") );
		exitval = 2;
		goto EXITING;
    }

    /* get a window on the workbench	*/
    window = getNewWind(120, 20, 400, 30, flg, iflg);
    if (window == NULL)
    {
		D( printf("test: can't get window.\n") );
		exitval = 1;
		goto EXITING;
    }

    /* Add bitplane to the Workbench, as far as ViewPort is concerned	*/

    wbscreen = window->WScreen;		/* find it	*/

	/* new 3-deep bitmap will replace the one in WB's RInfo	*/
    if (!(ribitmap = (struct BitMap *)
		AllocMem((LONG) sizeof(struct BitMap), (LONG) MEMF_PUBLIC|MEMF_CLEAR)))
    {
		D( printf("alloc bitmap failed\n") );
		goto EXITING;
    }
    InitBitMap(ribitmap, 3L, (LONG) wbscreen->Width, (LONG) wbscreen->Height);

    /* allocate bitmap for my rastport view of single bitplane	*/
    if (!(bmap2 = (struct BitMap *)
		AllocMem((LONG) sizeof(struct BitMap), (LONG) MEMF_PUBLIC|MEMF_CLEAR)))
    {
		D( printf("alloc bitmap failed\n") );
		goto EXITING;
    }

	/* my rendering bit map: depth 1	*/
    InitBitMap(bmap2, 1L, (LONG) wbscreen->Width, (LONG) wbscreen->Height);

    if (!(bmap2->Planes[0] =
		(UBYTE *) AllocRaster((LONG) wbscreen->Width, (LONG) wbscreen->Height)))
    {
		D( printf("alloc raster failed\n") );
		goto EXITING;
    }

    /* get a rastport, and set it up for rendering into bmap2	*/
    if (!(rport2 = (struct RastPort *)
		AllocMem((LONG) sizeof (struct RastPort), (LONG) MEMF_PUBLIC)))
    {
		D( printf("alloc rastport failed\n") );
		goto EXITING;
    }
    InitRastPort(rport2);
    rport2->BitMap = bmap2;

    SetRast(rport2, 0L);

	/* set up new bitmap for RInfo	*/
	save_bitmap =  wbscreen->ViewPort.RasInfo->BitMap;
	ribitmap->Planes[0] = save_bitmap->Planes[0];
	ribitmap->Planes[1] = save_bitmap->Planes[1];
	ribitmap->Planes[2] = bmap2->Planes[0];

    Forbid();

	/* add our extra plane to the bit-map as viewed by
	 * the WB ViewPort (WB should never know the difference)
	 */
	wbscreen->ViewPort.RasInfo->BitMap = ribitmap;

    Permit();

	it_is_done = 1;

	shadowColors(wbscreen);

    /* put viewport changed into effect	*/
    MakeScreen(wbscreen);
    RethinkDisplay();

	twxtpot = twxtinfo.HorizPot;
	hdrop = twxtpot >> TWXTSHIFT;
	vdrop = twxtpot >> (TWXTSHIFT + 1);
    drawShadow();

	/* hook into the library vectors	*/
	mytask = FindTask(0L);
	mysig  = AllocSignal((LONG) -1);
	sigmask = ((LONG) 1 << mysig);
	sigmask |= ((LONG) 1 << window->UserPort->mp_SigBit);
	setup_hooks();

	/* a little help	*/
	SetTaskPri(mytask, MYPRI);

    FOREVER
    {
		if ((msg = (struct IntuiMessage *)GetMsg(window->UserPort)) == NULL)
		{
			sigtaken = Wait(sigmask);
		    if (sigtaken & ((LONG) 1 << mysig))
			{
				drawShadow();
			}
		    continue;
		}

		class   = msg->Class;
		code	= msg->Code;
		gaddress  = (struct Gadget *) msg->IAddress;
		ReplyMsg(msg);

		switch (class)
		{
		case MOUSEMOVE:		/* taken at higher priority	*/
			shadowColors(wbscreen);
			break;

		case GADGETDOWN:
			SetTaskPri(mytask, INPUTPRI_PLUS);
			break;

		case GADGETUP:
			SetTaskPri(mytask, MYPRI);

			switch (gaddress->GadgetID)
			{
			case TWIXTID:
				twxtpot = twxtinfo.HorizPot;
				hdrop = twxtpot >> TWXTSHIFT;
				vdrop = twxtpot >> (TWXTSHIFT + 1);
				drawShadow();
				break;
			case DARKID:
				shadowColors(wbscreen);
				break;
			}
			break;

		case CLOSEWINDOW:
			SetTaskPri(mytask, 0L);
			if (cleanup_hooks()) goto EXITING;
		default:
		    D( printf("unknown event: class %lx\n", class) );
		}
    }

EXITING:
    /* clean up trick	*/
    if (it_is_done)
    {
		Forbid();
		wbscreen->ViewPort.RasInfo->BitMap = save_bitmap;
		Permit();
		MakeScreen(wbscreen);
		RethinkDisplay();
    }

	if (mysig) FreeSignal((LONG) mysig);

    if (rport2) FreeMem(rport2, (LONG) sizeof (struct RastPort));
    if (bmap2) 
    {
		if (bmap2->Planes[0])
		{
		    FreeRaster(bmap2->Planes[0],
		    	(LONG) wbscreen->Width, (LONG) wbscreen->Height);
		}
		FreeMem(bmap2, (LONG) sizeof (struct BitMap));
    }
	if (ribitmap) FreeMem(ribitmap, (LONG) sizeof (struct BitMap));

    if (window) CloseWindow(window);
    if (GfxBase) CloseLibrary(GfxBase);
    if (LayersBase) CloseLibrary(LayersBase);
    if (IntuitionBase) CloseLibrary(IntuitionBase);

    exit (exitval);
}

/* returns true if this layer casts a shadow
 * note that menu layer does not cast a shadow
 */
shadowCaster(l)
register struct Layer *l;
{
	extern struct PropInfo darkinfo;
	register struct Window		*w;
	register struct RastPort	*brp;

	if (l && (w = (struct Window *) l->Window))
	{
		/* must be border layer for GZZ window	*/
		if (brp = w->BorderRPort)
		{
			/* GZZ	*/
			return (brp->Layer == l);
		}
		else
		{
			/* non-GZZ	*/
			return (l == w->WLayer);
		}
	}
	return (0);
}

/* called by my replacement layers vectors	*/
drawShadowSignal(sysret)
LONG	sysret;
{
	Signal( mytask, ((LONG) 1 << mysig));
	return (sysret);
}

/* run on my own task's schedule	*/
drawShadow(sysret)
LONG	sysret;
{
	struct Region	*shadow;

	if ( !(shadow = NewRegion()) ) return (sysret);

	bigShadow(shadow);

	SetRast(rport2, 0L);

	Forbid();
	WaitBOVP(&window->WScreen->ViewPort);

	SetAPen(rport2, 1L);
	fillRegion(rport2, shadow);
	Permit();

	DisposeRegion(shadow);
}

/* you want speed? speed this up.	*/
bigShadow(rgn)
struct	Region	*rgn;
{
	register struct Layer	*pred;	/* pred to 'l'						*/
	register LONG			tmprgn;	/* accumulates shadow on layer 'l'	*/
	register struct Layer	*l;		/* create all shadows on each layer	*/

	int				twixt;		/* number of "spaces" between l, pred	*/
	int				htwixt;
	int				vtwixt;
	struct Rectangle lrect;
	struct Rectangle predrect;

	if ( !(tmprgn = (LONG) NewRegion()) ) return;

	/* protect the integrity of the layer list	*/
	LockLayerInfo(window->WLayer->LayerInfo);

	BACKTOFRONT(l)
	{
		/* could eliminate any layers you don't want a shadow on here.	*/

		/* layer rectangle	*/
		lrect = l->bounds;

#if 0
		notRectRegion(rgn, &lrect);	/* l obscures shadows behind it		*/
#else
		ClearRectRegion(rgn, &lrect);	/* dale's version				*/
#endif

#if OLDTWIXT
		twixt = 0;
#else
		htwixt = hdrop << 1;
		vtwixt = vdrop << 1;
#endif
		ClearRegion(tmprgn);		/* will build shadow on single layer */

		/* walk through shadow casters	*/
		PREDLAYERS(pred, l)
		{
			/* menu bar doesn't cast shadow	*/
			if (!shadowCaster(pred)) 
			{
				continue;
			}

			/* rectangle will cast shadow				*/
			predrect = pred->bounds;	/* layer rect	*/

			/* shadow offset increases with depth		*/
#if OLDTWIXT
			translateRect(&predrect,
				HDROP + twixt * (HDROP >> 1), VDROP + twixt * (VDROP >> 1));

			++twixt;				/* another level up					*/
#else
			translateRect(&predrect, htwixt, vtwixt);
			htwixt += hdrop;
			vtwixt += vdrop;

#endif
			/* add it to accumulated shadow on 'l'		*/
			OrRectRegion(tmprgn, &predrect);
		}

		/* all shadows on 'l' should actually be on 'l'	*/
		AndRectRegion(tmprgn, &lrect);

		/* add shadows on l to shadows on layers behind l	*/
		OrRegionRegion(tmprgn, rgn);
	}

	UnlockLayerInfo(window->WLayer->LayerInfo);
	DisposeRegion(tmprgn);
}

/* can use dale's ClearRectRegion	*/
#if 0
notRectRegion(rgn, rect)
struct Region		*rgn;
struct Rectangle	*rect;
{
	OrRectRegion(rgn, rect);
	XorRectRegion(rgn, rect);
}
#endif

/* this doesn't get use anymore	*/
#define GOODRECT(r)	(((r)->MinX < (r)->MaxX) && ((r)->MinY < (r)->MaxY))

fillRegion(rp, rgn)
struct RastPort	*rp;
struct Region	*rgn;
{
	register struct	RegionRectangle	*rgnrect;
	register struct	Rectangle	*bds;

	register int		minx;		/* offsets for all region rectangles	*/
	register int		miny;

	minx = rgn->bounds.MinX;
	miny = rgn->bounds.MinY;
	rgnrect = rgn->RegionRectangle;

	while (rgnrect)
	{
		bds = &rgnrect->bounds;
		RectFill(rp,
			(LONG)minx + bds->MinX,
			(LONG) miny + bds->MinY, 
			(LONG) minx + bds->MaxX,
			(LONG) miny + bds->MaxY);

		rgnrect = rgnrect->Next;
	}
}

translateRect(r, dx, dy)
register struct	Rectangle *r;
register int		dx;
register int		dy;
{
	r->MinX	+= dx;
	r->MaxX	+= dx;
	r->MinY	+= dy;
	r->MaxY	+= dy;
}

screenRect(s, r)
register struct Screen		*s;
register struct Rectangle	*r;
{
	r->MaxX = (r->MinX = s->LeftEdge) + s->Width - 1;
	r->MaxY = (r->MinY = s->TopEdge) + s->Height - 1;
}

windowRect(w, r)
register struct Window		*w;
register struct Rectangle	*r;
{
	r->MaxX = (r->MinX = w->LeftEdge) + w->Width - 1;
	r->MaxY = (r->MinY = w->TopEdge) + w->Height - 1;
}

struct  Window * getNewWind(left, top, width, height, flg, iflg)
SHORT   left, top, width, height;
ULONG   flg, iflg;
{
	extern struct Gadget darkness;

    struct  Window  *OpenWindow();
    struct  NewWindow   nw;

    nw.LeftEdge =   (SHORT) left;
    nw.TopEdge  =   (SHORT) top;
    nw.Width    =   (SHORT) width;
    nw.Height   =   (SHORT) height;
    nw.DetailPen    =   (UBYTE) -1;
    nw.BlockPen =   (UBYTE) -1;
    nw.IDCMPFlags   =   (ULONG) iflg;

    nw.Flags    =   (ULONG) flg;

    nw.FirstGadget  =   (struct Gadget *)   &darkness;
    nw.CheckMark    =   (struct Image *)    NULL;
    nw.Title    =   (UBYTE *)   wtitle;
    nw.Screen   =   (struct Screen *)   NULL;
    nw.BitMap   =   (struct BitMap *)   NULL;
    nw.MinWidth =   (SHORT) 50;
    nw.MinHeight=   (SHORT) 30;
    /* work around bug  */
    nw.MaxWidth =   (SHORT) nw.Width;
    nw.MaxHeight    =   (SHORT) nw.Height;
    nw.Type     =   (USHORT) WBENCHSCREEN;

    return ((struct Window *) OpenWindow(&nw));
}


#define DARKER(c)  (((long) (((c) & 0xF) * (darkpot)) ) >> 16)

shadowColors(s)
struct Screen *s;
{
	register int	i;
	register ULONG	rgb;
	ULONG	red, green, blue;
	struct	ColorMap	*cm;
	USHORT	darkpot;

	darkpot = darkinfo.HorizPot;

	cm = s->ViewPort.ColorMap;

	for (i = 0; i < 4; ++i)
	{
		/* get three colors	*/
		rgb		= GetRGB4(cm, (LONG) i);

#if	0
		rgb = ~rgb;	/* wild colors, not wild enough	*/
#endif

		blue	= DARKER(rgb);
		green	= DARKER(rgb >>= 4);
		red		= DARKER(rgb >> 4);
	    SetRGB4(&s->ViewPort, (LONG) i + 4,(LONG) red,(LONG) green,(LONG) blue);
	}
}
