/* * 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 Library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; see the file COPYING.LIB. * If not, write to the Free Software Foundation, * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include // needed by NSViewAnimation #include #include #include /* * NSAnimation class */ NSString *NSAnimationProgressMarkNotification = @"NSAnimationProgressMarkNotification"; #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 _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}} } }; _NSAnimationCurveDesc *_gs_animationCurveDesc = __gs_animationCurveDesc; @interface NSAnimation(PrivateNotificationCallbacks) - (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification; - (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification; @end @interface NSAnimation(Private) - (void) _gs_didReachProgressMark: (NSAnimationProgress)progress; - (_NSAnimationCurveDesc*) _gs_curveDesc; - (NSAnimationProgress) _gs_curveShift; @end NSComparisonResult nsanimation_progressMarkSorter( NSAnimationProgress first,NSAnimationProgress second) { float diff = first - second; return (NSComparisonResult) (diff / fabs(diff)); } @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); } - (void) addProgressMark: (NSAnimationProgress)progress { if(progress < 0.0) progress = 0.0; if(progress > 1.0) progress = 1.0; 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; } - (NSAnimationBlockingMode) animationBlockingMode { return _blockingMode; } - (NSAnimationCurve) animationCurve { return _curve; } - (void) clearStartAnimation { [[NSNotificationCenter defaultCenter] removeObserver: self name: NSAnimationProgressMarkNotification object: _startAnimation]; _startAnimation = nil; } - (void) clearStopAnimation { [[NSNotificationCenter defaultCenter] removeObserver: self name: NSAnimationProgressMarkNotification object: _stopAnimation]; _stopAnimation = nil; } - (NSAnimationProgress) currentProgress { return _currentProgress; } - (float) currentValue { float value; id delegate; delegate = GS_GC_UNHIDE(_delegate); if(_delegate_animationValueForProgress) // method is cached (the animation is running) { NSDebugFLLog(@"NSAnimationDelegate",@"%@ [delegate animationValueForProgress] (cached)",self); value = (*_delegate_animationValueForProgress)(delegate,@selector(animation:valueForProgress:),self,_currentProgress); } else // method is not cached (the animation did not start yet) if( _delegate != nil && [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; }*/ return value; } - (id) delegate { return (_delegate == nil)? nil : GS_GC_UNHIDE(_delegate); } - (NSTimeInterval) duration { return _duration; } - (float) frameRate { return _frameRate; } - (id) initWithDuration: (NSTimeInterval)duration animationCurve: (NSAnimationCurve)curve { if ((self = [super init])) { _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; } return self; } - (id) copyWithZone: (NSZone*)zone { return [self notImplemented: _cmd]; } - (void) dealloc { GSIArrayEmpty(_progressMarks); NSZoneFree([self zone], _progressMarks); if(_cachedProgressMarkNumbers != NULL) { unsigned i; for( i=0; i<_cachedProgressMarkNumberCount; i++) RELEASE(_cachedProgressMarkNumbers[i]); NSZoneFree([self zone], _cachedProgressMarkNumbers); } if( _startAnimation != nil || _stopAnimation != nil) [[NSNotificationCenter defaultCenter] removeObserver:self]; TEST_RELEASE(_animator); [super dealloc]; } - (BOOL) isAnimating { return (_animator != nil)? [_animator isAnimationRunning] : NO; } - (NSArray*) progressMarks { unsigned 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); } - (NSArray*) runLoopModesForAnimating { return nil; } - (void) setAnimationBlockingMode: (NSAnimationBlockingMode)mode { _isANewAnimatorNeeded |= (_blockingMode != mode); _blockingMode = mode; } - (void) setAnimationCurve: (NSAnimationCurve)curve { 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; } - (void) setCurrentProgress: (NSAnimationProgress)progress { BOOL needSearchNextMark = NO; NSAnimationProgress markedProgress; if(progress < 0.0) progress = 0.0; if(progress > 1.0) progress = 1.0; // 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]; } - (void) setDelegate: (id)delegate { _delegate = (delegate == nil)? nil : GS_GC_HIDE(delegate); } - (void) setDuration: (NSTimeInterval)duration { _duration = duration; } - (void) setFrameRate: (float)fps { if(fps<0.0) [NSException raise: NSInvalidArgumentException format: @"%@ Framerate must be >= 0.0 (passed: %f)",self,fps]; _isANewAnimatorNeeded |= (_frameRate != fps); _frameRate = fps; } - (void) setProgressMarks: (NSArray*)marks { GSIArrayEmpty(_progressMarks); 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) { NSDebugFLLog(@"NSAnimationDelegate",@"%@ Cache delegation methods",self); // delegation methods are cached while the animation is running id delegate; 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); } 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; } if(_animator==nil || _isANewAnimatorNeeded) { TEST_RELEASE(_animator); _animator = [GSAnimator animatorWithAnimation: self mode: _blockingMode frameRate: _frameRate zone: [self zone]]; NSAssert(_animator,@"Can not create a GSAnimator"); RETAIN(_animator); NSDebugFLLog(@"NSAnimationAnimator",@"%@ New GSAnimator: %@", self,[_animator class]); } NSDebugFLLog(@"NSAnimationAnimator",@"%@ Start animator %@...",self,_animator); [_animator startAnimation]; } - (void) startWhenAnimation: (NSAnimation*)animation reachesProgress: (NSAnimationProgress)start { _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]; } - (void) stopAnimation { if([self isAnimating]) [_animator stopAnimation]; } - (void) stopWhenAnimation: (NSAnimation*)animation reachesProgress: (NSAnimationProgress)stop { _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]; } - (void) encodeWithCoder: (NSCoder*)coder { [self notImplemented: _cmd]; } - (id) initWithCoder: (NSCoder*)coder { return [self notImplemented: _cmd]; } /* * protocol GSAnimation (callbacks) */ - (void) animatorDidStart { NSDebugFLLog(@"NSAnimationAnimator",@"%@",self); id delegate; delegate = GS_GC_UNHIDE(_delegate); if(_delegate_animationShouldStart) // method is cached (the animation is running) { NSDebugFLLog(@"NSAnimationDelegate",@"%@ [delegate animationShouldStart] (cached)",self); _delegate_animationShouldStart(delegate,@selector(animationShouldStart:),self); } RETAIN(self); } - (void) animatorDidStop { NSDebugFLLog(@"NSAnimationAnimator",@"%@ Progress = %f",self,_currentProgress); id delegate; delegate = GS_GC_UNHIDE(_delegate); 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); } - (void) animatorStep: (NSTimeInterval) elapsedTime; { NSDebugFLLog(@"NSAnimationAnimator",@"%@ Elapsed time : %f",self,elapsedTime); NSAnimationProgress 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]; } @end //implementation NSAnimation @implementation NSAnimation(PrivateNotificationCallbacks) - (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification { NSDebugFLLog(@"NSAnimationMark",@"%@",self); NSAnimation *animation = [notification object]; if( animation == _startAnimation && [_startAnimation currentProgress] >= _startMark) { // [self clearStartAnimation]; [self startAnimation]; } } - (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification { NSDebugFLLog(@"NSAnimationMark",@"%@",self); NSAnimation *animation = [notification object]; if( animation == _stopAnimation && [_stopAnimation currentProgress] >= _stopMark) { // [self clearStopAnimation]; [self stopAnimation]; } } @end // implementation NSAnimation(PrivateNotificationCallbacks) @implementation NSAnimation(Private) - (void) _gs_didReachProgressMark: (NSAnimationProgress) progress { NSDebugFLLog(@"NSAnimationMark",@"%@ progress %f",self, progress); // 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(_delegate),@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) ; NSDebugFLLog(@"NSAnimationMark",@"%@ Next mark #%d for %f",self,_nextMark,GSIArrayItemAtIndex(_progressMarks,_nextMark)); } - (_NSAnimationCurveDesc*) _gs_curveDesc { return &self->_curveDesc; } - (NSAnimationProgress) _gs_curveShift { return _curveProgressShift; } @end // implementation NSAnimation(Private) @implementation NSAnimation(GNUstep) - (unsigned int) frameCount { return (_animator != nil)? [_animator frameCount] : 0; } - (void) resetCounters { if(_animator != nil) [_animator resetCounters]; } - (float) actualFrameRate; { return (_animator != nil)? [_animator frameRate] : 0.0; } @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 { if(_viewAnimations != animations) DESTROY(_viewAnimationDesc); ASSIGN(_viewAnimations, animations) ; } - (NSArray*) viewAnimations { return _viewAnimations; } - (void) startAnimation { if(_viewAnimationDesc == nil) { unsigned i,c; c = [_viewAnimations count]; _viewAnimationDesc = [NSMutableArray arrayWithCapacity: c]; RETAIN(_viewAnimationDesc); for(i=0;i