mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-23 12:20:49 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@39613 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)
|
|
(_currentDelegate,
|
|
@selector (animation:valueForProgress:),
|
|
self, _currentProgress);
|
|
}
|
|
else // method is not cached (the animation did not start yet)
|
|
if ( _delegate != nil
|
|
&& [_delegate respondsToSelector:
|
|
@selector (animation:valueForProgress:)] )
|
|
{
|
|
NSDebugMLLog (@"NSAnimationDelegate",
|
|
@"[delegate animationValueForProgress]");
|
|
value = [_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 : _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
|
|
{
|
|
NSUInteger 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 #%lu (%f) for (next:#%d)",
|
|
(unsigned long)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 : 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 = _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 : %p %p %p %p %p",
|
|
_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 = _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 = _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 (_currentDelegate,
|
|
@selector(animation:didReachProgressMark:),
|
|
self,progress);
|
|
}
|
|
else // method is not cached (the animation did not start yet)
|
|
if ( _delegate != nil
|
|
&& [_delegate
|
|
respondsToSelector: @selector(animation:didReachProgressMark:)] )
|
|
{
|
|
NSDebugMLLog(@"NSAnimationDelegate",
|
|
@"[delegate animationdidReachProgressMark]");
|
|
[_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
|
|
{
|
|
DESTROY(_viewAnimations);
|
|
DESTROY(_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
|
|
|