mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-24 02:51:22 +00:00
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
1536 lines
42 KiB
Objective-C
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
|
|
|