In the remainder of this chapter, we’ll implement the MathAnimation view, as well as a special surprise.
Insert the statements shown here in bold into the
MathAnimation.h
file:
#import <Cocoa/Cocoa.h> @interface MathAnimation : NSView { float theta; // Current rotation for the star float fraction; // Current intensity for the pulsing icon float ddelta; // Density delta for icon NSMutableAttributedString *str; // "MathPaper" string NSImage *image; NSTimer *timer; // Our timer } - (IBAction)tick:(id)sender; @end
The purpose of these six instance variables is easier to understand
with the About panel from Figure 14-1 in mind, so
you might want to go back and take a quick look at it. The first
three variables, theta
,
fraction
, and ddelta
, will be
used to keep track of the animation’s rotation,
intensity, and density, respectively. They will be updated by NSTimer
methods as they are invoked. The NSMutableAttributedString variable
str
will be used to hold the attributed
“MathPaper” text.
We’ll create this string when the MathAnimation view
is first initialized, and then draw it on the view each time that we
are asked to draw the view. The same is true for the image held in
the NSImage instance variable, image
. Finally, the
NSTimer variable timer
will be used to keep track
of the timer. This timer is created when the window is exposed.
We need to keep the instance variable so
we can invalidate the timer when the window is closed.
Edit the file MathAnimation.m
and add the #define
statements and the awakeFromNib
method shown here:
@implementation MathAnimation#define FPS 30.0 // Frames per second - (void)awakeFromNib { // Set up the attributed text NSFont *font = [NSFont fontWithName:@"Helvetica" size:36.0]; NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; [attrs setObject:font forKey:NSFontAttributeName]; [attrs setObject:[NSColor greenColor] forKey:NSForegroundColorAttributeName]; str = [ [NSMutableAttributedString alloc] initWithString:@"MathPaper" attributes:attrs]; ddelta = (1.0 / FPS) / 5.0; theta = 0.0; image = [[NSImage imageNamed:@"PaperIcon"] retain]; [ [NSNotificationCenter defaultCenter] addObserver:self selector:@selector(start:) name:NSWindowDidBecomeKeyNotification object:[self window]]; [ [NSNotificationCenter defaultCenter] addObserver:self selector:@selector(stop:) name:NSWindowWillCloseNotification object:[self window] ]; }
The awakeFromNib method is
automatically invoked when the MathAnimation view is first unpacked
from AboutPanel.nib
. The first half of the
method sets up the attributed text that will draw
“MathPaper” in the window. The text
is drawn in 36-point green Helvetica, which is positively ugly! The
variable ddelta
is the increment that will be
added to the variable fraction
each time the timer
clicks. We initialize theta
to the initial
rotation angle for the star. The NSImage object
image
is set to be the same image that we use for
the document icon. Finally, we add two
“observers” for the default
notification center. The first observer will be
self
— that is, the MathAnimation
view — and will receive the start: message when the window in which it
resides becomes the key window. The second observer will cause the
stop: message to be sent to the
MathAnimation view when the window is closed.
Notifications
are similar to delegate messages,
except that any number of objects can receive the same notification.
This completes the initialization logic.
Next, we implement the start: and stop: methods that were referenced earlier:
Insert the start: and stop: methods in
MathAnimation.m
:
- (void)start:(void *)userInfo { if (!timer) { timer = [NSTimer scheduledTimerWithTimeInterval:1.0/FPS target:self selector:@selector(tick:) userInfo:0 repeats:YES]; } } - (void)stop:(void *)userInfo { if (timer) { [timer invalidate]; timer = nil; // No need to release; we did not retain the // NSTimer object because scheduled timers are // automatically retained by the AppKit } }
The start: method starts the NSTimer
if it does not already exist. The stop:
method stops the timer and then resets the
timer
instance variable to 0. This is necessary so
that the timer will be recreated if the window is exposed again.
Insert the following updated drawRect:
method near the end of
MathAnimation.m
:
- (void)drawRect:(NSRect)rect { float x,y,t2; NSBezierPath *oval; // Paint the background white [ [NSColor whiteColor] set]; NSRectFill([self bounds]); // Draw the name "MathPaper"; str was set in awakeFromNib [str drawAtPoint:NSMakePoint(20,50)]; // Draw those cool straight black lines [ [NSColor blackColor] set]; for (x=0; x<50; x+=10) { [NSBezierPath setDefaultLineWidth:(50-x)/10.0]; [NSBezierPath strokeLineFromPoint:NSMakePoint(20+x,50-x) toPoint:NSMakePoint(300.0,50-x)]; } // Put the PaperIcon in the upper-left corner of the panel [image compositeToPoint: NSMakePoint(10.0, [self bounds].size.height-128.0) operation:NSCompositeSourceOver fraction:fraction]; // Make a path for the star x = [self bounds].size.width * .75; y = [self bounds].size.height * .75; oval = [NSBezierPath bezierPath]; [oval moveToPoint: NSMakePoint(x + cos(theta)*50, y + sin(theta) * 50)]; for (t2=0; t2<=2*M_PI+.1; t2+=M_PI*.5) { [oval curveToPoint:NSMakePoint(x + cos(theta+t2)*50, y + sin(theta+t2)*50) controlPoint1:NSMakePoint(x,y) controlPoint2:NSMakePoint(x,y)]; } [ [NSColor blackColor] set]; [oval stroke]; }
This method may seem complicated, but it is actually quite straightforward. This is what it does:
Paints the entire view (background) white
Draws the word “MathPaper”
Draws those five black lines
Composites the PaperIcon in the upper-left corner
Creates an NSBezierPath for the star, sets the drawing color to black, and strokes (actually draws) the star
M_PI is the ANSI C-defined constant for pi, which is the ratio of a circumference of a circle to its radius.
There is only one method left to create — the tick: method that supports our animation:
Insert the statements shown here in bold into the implementation of
the tick: method in
MathAnimation.m
:
- (IBAction)tick:(id)sender { theta += (2.0 * M_PI / FPS) / 2.0; // Spin every 2 seconds fraction += ddelta; // Pulse every 5 seconds if (fraction<0 || fraction>1) { // Do we need to reverse pulse? ddelta = -ddelta; fraction += ddelta; } [self setNeedsDisplay:YES]; }
All this method does is increment the theta
and
fraction
variables and then display the updated
view. If fraction
is out of range, that means that
it has gone too far, and it’s time to reverse
direction. After the instance variables are updated, [self display] causes focus to be locked on
the MathAnimation view and drawRect:
to be called.
Build and run MathPaper.
Choose MathPaper → About MathPaper and admire your animation.
Quit MathPaper.
Pretty cool, eh? But you haven’t seen anything yet!
What good would an About panel be without an Easter egg?[37] This Easter egg will show up when the MathPaper → About MathPaper menu command is chosen with either the Shift or Option (Alt) modifier key held down. To implement this, we’ll need to modify our MathApplication class so that it can detect this menu/key combination and pass the information along to our MathAnimation class. Then we’ll need to modify the MathAnimation class to detect the fact that it should display an Easter Egg, and then to actually display it.
We will store an easterEgg
flag in the
MathApplication class to indicate whether a modifier key was held
down when the user chose MathPaper → About
MathPaper.
It’s actually fairly easy to find out if the Option key is down — just query the current event! We’ll do that in the following modification to the MathApplication implementation:
Insert the lines shown here in bold into the
MathApplication.m
file:
@implementation MathApplication - (void)orderFrontStandardAboutPanel:(id)sender { if (aboutPanel == nil) { [NSBundle loadNibNamed:@"AboutPanel" owner:self]; } easterEgg = ([ [self currentEvent] modifierFlags] & (NSShiftKeyMask | NSAlternateKeyMask)) != 0; [aboutPanel makeKeyAndOrderFront:self]; } -(BOOL)doEasterEgg { return easterEgg; } @end
To finish this off, we need to modify the MathAnimation class to
check the value of the easterEgg
flag and act
accordingly:
Insert the following #import
directive near the
top of the MathAnimation.m
file:
#import "MathApplication.h"
Replace the first statement in the tick:
method in the MathAnimation.m
file
with the statements shown here in bold:
- (IBAction)tick:(id)sender { if ([ ((MathApplication *)NSApp) doEasterEgg] ) { theta -= (4.0 * M_PI / FPS) / 2.0; // Spin reverse faster } else { theta += (2.0 * M_PI / FPS) / 2.0; // Spin every 2 seconds } fraction += ddelta; // Pulse every 5 seconds if (fraction<0 || fraction>1) { ddelta = -ddelta; fraction += ddelta; } [self display]; }
Build and run MathPaper.
Hold down the Option key and choose MathPaper → About MathPaper, then admire your Easter Egg!
Quit MathPaper.
[37] An Easter egg is an undocumented frill inserted by playful programmers into shipping programs, often without the knowledge of their managers. In this context, it’s a surprise that users find in About panels by selecting key combinations or by other arcane methods. It has nothing to do with an egg!
18.188.151.107