/*
 * EKInfoManager
 * description: a generic object for having a spiffy info panel
 * history:
 *	5/10/93 [Erik Kay] - created
 *	5/20/93 [Erik Kay] - dissolve from author tiff to app tiff
 *	5/23/93 [Erik Kay] - added other animation effects:
 *			random, checker, circle, slide
 * bugs:
 *	this should be able to deal with multiple authors
 * todo:
 *	could always add a few more animation effects:
 *		barn door, 4 triangles, rectangle, etc.
 */

#import "EKInfoManager.h"

//! <<<WARNING>>>
//! THIS VARIABLE MUST BE DEFINED ELSEWHERE IN THE APP!
//! define it to be any string you want, but it must be defined
//! I've found that vers_string works well here. - EK
extern char VERS_NUM[];

// timed entry function for animating to author TIFF
void animateToAuthor (DPSTimedEntry te, double now, void *data)
{
    static int count = 0;
    [(id)data animateToAuthor:++count];
    if (count > [(id)data animationSteps]) {
	count = 0;
	[(id)data finishAnimation];
    }
}

// timed entry function for animating to app TIFF
void animateToApp (DPSTimedEntry te, double now, void *data)
{
    static int count = 0;
    [(id)data animateToApp:++count];
    if (count > [(id)data animationSteps]) {
	count = 0;
	[(id)data finishAnimation];
    }
}

@implementation EKInfoManager

- init
{
    [super init];

    // you've got to have an image named "author.tiff" in your app wrapper
    authorTIFF = [NXImage findImageNamed:"author"];
    //! you don't need an image named "app.tiff", this will just grab the
    //! icon you're using for your app tile
    appTIFF = [NXImage findImageNamed:"app"];

    te = NULL;
    return self;
}

// load the info panel from the nib if not loaded already
- loadInfoPanel:sender
{
    NXSize sz;
    if (!infoPanel) {
    	// load the nib
	[NXApp loadNibSection:"Info.nib" owner:self withNames:NO];
	
	// initialize the images
	[authorTIFF getSize:&sz];
	workTIFF = [[NXImage alloc] initSize:&sz];
	
	// display the version string in the version text field
	[versionNumber setStringValue:VERS_NUM];
    }
    return self;
}

// show the info panel
- showInfoPanel:sender
{
    NXPoint pt = {0,0};
    
    // load it if not loaded already
    [self loadInfoPanel:sender];
    
    // the button starts out as the app icon
    currentImage = appTIFF;
    
    // draw it into the work area
    [workTIFF lockFocus];
    [currentImage composite:NX_SOVER toPoint:&pt];
    [workTIFF unlockFocus];
    
    // put it into the button
    [iconButton setImage:workTIFF];
    
    // bring the panel up
    [infoPanel makeKeyAndOrderFront:sender];
    return self; 
}

// In your info panel, put a transparent button behind the author's name
// then connect the button to this method
// This action changes the image to the author if it's not at the author
// already.
- switchToAuthor:sender
{
    if (!te && (currentImage != authorTIFF)) {
	te = DPSAddTimedEntry(0.1,animateToAuthor,
				(void *)self,NX_BASETHRESHOLD);
	currentImage = authorTIFF;
    }
    return self;
}

// In your info panel, put a transparent button behind your app's name
// then connect the button to this method
// This action changes the image to the app if it's not at the app
// already.
- switchToApp:sender
{
    if (!te && (currentImage != appTIFF)) {
	te = DPSAddTimedEntry(0.1,animateToApp,(void *)self,NX_BASETHRESHOLD);
	currentImage = appTIFF;
    }
    return self;
}

// In the info panel, hook up the button that the image is being drawn into
// to this action.  It will toggle the image back and forth.
- toggleButton:sender
{
    if (currentImage == appTIFF) {
	[self switchToAuthor:sender];
    } else {
	[self switchToApp:sender];
    }
    return self;
}

// Actually do the animation to the author tiff.
// The example implementation here just does a dissolve to the author tiff.
// This is a prime candidate for subclassing.  If you want to use a different
// effect, just subclass away.
- animateToAuthor:(int)i
{
    [self dissolveTo:authorTIFF step:i];
    return self;
}

// Actually do the animation to the app tiff.
// The example implementation here just does a dissolve to the app tiff.
// This is a prime candidate for subclassing.  If you want to use a different
// effect, just subclass away.
- animateToApp:(int)i
{
    [self dissolveTo:appTIFF step:i];
    return self;
}

