/* NSAnimation.m Created by Dr. H. Nikolaus Schaller on Sat Mar 06 2006. Copyright (c) 2007 Free Software Foundation, Inc. Author: Xavier Glattard (xgl) 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 3 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 or write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include // needed by NSViewAnimation #include #include #include /*===================* * NSAnimation class * *===================*/ NSString* NSAnimationProgressMarkNotification = @"NSAnimationProgressMarkNotification"; NSString *NSAnimationProgressMark = @"NSAnimationProgressMark"; NSString* NSAnimationBlockingRunLoopMode = @"NSAnimationBlockingRunLoopMode"; #define GSI_ARRAY_NO_RETAIN #define GSI_ARRAY_NO_RELEASE #define GSIArrayItem NSAnimationProgress #include #include // '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); \ NSDebugFLLog(@"NSAnimationLock",\ @"%@ LOCK %@",self,[NSThread currentThread]);\ [_isAnimatingLock lock]; \ __gs_isLocked = YES; \ } #define _NSANIMATION_UNLOCK \ if (__gs_isLocked) \ { \ /* NSAssert(__gs_isLocked == YES, NSInternalInconsistencyException); */ \ NSDebugFLLog(@"NSAnimationLock",\ @"%@ UNLOCK %@",self,[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); NSDebugFLLog (@"NSAnimationMark", @"%@ Insert 1st mark for %f (next:#%d)", self,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); NSDebugFLLog (@"NSAnimationMark", @"%@ Insert mark #%d/%d for %f (next:#%d)", self,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) NSDebugFLLog (@"NSAnimationDelegate", @"%@ [delegate animationValueForProgress] (cached)",self); 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:)] ) { NSDebugFLLog (@"NSAnimationDelegate", @"%@ [delegate animationValueForProgress]",self); 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 { return [self notImplemented: _cmd]; } - (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 index) _nextMark--; NSDebugFLLog (@"NSAnimationMark",@"%@ Remove mark #%d for (next:#%d)",self,index,progress,_nextMark); } else NSWarnFLog (@"%@ Unexistent progress mark",self); _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)) NSDebugFLLog (@"NSAnimationMark",@"%@ Next mark #%d for %f", self,_nextMark, GSIArrayItemAtIndex (_progressMarks,_nextMark)); } NSDebugFLLog (@"NSAnimation",@"%@ Progress = %f",self,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= 1.0) { [self setCurrentProgress: 0.0]; _nextMark = 0; } _curveDesc = _gs_animationCurveDesc[_curve]; _curveProgressShift = 0.0; if (_delegate != nil) { id delegate; NSDebugFLLog (@"NSAnimationDelegate", @"%@ Cache delegation methods",self); // 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; NSDebugFLLog (@"NSAnimationDelegate", @"%@ Delegation methods : %x %x %x %x %x", self, _delegate_animationDidReachProgressMark, _delegate_animationValueForProgress, _delegate_animationDidEnd, _delegate_animationDidStop, _delegate_animationShouldStart); _currentDelegate = _delegate; } else { NSDebugFLLog (@"NSAnimationDelegate", @"%@ No delegate : clear delegation methods",self); _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 animatorWithAnimation: self frameRate: _frameRate zone: [self zone]]; NSAssert (_animator,@"Can not create a GSAnimator"); RETAIN (_animator); NSDebugFLLog (@"NSAnimationAnimator",@"%@ New GSAnimator: %@", self,[_animator class]); _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]; NSDebugFLLog (@"NSAnimationMark",@"%@ register for progress %f",self,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]; NSDebugFLLog (@"NSAnimationMark",@"%@ register for progress %f",self,stop); [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (_gs_stopAnimationReachesProgressMark:) name: NSAnimationProgressMarkNotification object: _stopAnimation]; _NSANIMATION_UNLOCK; } - (void) encodeWithCoder: (NSCoder*)coder { [self notImplemented: _cmd]; } - (id) initWithCoder: (NSCoder*)coder { return [self notImplemented: _cmd]; } /* * protocol GSAnimation (callbacks) */ - (void) animatorDidStart { id delegate; _NSANIMATION_LOCKING_SETUP; NSDebugFLLog (@"NSAnimationAnimator",@"%@",self); _NSANIMATION_LOCK; delegate = GS_GC_UNHIDE (_currentDelegate); if (_delegate_animationShouldStart) // method is cached (the animation is running) { NSDebugFLLog (@"NSAnimationDelegate",@"%@ [delegate animationShouldStart] (cached)",self); _delegate_animationShouldStart (delegate,@selector(animationShouldStart:),self); } RETAIN (self); _NSANIMATION_UNLOCK; } - (void) animatorDidStop { id delegate; _NSANIMATION_LOCKING_SETUP; NSDebugFLLog (@"NSAnimationAnimator",@"%@ Progress = %f",self,_currentProgress); _NSANIMATION_LOCK; delegate = GS_GC_UNHIDE (_currentDelegate); if (_currentProgress < 1.0) { if (_delegate_animationDidStop) // method is cached (the animation is running) { NSDebugFLLog (@"NSAnimationDelegate",@"%@ [delegate animationDidStop] (cached)",self); _delegate_animationDidStop (delegate,@selector(animationDidStop:),self); } } else { if (_delegate_animationDidEnd) // method is cached (the animation is running) { NSDebugFLLog (@"NSAnimationDelegate",@"%@ [delegate animationDidEnd] (cached)",self); _delegate_animationDidEnd (delegate,@selector(animationDidEnd:),self); } } RELEASE (self); _NSANIMATION_UNLOCK; } - (void) animatorStep: (NSTimeInterval) elapsedTime; { NSAnimationProgress progress; _NSANIMATION_LOCKING_SETUP; NSDebugFLLog (@"NSAnimationAnimator",@"%@ Elapsed time : %f",self,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]; NSDebugFLLog (@"NSAnimationMark", @"%@ Start Animation %@ reaches %f",self,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]; NSDebugFLLog (@"NSAnimationMark", @"%@ Stop Animation %@ reaches %f",self,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; NSDebugFLLog (@"NSAnimationMark",@"%@ progress %f",self, progress); _NSANIMATION_LOCK; // calls delegate's method if (_delegate_animationDidReachProgressMark) // method is cached (the animation is running) { NSDebugFLLog (@"NSAnimationDelegate", @"%@ [delegate animationdidReachProgressMark] (cached)",self); _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:)] ) { NSDebugFLLog (@"NSAnimationDelegate", @"%@ [delegate animationdidReachProgressMark]",self); [GS_GC_UNHIDE (_delegate) animation: self didReachProgressMark: progress]; } // posts a notification NSDebugFLLog (@"NSAnimationNotification", @"%@ Post NSAnimationProgressMarkNotification : %f",self,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; NSDebugFLLog (@"NSAnimationMark", @"%@ Next mark #%d for %f", self,_nextMark,GSIArrayItemAtIndex(_progressMarks,_nextMark)); } - (void) _gs_startThreadedAnimation { // NSAssert(_isThreaded); CREATE_AUTORELEASE_POOL (pool); NSDebugFLLog (@"NSAnimationThread", @"%@ Start of %@",self,[NSThread currentThread]); [self _gs_startAnimationInOwnLoop]; NSDebugFLLog (@"NSAnimationThread", @"%@ End of %@",self,[NSThread currentThread]); RELEASE (pool); _isThreaded = NO; } - (void) _gs_startAnimationInOwnLoop { [_animator setRunLoopModesForAnimating: [NSArray arrayWithObject: NSAnimationBlockingRunLoopMode]]; [_animator startAnimation]; while ( [[NSRunLoop currentRunLoop] runMode: NSAnimationBlockingRunLoopMode beforeDate: [NSDate distantFuture]] ) /* do nothing */; } - (_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 * *=======================*/ NSString *NSViewAnimationTargetKey = @"NSViewAnimationTargetKey"; NSString *NSViewAnimationStartFrameKey = @"NSViewAnimationStartFrameKey"; NSString *NSViewAnimationEndFrameKey = @"NSViewAnimationEndFrameKey"; NSString *NSViewAnimationEffectKey = @"NSViewAnimationEffectKey"; NSString *NSViewAnimationFadeInEffect = @"NSViewAnimationFadeInEffect"; NSString *NSViewAnimationFadeOutEffect = @"NSViewAnimationFadeOutEffect"; @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) /* subclassResponsibility */; if (_effect == NSViewAnimationFadeInEffect) /* subclassResponsibility */; } 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]; _viewAnimations = [animations retain]; _viewAnimationDesc = nil; } 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 i,c; c = [_viewAnimations count]; _viewAnimationDesc = [NSMutableArray arrayWithCapacity: c]; RETAIN (_viewAnimationDesc); for (i=0;i