diff --git a/Headers/Additions/GNUstepGUI/GSAnimator.h b/Headers/Additions/GNUstepGUI/GSAnimator.h new file mode 100644 index 000000000..28c1dba87 --- /dev/null +++ b/Headers/Additions/GNUstepGUI/GSAnimator.h @@ -0,0 +1,123 @@ +/* + * GSAnimator.h + * + * Author: Xavier Glattard (xgl) + * + * Copyright (c) 2007 Free Software Foundation, Inc. + * + * 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. + */ + +#ifndef _GNUstep_H_GSAnimator_ +#define _GNUstep_H_GSAnimator_ + +@class NSRunLoop; +@class NSEvent; +@class NSTimer; +@class NSThread; + +#include +#include +#include + +/** + * Protocol that needs to be adopted by classes that want to + * be animated by a GSAnimator. + */ +@protocol GSAnimation +/** Returns the run-loop modes useed to run the animation timer in. */ +- (NSArray*) runLoopModesForAnimating; +/** Call back method indicating that the GSAnimator did start the + * animation loop. */ +- (void) animatorDidStart; +/** Call back method indicating that the GSAnimator did stop the + * animation loop. */ +- (void) animatorDidStop; +/** Call back method called for each animation loop. */ +- (void) animatorStep: (NSTimeInterval) elapsedTime; +@end + +typedef enum +{ + GSTimerBasedAnimation, + GSPerformerBasedAnimation, + // Cocoa compatible animation modes : + GSBlockingCocoaAnimation, + GSNonblockingCocoaAnimation, + GSNonblockingCocoaThreadedAnimation +} GSAnimationBlockingMode; + +/** + * GSAnimator is the front of a class cluster. Instances of a subclass of + * GSAnimator manages the timing of an animation. + */ +@interface GSAnimator : NSObject +{ + id _animation; // The Object to be animated + NSDate *_startTime; // The time the animation did started + BOOL _running; // Indicates that the animator is looping + + NSTimeInterval _elapsed; // Elapsed time since the animator started + NSTimeInterval _lastFrame; // The time of the last animation loop + unsigned int _frameCount; // The number of loops since the start + + NSRunLoop *_runLoop; // The run-loop used for looping + + NSTimer *_timer; // Timer used for looping + NSTimeInterval _timerInterval; +} + +/** Returns a GSAnimator object initialized with the specified object + * to be animated. */ ++ (GSAnimator*) animatorWithAnimation: (id)anAnimation + mode: (GSAnimationBlockingMode)aMode + frameRate: (float)fps; + +/** Returns a GSAnimator object allocated in the given NSZone and + * initialized with the specified object to be animated. */ ++ (GSAnimator*) animatorWithAnimation: (id)anAnimation + mode: (GSAnimationBlockingMode)aMode + frameRate: (float)fps + zone: (NSZone*)aZone; + +/** Returns a GSAnimator object initialized with the specified object + * to be animated. */ +- (GSAnimator*) initWithAnimation: (id)anAnimation + frameRate: (float)aFrameRate + runLoop: (NSRunLoop*)aRunLoop; +- (GSAnimator*) initWithAnimation: (id)anAnimation; + +- (unsigned int) frameCount; +- (void) resetCounters; +- (float) frameRate; +- (NSRunLoop*) runLoopForAnimating; +- (NSArray*) runLoopModesForAnimating; + +- (void) startAnimation; +- (void) stopAnimation; +- (BOOL) isAnimationRunning; +- (void) startStopAnimation; + +- (void) stepAnimation; + +- (void) animationLoopEvent: (NSEvent*)e; +@end + +#endif /* _GNUstep_H_GSAnimator_ */ diff --git a/Headers/AppKit/AppKit.h b/Headers/AppKit/AppKit.h index 319ecc9fe..50420144f 100644 --- a/Headers/AppKit/AppKit.h +++ b/Headers/AppKit/AppKit.h @@ -45,6 +45,7 @@ #include #include +#include #include #include #include diff --git a/Headers/AppKit/NSAnimation.h b/Headers/AppKit/NSAnimation.h index 638abf4bc..bf4bf858d 100644 --- a/Headers/AppKit/NSAnimation.h +++ b/Headers/AppKit/NSAnimation.h @@ -2,7 +2,9 @@ * NSAnimation.h * * Created by Dr. H. Nikolaus Schaller on Sat Jan 07 2006. - * Copyright (c) 2005 DSITRI. + * 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. @@ -24,106 +26,339 @@ * Boston, MA 02110-1301, USA. */ -#ifndef _NSAnimation_h_GNUstep_ -#define _NSAnimation_h_GNUstep_ +#ifndef _GNUstep_H_NSAnimation_ +#define _GNUstep_H_NSAnimation_ -#include +#include +#if OS_API_VERSION(MAC_OS_X_VERSION_10_4, GS_API_LATEST) + +#include +#include +#include +#include +#include + +// Bezier curve parameters +typedef struct __GSBezierDesc +{ + float p[4]; // control points + BOOL areCoefficientsComputed; + float a[4]; // coefficients +} _GSBezierDesc; + +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]); +} + +// Rational Bezier curve parameters +typedef struct __GSRationalBezierDesc +{ + float w[4]; // weights + float p[4]; // control points + BOOL areBezierDescComputed; + _GSBezierDesc n; // numerator + _GSBezierDesc d; // denumerator +} _GSRationalBezierDesc; + +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) +{ + if(!rb->areBezierDescComputed) + _GSRationalBezierComputeBezierDesc(rb); + float h = _GSBezierEval(&(rb->d),t); + return ( _GSBezierDerivEval(&(rb->n),t) * h + - _GSBezierEval (&(rb->n),t) * _GSBezierDerivEval(&(rb->d),t) ) + / (h*h); +} + +typedef struct __NSAnimationCurveDesc +{ + float s,e; // start & end values + float sg,eg; // start & end gradients + _GSRationalBezierDesc rb; + BOOL isRBezierComputed; +} _NSAnimationCurveDesc; + +extern +_NSAnimationCurveDesc *_gs_animationCurveDesc; + +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) ); +} + +@class NSString; +@class NSArray; +@class NSNumber; + +/** These constants describe the curve of an animation—that is, the relative speed of an animation from start to finish. */ typedef enum _NSAnimationCurve { NSAnimationEaseInOut = 0, // default NSAnimationEaseIn, NSAnimationEaseOut, - NSAnimationLinear + NSAnimationLinear, + NSAnimationSpeedInOut // GNUstep only } NSAnimationCurve; +/** These constants indicate the blocking mode of an NSAnimation object when it is running. */ typedef enum _NSAnimationBlockingMode { - NSAnimationBlocking, - NSAnimationNonblocking, - NSAnimationNonblockingThreaded + NSAnimationBlocking = GSBlockingCocoaAnimation, + NSAnimationNonblocking = GSNonblockingCocoaAnimation, + NSAnimationNonblockingThreaded = GSNonblockingCocoaThreadedAnimation } NSAnimationBlockingMode; typedef float NSAnimationProgress; -extern NSString *NSAnimationProgressMarkNotification; +/** Posted when the current progress of a running animation reaches one of its progress marks. */ +APPKIT_EXPORT NSString *NSAnimationProgressMarkNotification; -@interface NSAnimation : NSObject < NSCopying, NSCoding > +/** + * Objects of the NSAnimation class manage the timing and progress of + * animations in the user interface. The class also lets you link together + * multiple animations so that when one animation ends another one starts. + * It does not provide any drawing support for animation and does not directly + * deal with views, targets, or actions. + */ +@interface NSAnimation : NSObject < NSCopying, NSCoding, GSAnimation > { - NSAnimationBlockingMode _animationBlockingMode; - NSAnimationCurve _animationCurve; - NSAnimationProgress _currentProgress; - NSMutableArray *_progressMarks; - id _delegate; - NSTimeInterval _duration; - float _currentValue; - float _frameRate; - BOOL _isAnimating; // ?or the NSThread * + NSTimeInterval _duration; // Duration of the animation + float _frameRate; // Wanted frame rate + NSAnimationCurve _curve; // Id of progres->value function + _NSAnimationCurveDesc _curveDesc; // The curve as a rat. Bezier + NSAnimationProgress _curveProgressShift;// Shift used for switching + // from a curve to an other + NSAnimationProgress _currentProgress; // Progress of the animation + + /* GSIArray */ void *_progressMarks; // Array + unsigned int _nextMark; // The next mark to be reached + // = count if no next mark + NSNumber **_cachedProgressMarkNumbers; // Cached values used by + unsigned _cachedProgressMarkNumberCount;// [-progressMarks] + BOOL _isCachedProgressMarkNumbersValid; + + NSAnimation *_startAnimation, *_stopAnimation; // Animations used as + NSAnimationProgress _startMark, _stopMark; // trigger, and marks + + NSAnimationBlockingMode _blockingMode; // Blocking mode + GSAnimator *_animator; // The animator + BOOL _isANewAnimatorNeeded; // Some parameters have changed... + + id _delegate; // The delegate, and the cached delegation methods... + void (*_delegate_animationDidReachProgressMark)(id,SEL,NSAnimation*,NSAnimationProgress); + float (*_delegate_animationValueForProgress )(id,SEL,NSAnimation*,NSAnimationProgress); + void (*_delegate_animationDidEnd )(id,SEL,NSAnimation*); + void (*_delegate_animationDidStop )(id,SEL,NSAnimation*); + BOOL (*_delegate_animationShouldStart )(id,SEL,NSAnimation*); } -- (void) addProgressMark: (NSAnimationProgress) progress; +/** Adds the progress mark to the receiver. */ +- (void) addProgressMark: (NSAnimationProgress)progress; + +/** Returns the blocking mode the receiver is next scheduled to run under. */ - (NSAnimationBlockingMode) animationBlockingMode; + +/** Returns the animation curve the receiver is running under. */ - (NSAnimationCurve) animationCurve; + +/** Clears linkage to another animation that causes the receiver to start. */ - (void) clearStartAnimation; + +/** Clears linkage to another animation that causes the receiver to stop. */ - (void) clearStopAnimation; + +/** Returns the current progress of the receiver. */ - (NSAnimationProgress) currentProgress; + +/** Returns the current value of the effect based on the current progress. */ - (float) currentValue; + +/** Returns the delegate of the receiver. */ - (id) delegate; + +/** Returns the duration of the animation, in seconds. */ - (NSTimeInterval) duration; + +/** Returns the frame rate of the animation. */ - (float) frameRate; -- (id) initWithDuration: (NSTimeInterval) duration animationCurve: - (NSAnimationCurve) curve; + +/** Returns an NSAnimation object initialized with the specified duration and animation-curve values. */ +- (id) initWithDuration: (NSTimeInterval)duration + animationCurve: (NSAnimationCurve)curve; + +/** Returns a Boolean value that indicates whether the receiver is currently animating. */ - (BOOL) isAnimating; -- (NSArray *) progressMarks; -- (void) removeProgressMark: (NSAnimationProgress) progress; -- (NSArray *) runLoopModesForAnimating; -- (void) setAnimationBlockingMode: (NSAnimationBlockingMode) mode; -- (void) setAnimationCurve: (NSAnimationCurve) curve; -- (void) setCurrentProgress: (NSAnimationProgress) progress; -- (void) setDelegate: (id) delegate; -- (void) setDuration: (NSTimeInterval) duration; -- (void) setFrameRate: (float) fps; -- (void) setProgressMarks: (NSArray *) progress; + +/** Returns the receiver’s progress marks. */ +- (NSArray*) progressMarks; + +/** Removes progress mark from the receiver. */ +- (void) removeProgressMark: (NSAnimationProgress)progress; + +/** Overridden to return the run-loop modes that the receiver uses to run the animation timer in. */ +- (NSArray*) runLoopModesForAnimating; + +/** Sets the blocking mode of the receiver. */ +- (void) setAnimationBlockingMode: (NSAnimationBlockingMode)mode; + +/** Sets the receiver’s animation curve. */ +- (void) setAnimationCurve: (NSAnimationCurve)curve; + +/** Sets the current progress of the receiver. */ +- (void) setCurrentProgress: (NSAnimationProgress)progress; + +/** Sets the delegate of the receiver. */ +- (void) setDelegate: (id)delegate; + +/** Sets the duration of the animation to a specified number of seconds. */ +- (void) setDuration: (NSTimeInterval)duration; + +/** Sets the frame rate of the receiver. */ +- (void) setFrameRate: (float)fps; + +/** Sets the receiver’s progress marks to the values specified in the passed-in array. */ +- (void) setProgressMarks: (NSArray*)progress; + +/** Starts the animation represented by the receiver. */ - (void) startAnimation; -- (void) startWhenAnimation: (NSAnimation *) animation reachesProgress: - (NSAnimationProgress) start; + +/** Starts running the animation represented by the receiver when another animation reaches a specific progress mark. */ +- (void) startWhenAnimation: (NSAnimation*)animation + reachesProgress: (NSAnimationProgress)start; + +/** Stops the animation represented by the receiver. */ - (void) stopAnimation; -- (void) stopWhenAnimation: (NSAnimation *) animation reachesProgress: - (NSAnimationProgress) stop; + +/** Stops running the animation represented by the receiver when another animation reaches a specific progress mark. */ +- (void) stopWhenAnimation: (NSAnimation*)animation + reachesProgress: (NSAnimationProgress)stop; @end +@interface NSAnimation (GNUstep) + +/** Returns the current value of the frame counter */ +- (unsigned int) frameCount; + +/** Resets all stats */ +- (void) resetCounters; + +/** Returns the current the actual (mesured) frame rate value */ +- (float) actualFrameRate; + +@end @interface NSObject (NSAnimation) -- (void) animation: (NSAnimation *) animation didReachProgressMark: - (NSAnimationProgress) progress; -- (float) animation: (NSAnimation *) animation valueForProgress: - (NSAnimationProgress) progress; -- (void) animationDidEnd: (NSAnimation *) animation; -- (void) animationDidStop: (NSAnimation *) animation; -- (BOOL) animationShouldStart: (NSAnimation *) animation; +/** NSAnimation delegate method. + * Sent to the delegate when an animation reaches a specific progress mark. */ +- (void) animation: (NSAnimation*)animation + didReachProgressMark: (NSAnimationProgress)progress; + +/** NSAnimation delegate method. + * Requests a custom curve value for the current progress value. */ +- (float) animation: (NSAnimation*)animation + valueForProgress: (NSAnimationProgress)progress; + +/** NSAnimation delegate method. + * Sent to the delegate when the specified animation completes its run. */ +- (void) animationDidEnd: (NSAnimation*)animation; + +/** NSAnimation delegate method. + * Sent to the delegate when the specified animation is stopped before it completes its run. */ +- (void) animationDidStop: (NSAnimation*)animation; + +/** NSAnimation delegate method. + * Sent to the delegate just after an animation is started. */ +- (BOOL) animationShouldStart: (NSAnimation*)animation; @end +APPKIT_EXPORT NSString *NSViewAnimationTargetKey; +APPKIT_EXPORT NSString *NSViewAnimationStartFrameKey; +APPKIT_EXPORT NSString *NSViewAnimationEndFrameKey; +APPKIT_EXPORT NSString *NSViewAnimationEffectKey; -extern NSString *NSViewAnimationTargetKey; -extern NSString *NSViewAnimationStartFrameKey; -extern NSString *NSViewAnimationEndFrameKey; -extern NSString *NSViewAnimationEffectKey; -extern NSString *NSViewAnimationFadeInEffect; -extern NSString *NSViewAnimationFadeOutEffect; +APPKIT_EXPORT NSString *NSViewAnimationFadeInEffect; +APPKIT_EXPORT NSString *NSViewAnimationFadeOutEffect; +/** + * The NSViewAnimation class, a public subclass of NSAnimation, + * offers a convenient way to animate multiple views and windows. + * The animation effects you can achieve are limited to changes in + * frame location and size, and to fade-in and fade-out effects. + */ @interface NSViewAnimation : NSAnimation { NSArray *_viewAnimations; + NSMutableArray *_viewAnimationDesc; } -- (id) initWithViewAnimations: (NSArray *) animations; -- (void) setWithViewAnimations: (NSArray *) animations; -- (NSArray *) viewAnimations; +/** Returns an NSViewAnimation object initialized with the supplied information. */ +- (id) initWithViewAnimations: (NSArray*)animations; + +/** Sets the dictionaries defining the objects to animate. */ +- (void) setViewAnimations: (NSArray*)animations; + +/** Returns the array of dictionaries defining the objects to animate. */ +- (NSArray*) viewAnimations; @end -#endif /* _NSAnimation_h_GNUstep_ */ +#endif /* OS_API_VERSION */ + +#endif /* _GNUstep_H_NSAnimation_ */ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 8e0004b5f..8cd9f12c2 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -170,6 +170,7 @@ NSWindow+Toolbar.m \ NSWindow.m \ NSWindowController.m \ NSWorkspace.m \ +GSAnimator.m \ GSDisplayServer.m \ GSFusedSilica.m \ GSFusedSilicaContext.m \ @@ -368,6 +369,7 @@ GUI_HEADERS = \ GSVersion.h \ GMAppKit.h \ GMArchiver.h \ +GSAnimator.h \ GSTheme.h \ GSFontInfo.h \ GSMemoryPanel.h \ diff --git a/Source/GSAnimator.m b/Source/GSAnimator.m new file mode 100644 index 000000000..da7f9d1b7 --- /dev/null +++ b/Source/GSAnimator.m @@ -0,0 +1,359 @@ +/* + * GSAnimator.m + * + * Author: Xavier Glattard (xgl) + * + * Copyright (c) 2007 Free Software Foundation, Inc. + * + * 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 +#include +#include + +#include + +typedef enum { + NullEvent, + GSAnimationNextFrameEvent, + GSAnimationEventTypeNumber +} GSAnimationEventType; + +@interface GSAnimator(private) +- (void) _animationBegin; +- (void) _animationLoop; +- (void) _animationEnd; +@end + +@interface GSTimerBasedAnimator : GSAnimator +{ } +@end + +@interface GSPerformerBasedAnimator : GSAnimator +{ } +@end + +@interface GSThreadedTimerBasedAnimator : GSAnimator +{ + NSThread* _thread; +} +@end + +@implementation GSAnimator + ++ (GSAnimator*) animatorWithAnimation: (id)anAnimation + mode: (GSAnimationBlockingMode)aMode + frameRate: (float)fps +{ + return [self + animatorWithAnimation: anAnimation + mode: aMode + frameRate: fps + zone: NULL]; +} + ++ (GSAnimator*) animatorWithAnimation: (id)anAnimation + mode: (GSAnimationBlockingMode)aMode + frameRate: (float)fps + zone: (NSZone*)aZone +{ + GSAnimator* animator; + NSRunLoop* runLoop; + + switch(aMode) + {//FIXME + case GSTimerBasedAnimation: + case GSPerformerBasedAnimation: + case GSBlockingCocoaAnimation: + case GSNonblockingCocoaAnimation: + case GSNonblockingCocoaThreadedAnimation: + runLoop = [NSRunLoop currentRunLoop]; + animator = [[GSTimerBasedAnimator allocWithZone: aZone] + initWithAnimation: anAnimation + frameRate: fps + runLoop: runLoop]; + } + AUTORELEASE(animator); + return animator; +} + +- (GSAnimator*) initWithAnimation: (id)anAnimation + frameRate: (float)aFrameRate + runLoop: (NSRunLoop*)aRunLoop +{ + if((self = [super init])) + { + _running = NO; + + _animation = anAnimation; TEST_RETAIN(_animation); + _runLoop = aRunLoop; TEST_RETAIN(_runLoop); + _timerInterval = (aFrameRate==0.0)?0.0:(1.0/aFrameRate); + + [self resetCounters]; + } + return self; +} + +- (GSAnimator*) initWithAnimation: (id)anAnimation +{ + return [self initWithAnimation: anAnimation + frameRate: 0.0 + runLoop: [NSRunLoop currentRunLoop]]; +} + +- (void) dealloc +{ + [self stopAnimation]; + TEST_RELEASE(_animation); + TEST_RELEASE(_runLoop); + TEST_RELEASE(_startTime); + [super dealloc]; +} + +- (unsigned int) frameCount +{ return _frameCount; } + +- (void) resetCounters +{ + _elapsed = 0.0; + _frameCount = 0; + _lastFrame = [NSDate timeIntervalSinceReferenceDate]; +} + +- (float) frameRate +{ return ((float)[self frameCount]) / ((float)_elapsed); } + +- (NSRunLoop*) runLoopForAnimating +{ return _runLoop; } + +- (NSArray*) runLoopModesForAnimating +{ return [_animation runLoopModesForAnimating]; } + +- (void) startAnimation +{ + if(!_running) + { + _running = YES; + [self resetCounters]; + [_animation animatorDidStart]; + [self _animationBegin]; + [self _animationLoop]; + } +} + +- (void) stopAnimation +{ + if(_running) + { + _running = NO; + [self _animationEnd]; + [_animation animatorDidStop]; + } +} + +- (void) startStopAnimation +{ + if(_running) + [self stopAnimation]; + else + [self startAnimation]; +} + +- (BOOL) isAnimationRunning +{ return _running; } + +- (void) _animationBegin +{ [self subclassResponsibility: _cmd]; } + +- (void) _animationLoop +{ [self subclassResponsibility: _cmd]; } + +- (void) _animationEnd +{ [self subclassResponsibility: _cmd]; } + +- (void) stepAnimation +{ + NSTimeInterval thisFrame = [NSDate timeIntervalSinceReferenceDate]; + NSTimeInterval sinceLastFrame = (thisFrame - _lastFrame); + _elapsed += sinceLastFrame; + _lastFrame = thisFrame; + + [_animation animatorStep: _elapsed]; + _frameCount++; +} + +- (void) animationLoopEvent: (NSEvent*) e +{ [self subclassResponsibility: _cmd]; } + +@end + +static NSTimer* _GSTimerBasedAnimator_timer = nil; +static NSMutableSet* _GSTimerBasedAnimator_animators = nil; +static GSTimerBasedAnimator* _GSTimerBasedAnimator_the_one_animator = nil; +static int _GSTimerBasedAnimator_animator_count = 0; + +@implementation GSTimerBasedAnimator + ++ (void) loopsAnimators +{ + switch(_GSTimerBasedAnimator_animator_count) + { + case 0: + break; + case 1: + [_GSTimerBasedAnimator_the_one_animator _animationLoop]; + break; + default: + [[NSNotificationCenter defaultCenter] + postNotificationName: @"GSTimerBasedAnimator_loop" object: self]; + } +} + ++ (void) registerAnimator: (GSTimerBasedAnimator*) anAnimator +{ + if(anAnimator->_timerInterval == 0.0) + { + [[NSNotificationCenter defaultCenter] + addObserver: anAnimator + selector: @selector(_animationLoop) + name: @"GSTimerBasedAnimator_loop" + object: self]; + + if(!_GSTimerBasedAnimator_animator_count++) + _GSTimerBasedAnimator_the_one_animator = anAnimator; + + if(nil==_GSTimerBasedAnimator_animators) + _GSTimerBasedAnimator_animators = [[NSMutableSet alloc] initWithCapacity: 5]; + [_GSTimerBasedAnimator_animators addObject: anAnimator]; + + if(nil==_GSTimerBasedAnimator_timer) + { + _GSTimerBasedAnimator_timer = [NSTimer + scheduledTimerWithTimeInterval: 0.0 + target: self + selector: @selector(loopsAnimators) + userInfo: nil + repeats: YES + ]; + TEST_RETAIN(_GSTimerBasedAnimator_timer); + } + } + else + { + anAnimator->_timer = [NSTimer + scheduledTimerWithTimeInterval: anAnimator->_timerInterval + target: anAnimator + selector: @selector(_animationLoop) + userInfo: nil + repeats: YES + ]; + TEST_RETAIN(anAnimator->_timer); + } +} + ++ (void) unregisterAnimator: (GSTimerBasedAnimator*) anAnimator +{ + if(anAnimator->_timerInterval == 0.0) + { + [[NSNotificationCenter defaultCenter] + removeObserver: anAnimator + name: @"GSTimerBasedAnimator_loop" + object: self]; + + [_GSTimerBasedAnimator_animators removeObject: anAnimator]; + + if(!--_GSTimerBasedAnimator_animator_count) + { + [_GSTimerBasedAnimator_timer invalidate]; + DESTROY(_GSTimerBasedAnimator_timer); + _GSTimerBasedAnimator_the_one_animator = nil; + } + else + if(_GSTimerBasedAnimator_the_one_animator==anAnimator) + _GSTimerBasedAnimator_the_one_animator + = [_GSTimerBasedAnimator_animators anyObject]; + } + else + { + if(anAnimator->_timer != nil) + { + [anAnimator->_timer invalidate]; + DESTROY(anAnimator->_timer); + } + } +} + +- (void) _animationBegin +{ + [[self class] registerAnimator: self]; +} + +- (void) _animationLoop +{ + [self stepAnimation]; +} + +- (void) _animationEnd +{ + [[self class] unregisterAnimator: self]; +} + +@end + +static void _sendAnimationPerformer( GSAnimator* animator ) +{ + [[animator runLoopForAnimating] + performSelector: @selector(_animationLoop) + target: animator + argument: nil + order: 1000000 + modes: [animator runLoopModesForAnimating] + ]; +} + +static void _cancelAnimationPerformer( GSAnimator* animator ) +{ + [[animator runLoopForAnimating] cancelPerformSelectorsWithTarget: animator]; +} + +@implementation GSPerformerBasedAnimator + +- (void) _animationBegin +{ [self _animationLoop]; } + +- (void) _animationLoop +{ + [self stepAnimation]; + if(_running) + _sendAnimationPerformer(self); +} + +- (void) _animationEnd +{ _cancelAnimationPerformer(self); } + +@end + + diff --git a/Source/NSAnimation.m b/Source/NSAnimation.m index 53405ca0b..c902cd212 100644 --- a/Source/NSAnimation.m +++ b/Source/NSAnimation.m @@ -2,7 +2,9 @@ * NSAnimation.m * * Created by Dr. H. Nikolaus Schaller on Sat Mar 06 2006. - * Copyright (c) 2005 DSITRI. + * 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. @@ -25,42 +27,128 @@ */ #include +#include +#include +#include +#include + +// needed by NSViewAnimation +#include +#include + +#include + +/* + * NSAnimation class + */ NSString *NSAnimationProgressMarkNotification = @"NSAnimationProgressMarkNotification"; -NSString *NSViewAnimationTargetKey = @"NSViewAnimationTargetKey"; -NSString *NSViewAnimationStartFrameKey = @"NSViewAnimationStartFrameKey"; -NSString *NSViewAnimationEndFrameKey = @"NSViewAnimationEndFrameKey"; -NSString *NSViewAnimationEffectKey = @"NSViewAnimationEffectKey"; -NSString *NSViewAnimationFadeInEffect = @"NSViewAnimationFadeInEffect"; -NSString *NSViewAnimationFadeOutEffect = @"NSViewAnimationFadeOutEffect"; +#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 + +static INLINE NSComparisonResult +nsanimation_progressMarkSorter( NSAnimationProgress first,NSAnimationProgress second) +{ + float diff = first - second; + return (NSComparisonResult) (diff / fabs(diff)); +} @implementation NSAnimation -- (void) addProgressMark: (NSAnimationProgress) progress ++ (void) initialize { - [self notImplemented: _cmd]; + 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 _animationBlockingMode; + return _blockingMode; } - (NSAnimationCurve) animationCurve { - return _animationCurve; + return _curve; } - (void) clearStartAnimation { - [self notImplemented: _cmd]; + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSAnimationProgressMarkNotification + object: _startAnimation]; + _startAnimation = nil; } - (void) clearStopAnimation { - [self notImplemented: _cmd]; + [[NSNotificationCenter defaultCenter] + removeObserver: self + name: NSAnimationProgressMarkNotification + object: _stopAnimation]; + _stopAnimation = nil; } - (NSAnimationProgress) currentProgress @@ -70,12 +158,40 @@ NSString *NSViewAnimationFadeOutEffect = @"NSViewAnimationFadeOutEffect"; - (float) currentValue { - return _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; + return (_delegate == nil)? nil : GS_GC_UNHIDE(_delegate); } - (NSTimeInterval) duration @@ -88,154 +204,814 @@ NSString *NSViewAnimationFadeOutEffect = @"NSViewAnimationFadeOutEffect"; return _frameRate; } -- (id) initWithDuration: (NSTimeInterval) duration animationCurve: - (NSAnimationCurve) curve +- (id) initWithDuration: (NSTimeInterval)duration + animationCurve: (NSAnimationCurve)curve { if ((self = [super init])) - { - _duration = duration; - _animationCurve = curve; - } + { + _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 +- (id) copyWithZone: (NSZone*)zone { return [self notImplemented: _cmd]; } - (void) dealloc { - [_progressMarks release]; + 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 _isAnimating; + return (_animator != nil)? [_animator isAnimationRunning] : NO; } -- (NSArray *) progressMarks +- (NSArray*) progressMarks { - return _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 +- (NSArray*) runLoopModesForAnimating { return nil; } -- (void) setAnimationBlockingMode: (NSAnimationBlockingMode) mode +- (void) setAnimationBlockingMode: (NSAnimationBlockingMode)mode { - _animationBlockingMode = mode; + _isANewAnimatorNeeded |= (_blockingMode != mode); + _blockingMode = mode; } -- (void) setAnimationCurve: (NSAnimationCurve) curve +- (void) setAnimationCurve: (NSAnimationCurve)curve { - _animationCurve = 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 +- (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 +- (void) setDelegate: (id)delegate { - _delegate = delegate; + _delegate = (delegate == nil)? nil : GS_GC_HIDE(delegate); } -- (void) setDuration: (NSTimeInterval) duration +- (void) setDuration: (NSTimeInterval)duration { _duration = duration; } -- (void) setFrameRate: (float) fps +- (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 *) progress +- (void) setProgressMarks: (NSArray*)marks { - ASSIGN(_progressMarks, progress) ; -} - -- (void) _animate -{ - [self notImplemented: _cmd]; - // call delegate - // estimate delay to keep fps - // create new timer + 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 +- (void) startWhenAnimation: (NSAnimation*)animation + reachesProgress: (NSAnimationProgress)start { - [self notImplemented: _cmd]; + _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 { - [self notImplemented: _cmd]; - // remove any timer - _isAnimating = NO; + if([self isAnimating]) + [_animator stopAnimation]; } -- (void) stopWhenAnimation: (NSAnimation *) animation reachesProgress: - (NSAnimationProgress) stop +- (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]; } -- (void) encodeWithCoder: (NSCoder *) coder -{ - [self notImplemented: _cmd]; -} - -- (id) initWithCoder: (NSCoder *) coder +- (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 +- (id) initWithViewAnimations: (NSArray*)animations { - if ((self = [super init])) - { - _viewAnimations = [animations retain]; - } + self = [self initWithDuration: 0.5 animationCurve: NSAnimationEaseInOut]; + if (self) + { + [self setAnimationBlockingMode: NSAnimationNonblocking]; + _viewAnimations = [animations retain]; + _viewAnimationDesc = nil; + } return self; } - (void) dealloc { - [_viewAnimations release]; + RELEASE(_viewAnimations); + RELEASE(_viewAnimationDesc); [super dealloc]; } -- (void) setWithViewAnimations: (NSArray *) animations +- (void) setViewAnimations: (NSArray*)animations { + if(_viewAnimations != animations) + DESTROY(_viewAnimationDesc); ASSIGN(_viewAnimations, animations) ; } -- (NSArray *) viewAnimations +- (NSArray*) viewAnimations { return _viewAnimations; } -@end +- (void) startAnimation +{ + if(_viewAnimationDesc == nil) + { + unsigned i,c; + c = [_viewAnimations count]; + _viewAnimationDesc = [NSMutableArray arrayWithCapacity: c]; + RETAIN(_viewAnimationDesc); + for(i=0;i