// how many steps in the animation?

-(int)animationSteps
{
    return animationSteps;
}

- setAnimationSteps:(int)steps
{
    animationSteps = steps;
    return self;
}

// clean up
- finishAnimation
{
    DPSRemoveTimedEntry(te);
    te = NULL;
    return self;
}


//
// here are all of the various animation effects
//


// do dissolve animation: very simple stuff
- dissolveTo:image step:(int)i
{
    NXPoint pt = {0.0,0.0};
    float dissolve;
    
    dissolve = (float)i/(float)animationSteps;
    [workTIFF lockFocus];
    [image dissolve:dissolve toPoint:&pt];
    [workTIFF unlockFocus];
    [iconButton setImage:workTIFF];
    [iconButton display];
    [infoPanel flushWindow];
    return self;
}

#define LEFT 0
#define RIGHT 1
#define BOTTOM 2
#define UP 3

// slide an image in from a cycling direction

- slideTo:image step:(int)i
{
    static int direction = -1;
    
    if (i == 1) {
	direction++;
	if (direction > UP)
	    direction = LEFT;
    }
    [self slideTo:image step:i from:direction];
    return self;
}

// slide an image in from a specified direction

- slideTo:image step:(int)i from:(int)direction
{
    NXRect r;
    NXSize s;
    int width, height;
    NXPoint pt = {0.0,0.0};
    
    [image getSize:&s];
    width = i * (s.width / animationSteps);
    height = i * (s.height / animationSteps);
    switch (direction) {
	case LEFT:
	    NXSetRect(&r,s.width - width,0,width,s.height);
	    break;
	case RIGHT:
	    NXSetRect(&r,0,0,width,s.height);
	    pt.x = s.width - width;
	    break;
	case BOTTOM:
	    NXSetRect(&r,0,s.height - height,s.width,height);
	    break;
	case UP:
	    NXSetRect(&r,0,0,s.width,height);
	    pt.y = s.height - height;
	    break;
	default:
	    break;
    }
    [workTIFF lockFocus];
    [image composite:NX_COPY fromRect:&r toPoint:&pt];
    [workTIFF unlockFocus];
    [iconButton setImage:workTIFF];
    [iconButton display];
    [infoPanel flushWindow];
    return self;
}

// For all of the effects after this, they all use some basic compositing
// compositing tricks.  Using this technique, you can pretty much do any
// simple animation from one tiff to another with very little work.
// Here are the basic steps:
//	(1) create a new image of the same size of the target image
//	(2) in a completely opaque color, draw the pattern that defines your
//		effect on to the image (all of the real work is here)
// 	(3) do an NX_SIN composite of the target image on to the pattern image
// 	(4) NX_COPY the original image (the one being drawn over, not the
//		image being animated to) on to the workTIFF
//	(5) NX_SOVER the pattern image on to workTIFF
// 	(6) draw the workTIFF on to the button
// when commenting these functions, I'll just say which step they are

// do the random fill animation
- randomTo:image step:(int)i
{
    id img;
    NXSize imgSize;
    NXRect r, *rects;
    NXPoint pt = {0.0,0.0};
    int num, x, y, index, count;
    float percent;
    
    // step (1)
    [image getSize:&imgSize];
    img = [[NXImage alloc] initSize:&imgSize];
    
    // step (2) - this is pretty slow.  basically I'm drawing a random series
    //		of 1x1 rectangles on the image.  This gives the random
    //		"beam me up" effect
    [img lockFocus];
    PSsetgray(0.0);
    PSsetalpha(0.0);
    NXSetRect(&r,0,0,imgSize.width,imgSize.height);
    NXRectFill(&r);
    PSsetalpha(1.0);
    count = imgSize.width * imgSize.height;
    percent = i / (float)animationSteps;
    if (i <= animationSteps) {
	rects = malloc(sizeof(NXRect) * count);
	for (x = 0; x < imgSize.width; x++) {
	    for (y = 0; y < imgSize.height; y++) {
		index = x + y * imgSize.width;
		num = random() & 63; //! this is kind of dumb, vaguely size
				     //! dependant stuff here.
		if (num <= (63 * percent))
		    NXSetRect(&rects[index],x,y,1,1);
		else
		    NXSetRect(&rects[index],0,0,0,0);
	    }
	}
	NXRectFillList(rects,count);
	free(rects);
    } else {
	NXRectFill(&r);
    }

    // step (3)
    [image composite:NX_SIN toPoint:&pt];
    [img unlockFocus];
    
    // step (4)
    [workTIFF lockFocus];
    if (image == authorTIFF)
	[appTIFF composite:NX_COPY toPoint:&pt];
    else
	[authorTIFF composite:NX_COPY toPoint:&pt];

    // step (5)
    [img composite:NX_SOVER toPoint:&pt];
    [workTIFF unlockFocus];
    
    // step (6)
    [iconButton setImage:workTIFF];
    [iconButton display];
    [infoPanel flushWindow];
    [img free];
    return self;
}

