// BWSwoopView.m -- draw a cheesy swoopy trangular effect for BorkPad page turns

// Copyright (C) 2003 Borkware
// We use the BSD License.  Check out http://borkware.com/license

#import "BWSwoopView.h"

// the definition of the notification name that's posted when we
// complete the swooping animation

NSString *BWSwoopViewCompletedNotification = @"BWSwoopView Completed Swooping Notification";

// the swoop animation can be pretty expensive.  If this symbol is set to
// a non-zero value, timing information on a swoop run will be sent to
// standard out
#define PROFILE_SWOOP 0



@implementation BWSwoopView

// number of triangles visible at a time during the swooping process
#define MAX_SWOOPS	3

// if we're calculating run time, we need stuff from time.h.
#if PROFILE_SWOOP

#include <sys/time.h>

struct timeval start, end;  // start and end time

#endif // PROFILE_SWOOP


// handle timer firing.  In most cases, just invalidate ourselves and the
// drawSelf will draw the necessary triangles.
// When we've run out of triangles to draw, stop the timer, remove ourselves
// (leads to our eventual death), and then post a notification saying the
// animation is done.

- (void) handleTimer: (NSTimer *) theTimer
{
    // update which swoop we're dealing with
    currentSwoop++;

    if (currentSwoop >= (swoopCount + MAX_SWOOPS)) {

#if PROFILE_SWOOP
	gettimeofday (&end, NULL);
	double fstart, fend;
	fstart = (start.tv_sec * 1000000.0 + start.tv_usec) / 1000000.0;
	fend = (end.tv_sec * 1000000.0 + end.tv_usec) / 1000000.0;
	NSLog (@"swoop animation took %g seconds", fend - fstart);
#endif

	[self removeFromSuperview];
	[timer invalidate];

	// let the rest of the world know we're done
	[[NSNotificationCenter defaultCenter] 
	    postNotificationName: BWSwoopViewCompletedNotification
	    object: self];

    } else {
	[self setNeedsDisplay: YES];
    }
    
} // handleTimer



// Pre-calculate the coordinates of the triangles that will be drawn.
// stick the calculated coordinates into the three arrays that are
// passed in

static void fillPointArrays (int count, NSRect rect,
			     NSPoint *upperLeft, NSPoint *upperRight,
			     NSPoint *lowerRight)
{
    NSPoint p1, p2, p3;
    float deltaH, deltaV;
    int i;

// calculated by eye so that the swooping starts in the right place relative
// to the page control in the window.  Needless to say, this limits this
// class's usefulnes to just BorkPad

#define FUDGE_FACTOR 48

    rect.origin = NSZeroPoint;

    // the triangles are evenly distributed.  We could probably make it
    // a cooler effect by compressing the start of the swoop range.
    // But this is pretty easy to do, and gets the point across
    // visually

    deltaH = (rect.size.width - FUDGE_FACTOR) / (count - 1);
    deltaV = (rect.size.height - FUDGE_FACTOR) / (count - 1);

    // start point

    p1 = NSMakePoint (rect.origin.x, rect.origin.y + FUDGE_FACTOR);
    p2 = NSMakePoint (rect.origin.x + FUDGE_FACTOR, rect.origin.y + FUDGE_FACTOR);
    p3 = NSMakePoint (rect.origin.x + FUDGE_FACTOR, rect.origin.y);

    // fill in the rest of the triangles.  
    // save the last one til after the loop
    for (i = 0; i < count - 1; i++) {
	upperLeft[i] = p1;
	upperRight[i] = p2;
	lowerRight[i] = p3;

	p1.y += deltaV;
	p2.x += deltaH; p2.y += deltaV;
	p3.x += deltaH;
    }

    // and end up right at the edge of the rect
    p1 = NSMakePoint (rect.origin.x,
		      rect.origin.y + rect.size.height - 1.0);
    p2 = NSMakePoint (rect.origin.x + rect.size.width - 1.0,
		      rect.origin.y + rect.size.height - 1.0);
    p3 = NSMakePoint (rect.origin.x + rect.size.width - 1.0,
		      rect.origin.y);

    upperLeft[i] = p1;
    upperRight[i] = p2;
    lowerRight[i] = p3;

} // fillPointArrays



// if we're swooping backwards (from upper right down to lower left),
// just reverse the coordinates in the array

void reverseArray (NSPoint *array, int count)
{
    NSPoint *scan, *back;

    scan = array; 		// walk forward from the start
    back = array + count - 1; 	// walk backwards from the end

    while (scan < back) { 	// until they cross.  then we're done.
	NSPoint temp;

	temp = *scan;
	*scan = *back;
	*back = temp;

	scan++;	back--;
    }

} // reverseArray



// our initializer

- (id) initWithFrame: (NSRect) frame   swoopForwards: (BOOL) swoopForwards
{
    if (self = [super initWithFrame: frame]) {
	int count = 12; // 12 triangles looks pretty good.
			// although we should probably up the count for bigger
			// frames

	swoopCount = count;

	// make a path to re-use.  Also can set any particular path attributes
	// once
	path = [[NSBezierPath alloc] init];
	[path setLineWidth: 1.0];

	// allocate the point arrays and fill them
	upperLeft = malloc (sizeof(NSPoint) * swoopCount);
	upperRight = malloc (sizeof(NSPoint) * swoopCount);
	lowerRight = malloc (sizeof(NSPoint) * swoopCount);

	fillPointArrays (swoopCount, frame, upperLeft, upperRight, lowerRight);

	// handle the case of swooping backwards

	if (!swoopForwards) {
	    reverseArray (upperLeft, swoopCount);
	    reverseArray (upperRight, swoopCount);
	    reverseArray (lowerRight, swoopCount);
	}

	// start the animation timer
	timer = [NSTimer scheduledTimerWithTimeInterval: 0.01
			 target: self
			 selector: @selector(handleTimer:)
			 userInfo: nil
			 repeats: YES];

#if PROILE_SWOOP
	// record our start time (with sub-second granularity. woo!)
	gettimeofday (&start, NULL);
#endif
    }

    return (self);

} // initWithFrame



// clean up our mess

- (void) dealloc
{
    [path release];

    free (upperLeft);
    free (upperRight);
    free (lowerRight);

    [super dealloc];

} // dealloc



// draw the rectangle.  This typically gets erased for us by our
// owning window, so it doesn't explicilty clear itself out

- (void) drawRect: (NSRect) rect
{
    // draw the last three triangles

    int start, stop;
    start = MAX(currentSwoop - MAX_SWOOPS, 0);
    stop = MIN(currentSwoop, swoopCount);

    [[NSColor darkGrayColor] set];

    int i;
    for (i = start; i < stop; i++) {
	[path removeAllPoints];

	[path moveToPoint: upperLeft[i]];
	[path lineToPoint: upperRight[i]];
	[path lineToPoint: lowerRight[i]];
	[path closePath];

	[path stroke];
    }

} // drawRect


@end // BWSwoopView


/* $Id: BWSwoopView.m,v 1.6 2003/01/02 21:29:21 markd Exp $ */

