libs-gui/Source/NSAnimation.m
David Chisnall b4e3915c33 Turn release messages sent to autorelease pools into drain messages. No
functionality change in non-GC mode, in GC mode it invokes a quick GC pass to
try to delete the short-lived objects.

Also deleted some [pool release] lines just before exit() or return-from-main
statements.  These cause objects to be swapped in and destructors to be run to
no benefit (the OS will reclaim this memory without requiring stuff to be
swapped in when the process exits).



git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@33146 72102866-910b-0410-8b05-ffd578937521
2011-05-27 12:42:37 +00:00

1536 lines
42 KiB
Objective-C

/*
NSAnimation.m
Created by Dr. H. Nikolaus Schaller on Sat Mar 06 2006.
Copyright (c) 2007 Free Software Foundation, Inc.
Author: Xavier Glattard (xgl) <xavier.glattard@online.fr>
This file used to be part of the mySTEP Library.
This file now is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import <Foundation/NSDate.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSValue.h>
#import <GNUstepBase/GSLock.h>
#import "AppKit/NSAnimation.h"
#import "AppKit/NSApplication.h"
// needed by NSViewAnimation
#import "AppKit/NSView.h"
#import "AppKit/NSWindow.h"
#include <math.h>
NSString* NSAnimationBlockingRunLoopMode = @"NSAnimationBlockingRunLoopMode";
/*===================*
* NSAnimation class *
*===================*/
#define GSI_ARRAY_NO_RETAIN
#define GSI_ARRAY_NO_RELEASE
#define GSIArrayItem NSAnimationProgress
#include <GNUstepBase/GSIArray.h>
// 'reasonable value' ?
#define GS_ANIMATION_DEFAULT_FRAME_RATE 25.0
static NSArray* _NSAnimationDefaultRunLoopModes;
static inline void
_GSBezierComputeCoefficients(_GSBezierDesc *b)
{
b->a[0] = b->p[0];
b->a[1] =-3.0*b->p[0] + 3.0*b->p[1];
b->a[2] = 3.0*b->p[0] - 6.0*b->p[1] + 3.0*b->p[2];
b->a[3] =- b->p[0] + 3.0*b->p[1] - 3.0*b->p[2] + b->p[3];
b->areCoefficientsComputed = YES;
}
static inline float
_GSBezierEval(_GSBezierDesc *b, float t)
{
if (!b->areCoefficientsComputed)
_GSBezierComputeCoefficients(b);
return b->a[0] + t * (b->a[1] + t * (b->a[2] + t * b->a[3]));
}
static inline float
_GSBezierDerivEval(_GSBezierDesc *b, float t)
{
if (!b->areCoefficientsComputed)
_GSBezierComputeCoefficients(b);
return b->a[1] + t * (2.0 * b->a[2] + t * 3.0 * b->a[3]);
}
static inline void
_GSRationalBezierComputeBezierDesc(_GSRationalBezierDesc *rb)
{
unsigned i;
for (i = 0; i < 4; i++)
rb->n.p[i] = (rb->d.p[i] = rb->w[i]) * rb->p[i];
_GSBezierComputeCoefficients(&rb->n);
_GSBezierComputeCoefficients(&rb->d);
rb->areBezierDescComputed = YES;
}
static inline float
_GSRationalBezierEval(_GSRationalBezierDesc *rb, float t)
{
if (!rb->areBezierDescComputed)
_GSRationalBezierComputeBezierDesc(rb);
return _GSBezierEval(&(rb->n), t) / _GSBezierEval(&(rb->d), t);
}
static inline float
_GSRationalBezierDerivEval(_GSRationalBezierDesc *rb, float t)
{
float h;
if (!rb->areBezierDescComputed)
_GSRationalBezierComputeBezierDesc(rb);
h = _GSBezierEval(&(rb->d), t);
return (_GSBezierDerivEval(&(rb->n), t) * h
- _GSBezierEval (&(rb->n), t) * _GSBezierDerivEval(&(rb->d), t))
/ (h*h);
}
static
_NSAnimationCurveDesc _gs_animationCurveDesc[] =
{
// easeInOut : endGrad = startGrad & startGrad <= 1/3
{ 0.0,1.0, 1.0/3,1.0/3 , {{2.0,2.0/3,2.0/3,2.0}} },
// easeIn : endGrad = 1/startGrad & startGrad >= 1/6
{ 0.0,1.0, 0.25,4.0 , {{4.0,3.0,2.0,1.0}} },
// easeOut : endGrad = 1/startGrad & startGrad <= 6
{ 0.0,1.0, 4.0 ,0.25, {{1.0,2.0,3.0,4.0}} },
// linear (not used)
{ 0.0,1.0, 1.0 ,1.0 , {{1.0,1.0,1.0,1.0}} },
// speedInOut: endGrad = startGrad & startGrad >=3
{ 0.0,1.0, 3.0 ,3.0 , {{2.0/3,2.0,2.0,2.0/3}} }
};
/* Translate the NSAnimationCurveDesc data (start/end points and start/end
* gradients) to GSRBezier data (4 control points), then evaluate it.
*/
static inline float
_gs_animationValueForCurve(_NSAnimationCurveDesc *c, float t, float t0)
{
if (!c->isRBezierComputed)
{
c->rb.p[0] = c->s;
c->rb.p[1] = c->s + (c->sg*c->rb.w[0]) / (3*c->rb.w[1]);
c->rb.p[2] = c->e - (c->eg*c->rb.w[3]) / (3*c->rb.w[2]);
c->rb.p[3] = c->e;
_GSRationalBezierComputeBezierDesc (&c->rb);
c->isRBezierComputed = YES;
}
return _GSRationalBezierEval ( &(c->rb), (t-t0) / (1.0-t0) );
}
@interface NSAnimation (PrivateNotificationCallbacks)
- (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification;
- (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification;
@end
@interface NSAnimation (Private)
- (void) _gs_didReachProgressMark: (NSAnimationProgress)progress;
- (void) _gs_startAnimationInOwnLoop;
- (void) _gs_startThreadedAnimation;
- (_NSAnimationCurveDesc*) _gs_curveDesc;
- (NSAnimationProgress) _gs_curveShift;
@end
NSComparisonResult
nsanimation_progressMarkSorter(NSAnimationProgress first, NSAnimationProgress second)
{
float diff = first - second;
return (NSComparisonResult)(diff / fabs(diff));
}
/* Thread locking/unlocking support macros.
* _isThreaded flag is an ivar that records whether the
* NSAnimation is running in thread mode.
* __gs_isLocked flag is local to each method and records
* whether the thread is locked and must be locked before
* the method exits.
* Both are needed because _isThreaded is reset when the
* NSAnimation stops : that may happen at any time between
* a lock/unlock pair.
*/
#define _NSANIMATION_LOCKING_SETUP \
BOOL __gs_isLocked = NO;
#define _NSANIMATION_LOCK \
if (_isThreaded) \
{ \
NSAssert(__gs_isLocked == NO, NSInternalInconsistencyException); \
NSDebugMLLog(@"NSAnimationLock",\
@"LOCK %@", [NSThread currentThread]);\
[_isAnimatingLock lock]; \
__gs_isLocked = YES; \
}
#define _NSANIMATION_UNLOCK \
if (__gs_isLocked) \
{ \
/* NSAssert(__gs_isLocked == YES, NSInternalInconsistencyException); */ \
NSDebugMLLog(@"NSAnimationLock",\
@"UNLOCK %@", [NSThread currentThread]);\
__gs_isLocked = NO; \
[_isAnimatingLock unlock]; \
}
@implementation NSAnimation
+ (void) initialize
{
unsigned i;
for (i = 0; i < 5; i++) // compute Bezier curve parameters...
_gs_animationValueForCurve(&_gs_animationCurveDesc[i], 0.0, 0.0);
_NSAnimationDefaultRunLoopModes
= [[NSArray alloc] initWithObjects:
NSDefaultRunLoopMode,
NSModalPanelRunLoopMode,
NSEventTrackingRunLoopMode,
nil];
}
- (void) addProgressMark: (NSAnimationProgress)progress
{
_NSANIMATION_LOCKING_SETUP;
if (progress < 0.0) progress = 0.0;
if (progress > 1.0) progress = 1.0;
_NSANIMATION_LOCK;
if (GSIArrayCount(_progressMarks) == 0)
{ // First mark
GSIArrayAddItem (_progressMarks,progress);
NSDebugMLLog (@"NSAnimationMark",
@"Insert 1st mark for %f (next:#%d)",
progress, _nextMark);
_nextMark = (progress >= [self currentProgress])? 0 : 1;
}
else
{
unsigned index;
index = GSIArrayInsertionPosition (_progressMarks,
progress,
&nsanimation_progressMarkSorter);
if (_nextMark < GSIArrayCount(_progressMarks))
if (index <= _nextMark
&& progress < GSIArrayItemAtIndex(_progressMarks,_nextMark))
_nextMark++;
GSIArrayInsertItem (_progressMarks,progress,index);
NSDebugMLLog (@"NSAnimationMark",
@"Insert mark #%d/%d for %f (next:#%d)",
index,GSIArrayCount(_progressMarks),progress,_nextMark);
}
_isCachedProgressMarkNumbersValid = NO;
_NSANIMATION_UNLOCK;
}
- (NSAnimationBlockingMode) animationBlockingMode
{
NSAnimationBlockingMode m;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
m = _blockingMode;
_NSANIMATION_UNLOCK;
return m;
}
- (NSAnimationCurve) animationCurve
{
NSAnimationCurve c;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
c = _curve;
_NSANIMATION_UNLOCK;
return c;
}
- (void) clearStartAnimation
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSAnimationProgressMarkNotification
object: _startAnimation];
[_startAnimation removeProgressMark: _startMark];
_startAnimation = nil;
_NSANIMATION_UNLOCK;
}
- (void) clearStopAnimation
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: NSAnimationProgressMarkNotification
object: _stopAnimation];
[_stopAnimation removeProgressMark: _stopMark];
_stopAnimation = nil;
_NSANIMATION_UNLOCK;
}
- (NSAnimationProgress) currentProgress
{
NSAnimationProgress p;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
p = _currentProgress;
_NSANIMATION_UNLOCK;
return p;
}
- (float) currentValue
{
float value;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
if (_delegate_animationValueForProgress)
{ // method is cached (the animation is running)
NSDebugMLLog (@"NSAnimationDelegate",
@"[delegate animationValueForProgress] (cached)");
value = (*_delegate_animationValueForProgress)
(GS_GC_UNHIDE (_currentDelegate),
@selector (animation:valueForProgress:),
self, _currentProgress);
}
else // method is not cached (the animation did not start yet)
if ( _delegate != nil
&& [GS_GC_UNHIDE (_delegate) respondsToSelector:
@selector (animation:valueForProgress:)] )
{
NSDebugMLLog (@"NSAnimationDelegate",
@"[delegate animationValueForProgress]");
value = [GS_GC_UNHIDE (_delegate) animation: self
valueForProgress: _currentProgress];
}
else // default -- FIXME ??
/* switch (_curve)
{
case NSAnimationEaseInOut:
case NSAnimationEaseIn:
case NSAnimationEaseOut:
case NSAnimationSpeedInOut:*/
value = _gs_animationValueForCurve (
&_curveDesc, _currentProgress, _curveProgressShift
);
/* break;
case NSAnimationLinear:
value = _currentProgress; break;
}*/
_NSANIMATION_UNLOCK;
return value;
}
- (id) delegate
{
id d;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
d = (_delegate == nil)? nil : GS_GC_UNHIDE (_delegate);
_NSANIMATION_UNLOCK;
return d;
}
- (NSTimeInterval) duration
{
NSTimeInterval d;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
d = _duration;
_NSANIMATION_UNLOCK;
return d;
}
- (float) frameRate
{
float f;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
f = _frameRate;
_NSANIMATION_UNLOCK;
return f;
}
- (id) initWithDuration: (NSTimeInterval)duration
animationCurve: (NSAnimationCurve)curve
{
if ((self = [super init]))
{
if (duration<=0.0)
[NSException raise: NSInvalidArgumentException
format: @"%@ Duration must be > 0.0 (passed: %f)",self,duration];
_duration = duration;
_frameRate = GS_ANIMATION_DEFAULT_FRAME_RATE;
_curve = curve;
_curveDesc = _gs_animationCurveDesc[_curve];
_curveProgressShift = 0.0;
_currentProgress = 0.0;
_progressMarks = NSZoneMalloc ([self zone], sizeof(GSIArray_t));
GSIArrayInitWithZoneAndCapacity (_progressMarks, [self zone], 16);
_cachedProgressMarkNumbers = NULL;
_cachedProgressMarkNumberCount = 0;
_isCachedProgressMarkNumbersValid = NO;
_nextMark = 0;
_startAnimation = _stopAnimation = nil;
_startMark = _stopMark = 0.0;
_blockingMode = NSAnimationBlocking;
_animator = nil;
_isANewAnimatorNeeded = YES;
_delegate = nil;
_delegate_animationDidReachProgressMark =
(void (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
_delegate_animationValueForProgress =
(float (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
_delegate_animationDidEnd =
(void (*)(id,SEL,NSAnimation*)) NULL;
_delegate_animationDidStop =
(void (*)(id,SEL,NSAnimation*)) NULL;
_delegate_animationShouldStart =
(BOOL (*)(id,SEL,NSAnimation*)) NULL;
_isThreaded = NO;
_isAnimatingLock = [GSLazyRecursiveLock new];
}
return self;
}
- (id) copyWithZone: (NSZone*)zone
{
NSAnimation *c = (NSAnimation*)NSCopyObject (self, 0, zone);
c->_progressMarks = GSIArrayCopyWithZone(_progressMarks, zone);
c->_animator = nil;
c->_isANewAnimatorNeeded = YES;
c->_isAnimatingLock = [GSLazyRecursiveLock new];
return c;
}
- (void) dealloc
{
[self stopAnimation];
GSIArrayEmpty(_progressMarks);
NSZoneFree([self zone], _progressMarks);
if (_cachedProgressMarkNumbers != NULL)
{
unsigned i;
for (i = 0; i < _cachedProgressMarkNumberCount; i++)
RELEASE(_cachedProgressMarkNumbers[i]);
NSZoneFree([self zone], _cachedProgressMarkNumbers);
}
[self clearStartAnimation];
[self clearStopAnimation];
TEST_RELEASE(_animator);
RELEASE(_isAnimatingLock);
[super dealloc];
}
- (BOOL) isAnimating
{
BOOL f;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
f = (_animator != nil) ? [_animator isAnimationRunning] : NO;
_NSANIMATION_UNLOCK;
return f;
}
- (NSArray*) progressMarks
{
NSNumber **cpmn;
unsigned count;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
count = GSIArrayCount(_progressMarks);
if (!_isCachedProgressMarkNumbersValid)
{
unsigned i;
if (_cachedProgressMarkNumbers != NULL)
{
for (i = 0; i < _cachedProgressMarkNumberCount; i++)
RELEASE(_cachedProgressMarkNumbers[i]);
_cachedProgressMarkNumbers =
(NSNumber**)NSZoneRealloc([self zone], _cachedProgressMarkNumbers,
count * sizeof(NSNumber*));
}
else
{
_cachedProgressMarkNumbers =
(NSNumber**)NSZoneMalloc([self zone], count * sizeof(NSNumber*));
}
for (i = 0; i < count; i++)
{
_cachedProgressMarkNumbers[i] =
[NSNumber numberWithFloat: GSIArrayItemAtIndex (_progressMarks,i)];
}
_cachedProgressMarkNumberCount = count;
_isCachedProgressMarkNumbersValid = YES;
}
cpmn = _cachedProgressMarkNumbers;
_NSANIMATION_UNLOCK;
return [NSArray arrayWithObjects: cpmn count: count];
}
- (void) removeProgressMark: (NSAnimationProgress)progress
{
unsigned index;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
index = GSIArraySearch(_progressMarks, progress,
nsanimation_progressMarkSorter);
if (index < GSIArrayCount(_progressMarks)
&& progress == GSIArrayItemAtIndex (_progressMarks,index))
{
GSIArrayRemoveItemAtIndex(_progressMarks,index);
_isCachedProgressMarkNumbersValid = NO;
if (_nextMark > index) _nextMark--;
NSDebugMLLog(@"NSAnimationMark",@"Remove mark #%d for (next:#%d)",
index, progress, _nextMark);
}
else
NSWarnMLog(@"Unexistent progress mark");
_NSANIMATION_UNLOCK;
}
- (NSArray*) runLoopModesForAnimating
{
return nil;
}
- (void) setAnimationBlockingMode: (NSAnimationBlockingMode)mode
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
_isANewAnimatorNeeded |= (_blockingMode != mode);
_blockingMode = mode;
_NSANIMATION_UNLOCK;
}
- (void) setAnimationCurve: (NSAnimationCurve)curve
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
if (_currentProgress <= 0.0f || _currentProgress >= 1.0f)
{
_curveDesc = _gs_animationCurveDesc[curve];
}
else
{ // FIXME ??
_GSRationalBezierDesc newrb;
_GSRationalBezierDesc *rb1 = &(_curveDesc.rb);
float t1 = (_currentProgress - _curveProgressShift) / (1.0 - _curveProgressShift);
_GSRationalBezierDesc *rb2 = &(_gs_animationCurveDesc[curve].rb);
float t2 = _currentProgress;
float K;
newrb.p[0] = _GSRationalBezierEval ( rb1, t1 );
newrb.w[0] = _GSBezierEval (&rb1->d,t1 );
newrb.w[1] =
rb1->w[1]
+ t1*( 2*( rb1->w[2] - rb1->w[1] )
+ t1*( rb1->w[1] - 2*rb1->w[2] + rb1->w[3] ));
newrb.p[1] = (
rb1->w[1]*rb1->p[1]
+ t1*( 2*( rb1->w[2]*rb1->p[2] - rb1->w[1]*rb1->p[1] )
+ t1*( rb1->w[1]*rb1->p[1] - 2*rb1->w[2]*rb1->p[2] + rb1->w[3]*rb1->p[3] ))
) / newrb.w[1];
newrb.w[2] = rb2->w[2] + t2*(rb2->w[3] - rb2->w[2] );
newrb.p[2] = (
rb2->w[2]*rb2->p[2] + t2*(rb2->w[3]*rb2->p[3] - rb2->w[2]*rb2->p[2])
) / newrb.w[2];
// 3rd point is moved to the right by scaling : w3*p3 = w1*p1 + (w1*p1 - w0*p0)
K = ( 2*newrb.w[1]*newrb.p[1]-newrb.w[0]*newrb.p[0] ) / (newrb.w[2]*newrb.p[2]);
newrb.p[3] = rb2->p[3];
newrb.w[3] = rb2->w[3] * K;
newrb.w[2] = newrb.w[2]* K;
_GSRationalBezierComputeBezierDesc (&newrb);
#if 0
NSLog (@"prgrss = %f shift = %f",_currentProgress,_curveProgressShift);
switch (curve)
{ case 0:NSLog (@"EaseInOut t=%f - %f",t1,t2);break;
case 1:NSLog (@"EaseIn t=%f - %f",t1,t2);break;
case 2:NSLog (@"EaseOut t=%f - %f",t1,t2);break;
case 3:NSLog (@"Linear t=%f - %f",t1,t2);break;
default:NSLog (@"???");
}
NSLog (@"a=%f b=%f c=%f d=%f",newrb.p[0],newrb.p[1],newrb.p[2],newrb.p[3]);
NSLog (@" %f %f %f %f",newrb.w[0],newrb.w[1],newrb.w[2],newrb.w[3]);
#endif
_curveProgressShift = _currentProgress;
_curveDesc.rb = newrb;
_curveDesc.isRBezierComputed = YES;
}
_curve = curve;
_NSANIMATION_UNLOCK;
}
- (void) setCurrentProgress: (NSAnimationProgress)progress
{
BOOL needSearchNextMark = NO;
NSAnimationProgress markedProgress;
_NSANIMATION_LOCKING_SETUP;
if (progress < 0.0) progress = 0.0;
if (progress > 1.0) progress = 1.0;
_NSANIMATION_LOCK;
// NOTE: In the case of a forward jump the marks between the
// previous progress value and the new (excluded) progress
// value are never reached.
// In the case of a backward jump (rewind) the marks will
// be reached again !
if (_nextMark < GSIArrayCount(_progressMarks))
{
markedProgress = GSIArrayItemAtIndex (_progressMarks,_nextMark);
if (markedProgress == progress)
[self _gs_didReachProgressMark: markedProgress];
else
{
// the following should never happens if the progress
// is reached during the normal run of the animation
// (method called from animatorStep)
if (markedProgress < progress) // forward jump ?
needSearchNextMark = YES;
}
}
needSearchNextMark |= progress < _currentProgress; // rewind ?
if (needSearchNextMark)
{
_nextMark = GSIArrayInsertionPosition (_progressMarks,progress,&nsanimation_progressMarkSorter);
if (_nextMark < GSIArrayCount(_progressMarks))
NSDebugMLLog(@"NSAnimationMark",@"Next mark #%d for %f",
_nextMark, GSIArrayItemAtIndex(_progressMarks,_nextMark));
}
NSDebugMLLog(@"NSAnimation",@"Progress = %f", progress);
_currentProgress = progress;
if (progress >= 1.0 && _animator != nil)
[_animator stopAnimation];
_NSANIMATION_UNLOCK;
}
- (void) setDelegate: (id)delegate
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
_delegate = (delegate == nil)? nil : GS_GC_HIDE (delegate);
_NSANIMATION_UNLOCK;
}
- (void) setDuration: (NSTimeInterval)duration
{
_NSANIMATION_LOCKING_SETUP;
if (duration<=0.0)
[NSException raise: NSInvalidArgumentException
format: @"%@ Duration must be > 0.0 (passed: %f)",self,duration];
_NSANIMATION_LOCK;
_duration = duration;
_NSANIMATION_UNLOCK;
}
- (void) setFrameRate: (float)fps
{
_NSANIMATION_LOCKING_SETUP;
if (fps<0.0)
[NSException raise: NSInvalidArgumentException
format: @"%@ Framerate must be >= 0.0 (passed: %f)",self,fps];
_NSANIMATION_LOCK;
_isANewAnimatorNeeded |= (_frameRate != fps);
if ( _frameRate != fps && [self isAnimating] )
{ // a new animator is needed *now*
// FIXME : should I have been smarter ?
[self stopAnimation];
[self startAnimation];
}
_frameRate = fps;
_NSANIMATION_UNLOCK;
}
- (void) setProgressMarks: (NSArray*)marks
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
GSIArrayEmpty(_progressMarks);
_nextMark = 0;
if (marks != nil)
{
unsigned i, count = [marks count];
for (i = 0; i < count; i++)
[self addProgressMark: [(NSNumber*)[marks objectAtIndex:i] floatValue]];
}
_isCachedProgressMarkNumbersValid = NO;
_NSANIMATION_UNLOCK;
}
- (void) startAnimation
{
unsigned i;
if ([self isAnimating])
return;
NSDebugMLLog(@"NSAnimationStart",@"");
for (i = 0; i < GSIArrayCount(_progressMarks); i++)
NSDebugMLLog(@"NSAnimationMark", @"Mark #%d : %f",
i, GSIArrayItemAtIndex(_progressMarks,i));
if ([self currentProgress] >= 1.0)
{
[self setCurrentProgress: 0.0];
_nextMark = 0;
}
_curveDesc = _gs_animationCurveDesc[_curve];
_curveProgressShift = 0.0;
if (_delegate != nil)
{
id delegate;
NSDebugMLLog(@"NSAnimationDelegate", @"Cache delegation methods");
// delegation methods are cached while the animation is running
delegate = GS_GC_UNHIDE(_delegate);
_delegate_animationDidReachProgressMark =
([delegate respondsToSelector: @selector (animation:didReachProgressMark:)]) ?
(void (*)(id,SEL,NSAnimation*,NSAnimationProgress))
[delegate methodForSelector: @selector (animation:didReachProgressMark:)]
: NULL;
_delegate_animationValueForProgress =
([delegate respondsToSelector: @selector (animation:valueForProgress:)]) ?
(float (*)(id,SEL,NSAnimation*,NSAnimationProgress))
[delegate methodForSelector: @selector (animation:valueForProgress:)]
: NULL;
_delegate_animationDidEnd =
([delegate respondsToSelector: @selector (animationDidEnd:)]) ?
(void (*)(id,SEL,NSAnimation*))
[delegate methodForSelector: @selector (animationDidEnd:)]
: NULL;
_delegate_animationDidStop =
([delegate respondsToSelector: @selector (animationDidStop:)]) ?
(void (*)(id,SEL,NSAnimation*))
[delegate methodForSelector: @selector (animationDidStop:)]
: NULL;
_delegate_animationShouldStart =
([delegate respondsToSelector: @selector (animationShouldStart:)]) ?
(BOOL (*)(id,SEL,NSAnimation*))
[delegate methodForSelector: @selector (animationShouldStart:)]
: NULL;
NSDebugMLLog(@"NSAnimationDelegate",
@"Delegation methods : %x %x %x %x %x",
_delegate_animationDidReachProgressMark,
_delegate_animationValueForProgress,
_delegate_animationDidEnd,
_delegate_animationDidStop,
_delegate_animationShouldStart);
_currentDelegate = _delegate;
}
else
{
NSDebugMLLog(@"NSAnimationDelegate",
@" No delegate : clear delegation methods");
_delegate_animationDidReachProgressMark =
(void (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
_delegate_animationValueForProgress =
(float (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
_delegate_animationDidEnd =
(void (*)(id,SEL,NSAnimation*)) NULL;
_delegate_animationDidStop =
(void (*)(id,SEL,NSAnimation*)) NULL;
_delegate_animationShouldStart =
(BOOL (*)(id,SEL,NSAnimation*)) NULL;
_currentDelegate = nil;
}
if (_animator == nil || _isANewAnimatorNeeded)
{
TEST_RELEASE(_animator);
_animator = [[GSAnimator allocWithZone: [self zone]]
initWithAnimation: self
frameRate: _frameRate];
NSAssert(_animator,@"Can not create a GSAnimator");
NSDebugMLLog(@"NSAnimationAnimator", @"New GSAnimator: %@", _animator);
_isANewAnimatorNeeded = NO;
}
switch (_blockingMode)
{
case NSAnimationBlocking:
[self _gs_startAnimationInOwnLoop];
//[_animator setRunLoopModesForAnimating:
// [NSArray arrayWithObject: NSAnimationBlockingRunLoopMode]];
//[_animator startAnimation];
break;
case NSAnimationNonblocking:
{
NSArray *runLoopModes;
runLoopModes = [self runLoopModesForAnimating];
if (runLoopModes == nil)
runLoopModes = _NSAnimationDefaultRunLoopModes;
[_animator setRunLoopModesForAnimating: runLoopModes];
}
[_animator startAnimation];
break;
case NSAnimationNonblockingThreaded:
_isThreaded = YES;
[NSThread
detachNewThreadSelector: @selector (_gs_startThreadedAnimation)
toTarget: self
withObject: nil];
}
}
- (void) startWhenAnimation: (NSAnimation*)animation
reachesProgress: (NSAnimationProgress)start
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
_startAnimation = animation;
_startMark = start;
[_startAnimation addProgressMark: _startMark];
NSDebugMLLog (@"NSAnimationMark",@"register for progress %f", start);
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector (_gs_startAnimationReachesProgressMark:)
name: NSAnimationProgressMarkNotification
object: _startAnimation];
_NSANIMATION_UNLOCK;
}
- (void) stopAnimation
{
_NSANIMATION_LOCKING_SETUP;
if ([self isAnimating])
{
_NSANIMATION_LOCK;
[_animator stopAnimation];
_NSANIMATION_UNLOCK;
}
}
- (void) stopWhenAnimation: (NSAnimation*)animation
reachesProgress: (NSAnimationProgress)stop
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
_stopAnimation = animation;
_stopMark = stop;
[_stopAnimation addProgressMark: _stopMark];
NSDebugMLLog (@"NSAnimationMark",@"register for progress %f", stop);
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector (_gs_stopAnimationReachesProgressMark:)
name: NSAnimationProgressMarkNotification
object: _stopAnimation];
_NSANIMATION_UNLOCK;
}
- (void) encodeWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
[aCoder encodeInt: (int)[self animationCurve]
forKey: @"NSAnimationAnimationCurve"];
[aCoder encodeDouble: [self duration]
forKey: @"NSAnimationDuration"];
}
else
{
[self notImplemented: _cmd];
}
}
- (id) initWithCoder: (NSCoder*)aCoder
{
if ([aCoder allowsKeyedCoding])
{
NSTimeInterval duration = 1.0;
NSAnimationCurve animationCurve = 0;
if ([aCoder containsValueForKey: @"NSAnimationAnimationCurve"])
{
animationCurve = [aCoder decodeIntForKey: @"NSAnimationAnimationCurve"];
}
if ([aCoder containsValueForKey: @"NSAnimationDuration"])
{
duration = [aCoder decodeDoubleForKey: @"NSAnimationDuration"];
}
return [self initWithDuration: duration
animationCurve: animationCurve];
}
else
{
[self notImplemented: _cmd];
}
return self;
}
/*
* protocol GSAnimation (callbacks)
*/
- (void) animatorDidStart
{
id delegate;
_NSANIMATION_LOCKING_SETUP;
NSDebugMLLog(@"NSAnimationAnimator",@"");
_NSANIMATION_LOCK;
delegate = GS_GC_UNHIDE (_currentDelegate);
if (_delegate_animationShouldStart) // method is cached (the animation is running)
{
NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationShouldStart] (cached)");
_delegate_animationShouldStart (delegate,@selector(animationShouldStart:),self);
}
RETAIN (self);
_NSANIMATION_UNLOCK;
}
- (void) animatorDidStop
{
id delegate;
_NSANIMATION_LOCKING_SETUP;
NSDebugMLLog(@"NSAnimationAnimator",@"Progress = %f", _currentProgress);
_NSANIMATION_LOCK;
delegate = GS_GC_UNHIDE (_currentDelegate);
if (_currentProgress < 1.0)
{
if (_delegate_animationDidStop) // method is cached (the animation is running)
{
NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationDidStop] (cached)");
_delegate_animationDidStop (delegate,@selector(animationDidStop:),self);
}
}
else
{
if (_delegate_animationDidEnd) // method is cached (the animation is running)
{
NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationDidEnd] (cached)");
_delegate_animationDidEnd (delegate,@selector(animationDidEnd:),self);
}
}
RELEASE (self);
_NSANIMATION_UNLOCK;
}
- (void) animatorStep: (NSTimeInterval) elapsedTime;
{
NSAnimationProgress progress;
_NSANIMATION_LOCKING_SETUP;
NSDebugMLLog(@"NSAnimationAnimator", @"Elapsed time : %f", elapsedTime);
_NSANIMATION_LOCK;
progress = (elapsedTime / _duration);
{ // have some marks been passed ?
// NOTE: the case where progress == markedProgress is
// treated in [-setCurrentProgress]
unsigned count = GSIArrayCount (_progressMarks);
NSAnimationProgress markedProgress;
while ( _nextMark < count
&& progress > (markedProgress = GSIArrayItemAtIndex (_progressMarks,_nextMark)) ) // is a mark reached ?
{
[self _gs_didReachProgressMark: markedProgress];
}
}
[self setCurrentProgress: progress];
_NSANIMATION_UNLOCK;
}
@end //implementation NSAnimation
@implementation NSAnimation (PrivateNotificationCallbacks)
- (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification
{
NSAnimation *animation;
NSAnimationProgress mark;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
animation = [notification object];
mark = [[[notification userInfo] objectForKey: NSAnimationProgressMark] floatValue];
NSDebugMLLog(@"NSAnimationMark",
@"Start Animation %@ reaches %f", animation, mark);
if ( animation == _startAnimation && mark == _startMark)
{
// [self clearStartAnimation];
[self startAnimation];
}
_NSANIMATION_UNLOCK;
}
- (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification
{
NSAnimation *animation;
NSAnimationProgress mark;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
animation = [notification object];
mark = [[[notification userInfo] objectForKey: NSAnimationProgressMark] floatValue];
NSDebugMLLog(@"NSAnimationMark",
@"Stop Animation %@ reaches %f",animation, mark);
if ( animation == _stopAnimation && mark == _stopMark)
{
// [self clearStopAnimation];
[self stopAnimation];
}
_NSANIMATION_UNLOCK;
}
@end // implementation NSAnimation (PrivateNotificationCallbacks)
@implementation NSAnimation (Private)
- (void) _gs_didReachProgressMark: (NSAnimationProgress) progress
{
_NSANIMATION_LOCKING_SETUP;
NSDebugMLLog(@"NSAnimationMark", @"progress %f", progress);
_NSANIMATION_LOCK;
// calls delegate's method
if (_delegate_animationDidReachProgressMark) // method is cached (the animation is running)
{
NSDebugMLLog(@"NSAnimationDelegate",
@"[delegate animationdidReachProgressMark] (cached)");
_delegate_animationDidReachProgressMark (GS_GC_UNHIDE(_currentDelegate),
@selector(animation:didReachProgressMark:),
self,progress);
}
else // method is not cached (the animation did not start yet)
if ( _delegate != nil
&& [GS_GC_UNHIDE (_delegate)
respondsToSelector: @selector(animation:didReachProgressMark:)] )
{
NSDebugMLLog(@"NSAnimationDelegate",
@"[delegate animationdidReachProgressMark]");
[GS_GC_UNHIDE (_delegate) animation: self didReachProgressMark: progress];
}
// posts a notification
NSDebugMLLog(@"NSAnimationNotification",
@"Post NSAnimationProgressMarkNotification : %f", progress);
[[NSNotificationCenter defaultCenter]
postNotificationName: NSAnimationProgressMarkNotification
object: self
userInfo: [NSDictionary
dictionaryWithObject: [NSNumber numberWithFloat: progress]
forKey: NSAnimationProgressMark
]
];
// skips marks with the same progress value
while (
(++_nextMark) < GSIArrayCount(_progressMarks)
&& GSIArrayItemAtIndex(_progressMarks, _nextMark) == progress
)
;
_NSANIMATION_UNLOCK;
NSDebugMLLog(@"NSAnimationMark",
@"Next mark #%d for %f",
_nextMark, GSIArrayItemAtIndex(_progressMarks, _nextMark - 1));
}
- (void) _gs_startThreadedAnimation
{
// NSAssert(_isThreaded);
CREATE_AUTORELEASE_POOL(pool);
NSDebugMLLog(@"NSAnimationThread",
@"Start of %@", [NSThread currentThread]);
[self _gs_startAnimationInOwnLoop];
NSDebugMLLog(@"NSAnimationThread",
@"End of %@", [NSThread currentThread]);
[pool drain];
_isThreaded = NO;
}
- (void) _gs_startAnimationInOwnLoop
{
NSRunLoop *loop;
NSDate *end;
[_animator setRunLoopModesForAnimating:
[NSArray arrayWithObject: NSAnimationBlockingRunLoopMode]];
[_animator startAnimation];
loop = [NSRunLoop currentRunLoop];
end = [NSDate distantFuture];
for (;;)
{
if ([loop runMode: NSAnimationBlockingRunLoopMode beforeDate: end] == NO)
{
NSDate *d;
CREATE_AUTORELEASE_POOL(pool);
d = [loop limitDateForMode: NSAnimationBlockingRunLoopMode];
if (d == nil)
{
[pool drain];
break; // No inputs and no timers.
}
[NSThread sleepUntilDate: d];
[pool drain];
}
}
}
- (_NSAnimationCurveDesc*) _gs_curveDesc
{ return &self->_curveDesc; }
- (NSAnimationProgress) _gs_curveShift
{ return _curveProgressShift; }
@end // implementation NSAnimation (Private)
@implementation NSAnimation (GNUstep)
- (unsigned int) frameCount
{
unsigned c;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
c = (_animator != nil)? [_animator frameCount] : 0;
_NSANIMATION_UNLOCK;
return c;
}
- (void) resetCounters
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
if (_animator != nil) [_animator resetCounters];
_NSANIMATION_UNLOCK;
}
- (float) actualFrameRate;
{
float r;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
r = (_animator != nil)? [_animator frameRate] : 0.0;
_NSANIMATION_UNLOCK;
return r;
}
@end
/*=======================*
* NSViewAnimation class *
*=======================*/
@interface _GSViewAnimationBaseDesc : NSObject
{
id _target;
NSRect _startFrame;
NSRect _endFrame;
NSString* _effect;
}
- (id) initWithProperties: (NSDictionary*)properties;
- (void) setCurrentProgress: (float)progress;
- (void) setTargetFrame: (NSRect) frame;
@end
@interface _GSViewAnimationDesc : _GSViewAnimationBaseDesc
{
BOOL _shouldHide;
BOOL _shouldUnhide;
}
@end
@interface _GSWindowAnimationDesc : _GSViewAnimationBaseDesc
{
float _startAlpha;
}
@end
@implementation _GSViewAnimationBaseDesc
- (id) initWithProperties: (NSDictionary*)properties
{
if ([self isMemberOfClass: [_GSViewAnimationBaseDesc class]])
{
NSZone* zone;
id target;
zone = [self zone];
RELEASE (self);
target = [properties objectForKey: NSViewAnimationTargetKey];
if (target != nil)
{
if ([target isKindOfClass: [NSView class]])
self = [[_GSViewAnimationDesc allocWithZone: zone]
initWithProperties: properties];
else if ([target isKindOfClass: [NSWindow class]])
self = [(_GSWindowAnimationDesc*)[_GSWindowAnimationDesc allocWithZone: zone]
initWithProperties: properties];
else
[NSException
raise: NSInvalidArgumentException
format: @"Invalid viewAnimation property :"
@"target is neither a NSView nor a NSWindow"];
}
else
[NSException
raise: NSInvalidArgumentException
format: @"Invalid viewAnimation property :"
@"target is nil"];
}
else
{ // called from a subclass
if ((self = [super init]))
{
NSValue* startValue;
NSValue* endValue;
_target = [properties objectForKey: NSViewAnimationTargetKey];
startValue = [properties objectForKey: NSViewAnimationStartFrameKey];
endValue = [properties objectForKey: NSViewAnimationEndFrameKey];
_effect = [properties objectForKey: NSViewAnimationEffectKey];
_startFrame = (startValue!=nil) ?
[startValue rectValue]
: [_target frame];
_endFrame = (endValue!=nil) ?
[endValue rectValue]
: [_target frame];
}
}
return self;
}
- (void) setCurrentProgress: (float)progress
{
if (progress < 1.0f)
{
NSRect r;
r.origin.x = _startFrame.origin.x
+ progress*( _endFrame.origin.x - _startFrame.origin.x );
r.origin.y = _startFrame.origin.y
+ progress*( _endFrame.origin.y - _startFrame.origin.y );
r.size.width = _startFrame.size.width
+ progress*( _endFrame.size.width - _startFrame.size.width );
r.size.height = _startFrame.size.height
+ progress*( _endFrame.size.height - _startFrame.size.height );
[self setTargetFrame: r];
if (_effect == NSViewAnimationFadeOutEffect)
{
[self subclassResponsibility: _cmd];
}
if (_effect == NSViewAnimationFadeInEffect)
{
[self subclassResponsibility: _cmd];
}
}
else
{
[self setTargetFrame: _endFrame];
}
}
- (void) setTargetFrame: (NSRect)frame
{
[self subclassResponsibility: _cmd];
}
@end // implementation _GSViewAnimationDesc
@implementation _GSViewAnimationDesc
- (id) initWithProperties: (NSDictionary*)properties
{
if ((self = [super initWithProperties: properties]))
{
_shouldHide = ([properties objectForKey: NSViewAnimationEndFrameKey] == nil);
_shouldUnhide = ( _effect == NSViewAnimationFadeInEffect
&& [_target isHidden]
&& !_shouldHide);
}
return self;
}
- (void) setCurrentProgress: (float)progress
{
[super setCurrentProgress: progress];
if (_effect == NSViewAnimationFadeOutEffect) {}
/* ??? TODO */;
if (_effect == NSViewAnimationFadeInEffect) {}
/* ??? TODO */;
if (progress>=1.0f)
{
if (_shouldHide)
[_target setHidden:YES];
else if (_shouldUnhide)
[_target setHidden:NO];
}
}
- (void) setTargetFrame: (NSRect)frame
{
[_target setFrame: frame];
}
@end // implementation _GSViewAnimationDesc
@implementation _GSWindowAnimationDesc
- (id) initWithProperties: (NSDictionary*)properties
{
if ((self = [super initWithProperties: properties]))
{
_startAlpha = [_target alphaValue];
}
return self;
}
- (void) setCurrentProgress: (float)progress
{
[super setCurrentProgress: progress];
if (_effect == NSViewAnimationFadeOutEffect)
[_target setAlphaValue: _startAlpha * (1.0f - progress)];
if (_effect == NSViewAnimationFadeInEffect)
[_target setAlphaValue: _startAlpha + (1.0f - _startAlpha) * progress];
if (progress >= 1.0f)
{
if (_effect == NSViewAnimationFadeOutEffect)
[_target orderBack: self];
if (_effect == NSViewAnimationFadeInEffect)
[_target orderFront: self];
}
}
- (void) setTargetFrame: (NSRect) frame
{
[_target setFrame: frame display: YES];
}
@end // implementation _GSWindowAnimationDesc
@implementation NSViewAnimation
- (id) initWithViewAnimations: (NSArray*)animations
{
self = [self initWithDuration: 0.5 animationCurve: NSAnimationEaseInOut];
if (self)
{
[self setAnimationBlockingMode: NSAnimationNonblocking];
[self setViewAnimations: animations];
}
return self;
}
- (void) dealloc
{
RELEASE(_viewAnimations);
RELEASE(_viewAnimationDesc);
[super dealloc];
}
- (void) setViewAnimations: (NSArray*)animations
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
if (_viewAnimations != animations)
DESTROY(_viewAnimationDesc);
ASSIGN(_viewAnimations, animations) ;
_NSANIMATION_UNLOCK;
}
- (NSArray*) viewAnimations
{
NSArray *a;
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
a = _viewAnimations;
_NSANIMATION_UNLOCK;
return a;
}
- (void) startAnimation
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
if (_viewAnimationDesc == nil)
{
unsigned int i, c;
c = [_viewAnimations count];
_viewAnimationDesc = [[NSMutableArray alloc] initWithCapacity: c];
for (i = 0; i < c; i++)
{
_GSViewAnimationBaseDesc *vabd;
vabd = [[_GSViewAnimationBaseDesc alloc]
initWithProperties: [_viewAnimations objectAtIndex:i]];
[_viewAnimationDesc addObject: vabd];
RELEASE(vabd);
}
}
[super startAnimation];
_NSANIMATION_UNLOCK;
}
- (void) stopAnimation
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
[super stopAnimation];
[self setCurrentProgress: 1.0];
_NSANIMATION_UNLOCK;
}
- (void) _gs_updateViewsWithValue: (NSNumber*) value
{
// Runs in main thread : must not call any NSAnimation method to avoid a deadlock
unsigned int i, c;
float v;
v = [value floatValue];
if (_viewAnimationDesc != nil)
for (i = 0, c = [_viewAnimationDesc count]; i < c; i++)
[[_viewAnimationDesc objectAtIndex: i] setCurrentProgress: v];
}
- (void) setCurrentProgress: (NSAnimationProgress)progress
{
_NSANIMATION_LOCKING_SETUP;
_NSANIMATION_LOCK;
[super setCurrentProgress: progress];
[self performSelectorOnMainThread: @selector (_gs_updateViewsWithValue:)
withObject: [NSNumber numberWithFloat:[self currentValue]]
waitUntilDone: YES];
_NSANIMATION_UNLOCK;
}
@end // implementation NSViewAnimation