// animate in a kind of 'checker board' style
- checkerTo:image step:(int)i
{
    NXRect rects[64], r;
    NXSize imgSize,rectSize;
    NXPoint pt = {0.0,0.0};
    float height,h;
    int x, y, index;
    id img;
    
    // step (1)
    [image getSize:&imgSize];
    img = [[NXImage alloc] initSize:&imgSize];

    // step (2) - we're building up a checkerboard of rectangles.  First the
    //		"black" squares, then the "red" squares.
    rectSize.width = imgSize.width / 8;
    rectSize.height = imgSize.height / 8;
    height = 2 * i * (rectSize.height / animationSteps);
    h = height - rectSize.height;
    for (x = 0; x < 8; x ++) {
	for (y = 0; y < 8; y ++) {
	    index = x + y * 8;
	    if (height > rectSize.height) {
		if ((x % 2) == (y % 2)) {
		    NXSetRect(&rects[index],x * rectSize.width,
		    	(y + 1) * rectSize.height - h,
			rectSize.width, h);
		} else {
		    NXSetRect(&rects[index],x * rectSize.width,
		    	y * rectSize.height,
			rectSize.width,rectSize.height);
		}
	    } else {
		if ((x % 2) != (y % 2)) {
		    NXSetRect(&rects[index],x * rectSize.width,
		    	y * rectSize.height,rectSize.width, height);
		} else {
		    NXSetRect(&rects[index],0,0,0,0);
		}
	    }
	}
    }
    [img lockFocus];
    PSsetgray(0.0);
    PSsetalpha(0.0);
    NXSetRect(&r,0,0,imgSize.width,imgSize.height);
    NXRectFill(&r);
    PSsetalpha(1.0);
    // here's where step (2) actually happens.  All of the previous was just
    // setup. :-)
    NXRectFillList(rects,64);
    
    // step (3)
    [image composite:NX_SIN toPoint:&pt];
    [img unlockFocus];
    
    // step (4)
    [workTIFF lockFocus];
    if (image == authorTIFF)
	[appTIFF composite:NX_COPY toPoint:&pt];
    else
	[authorTIFF composite:NX_COPY toPoint:&pt];

    // step (5)
    [img composite:NX_SOVER toPoint:&pt];
    [workTIFF unlockFocus];
    
    // step (6)
    [iconButton setImage:workTIFF];
    [iconButton display];
    [infoPanel flushWindow];
    [img free];
    return self;
}

// animate as a growing circle in the center
- circleTo:image step:(int)i
{
    NXSize sz;
    NXRect r;
    NXPoint center, pt = {0.0,0.0};
    float radius;
    id img;
    
    // step (1)
    [image getSize:&sz];
    img = [[NXImage alloc] initSize:&sz];

    // step (2) - just draw a circle
    [img lockFocus];
    PSsetgray(0.0);
    PSsetalpha(0.0);
    NXSetRect(&r,0,0,sz.width,sz.height);
    NXRectFill(&r);
    PSsetalpha(1.0);
    center.x = sz.width / 2;
    center.y = sz.height / 2;
    radius = sqrt(center.x * center.x + center.y * center.y);
    radius = i * (radius / animationSteps);
    PSarc(center.x,center.y,radius,0,360);
    PSfill();
    
    // step (3)
    [image composite:NX_SIN toPoint:&pt];
    [img unlockFocus];
    
    // step (4)
    [workTIFF lockFocus];
    if (image == authorTIFF)
	[appTIFF composite:NX_COPY toPoint:&pt];
    else
	[authorTIFF composite:NX_COPY toPoint:&pt];

    // step (5)
    [img composite:NX_SOVER toPoint:&pt];
    [workTIFF unlockFocus];
    
    // step (6)
    [iconButton setImage:workTIFF];
    [iconButton display];
    [infoPanel flushWindow];
    [img free];
    return self;
}

@end
