1/*
2   NSAnimation.m
3
4   Created by Dr. H. Nikolaus Schaller on Sat Mar 06 2006.
5   Copyright (c) 2007 Free Software Foundation, Inc.
6
7   Author: Xavier Glattard (xgl) <xavier.glattard@online.fr>
8
9   This file used to be part of the mySTEP Library.
10   This file now is part of the GNUstep GUI Library.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This library is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
20   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; see the file COPYING.LIB.
24   If not, see <http://www.gnu.org/licenses/> or write to the
25   Free Software Foundation, 51 Franklin Street, Fifth Floor,
26   Boston, MA 02110-1301, USA.
27*/
28
29#import <Foundation/NSDate.h>
30#import <Foundation/NSDebug.h>
31#import <Foundation/NSDictionary.h>
32#import <Foundation/NSException.h>
33#import <Foundation/NSLock.h>
34#import <Foundation/NSNotification.h>
35#import <Foundation/NSRunLoop.h>
36#import <Foundation/NSThread.h>
37#import <Foundation/NSValue.h>
38#import <GNUstepBase/GSLock.h>
39
40#import "AppKit/NSAnimation.h"
41#import "AppKit/NSApplication.h"
42// needed by NSViewAnimation
43#import "AppKit/NSView.h"
44#import "AppKit/NSWindow.h"
45
46#include <math.h>
47
48NSString* NSAnimationBlockingRunLoopMode = @"NSAnimationBlockingRunLoopMode";
49
50/*===================*
51 * NSAnimation class *
52 *===================*/
53#define	GSI_ARRAY_NO_RETAIN
54#define	GSI_ARRAY_NO_RELEASE
55#define	GSIArrayItem NSAnimationProgress
56#include <GNUstepBase/GSIArray.h>
57
58// 'reasonable value' ?
59#define GS_ANIMATION_DEFAULT_FRAME_RATE 25.0
60
61static NSArray* _NSAnimationDefaultRunLoopModes;
62
63static inline void
64_GSBezierComputeCoefficients(_GSBezierDesc *b)
65{
66  b->a[0] =     b->p[0];
67  b->a[1] =-3.0*b->p[0] + 3.0*b->p[1];
68  b->a[2] = 3.0*b->p[0] - 6.0*b->p[1] + 3.0*b->p[2];
69  b->a[3] =-    b->p[0] + 3.0*b->p[1] - 3.0*b->p[2] + b->p[3];
70  b->areCoefficientsComputed = YES;
71}
72
73static inline float
74_GSBezierEval(_GSBezierDesc *b, float t)
75{
76  if (!b->areCoefficientsComputed)
77    _GSBezierComputeCoefficients(b);
78  return b->a[0] + t * (b->a[1] + t * (b->a[2] + t * b->a[3]));
79}
80
81static inline float
82_GSBezierDerivEval(_GSBezierDesc *b, float t)
83{
84  if (!b->areCoefficientsComputed)
85    _GSBezierComputeCoefficients(b);
86  return b->a[1] + t * (2.0 * b->a[2] + t * 3.0 * b->a[3]);
87}
88
89static inline void
90_GSRationalBezierComputeBezierDesc(_GSRationalBezierDesc *rb)
91{
92  unsigned i;
93
94  for (i = 0; i < 4; i++)
95    rb->n.p[i] = (rb->d.p[i] = rb->w[i]) * rb->p[i];
96  _GSBezierComputeCoefficients(&rb->n);
97  _GSBezierComputeCoefficients(&rb->d);
98  rb->areBezierDescComputed = YES;
99}
100
101static inline float
102_GSRationalBezierEval(_GSRationalBezierDesc *rb, float t)
103{
104  if (!rb->areBezierDescComputed)
105    _GSRationalBezierComputeBezierDesc(rb);
106  return _GSBezierEval(&(rb->n), t) / _GSBezierEval(&(rb->d), t);
107}
108
109static inline float
110_GSRationalBezierDerivEval(_GSRationalBezierDesc *rb, float t)
111{
112  float h;
113  if (!rb->areBezierDescComputed)
114    _GSRationalBezierComputeBezierDesc(rb);
115  h = _GSBezierEval(&(rb->d), t);
116  return (_GSBezierDerivEval(&(rb->n), t) * h
117          - _GSBezierEval   (&(rb->n), t) * _GSBezierDerivEval(&(rb->d), t))
118    / (h*h);
119}
120
121static
122_NSAnimationCurveDesc _gs_animationCurveDesc[] =
123{
124  // easeInOut : endGrad = startGrad & startGrad <= 1/3
125  { 0.0,1.0,  1.0/3,1.0/3 ,  {{2.0,2.0/3,2.0/3,2.0}} },
126  // easeIn    : endGrad = 1/startGrad & startGrad >= 1/6
127  { 0.0,1.0,  0.25,4.0 ,  {{4.0,3.0,2.0,1.0}} },
128  // easeOut   : endGrad = 1/startGrad & startGrad <= 6
129  { 0.0,1.0,  4.0 ,0.25,  {{1.0,2.0,3.0,4.0}} },
130  // linear (not used)
131  { 0.0,1.0,  1.0 ,1.0 ,  {{1.0,1.0,1.0,1.0}} },
132  // speedInOut: endGrad = startGrad & startGrad >=3
133  { 0.0,1.0,  3.0 ,3.0 ,  {{2.0/3,2.0,2.0,2.0/3}} }
134};
135
136/* Translate the NSAnimationCurveDesc data (start/end points and start/end
137 * gradients) to GSRBezier data (4 control points), then evaluate it.
138 */
139static inline float
140_gs_animationValueForCurve(_NSAnimationCurveDesc *c, float t, float t0)
141{
142  if (!c->isRBezierComputed)
143    {
144      c->rb.p[0] = c->s;
145      c->rb.p[1] = c->s + (c->sg*c->rb.w[0]) / (3*c->rb.w[1]);
146      c->rb.p[2] = c->e - (c->eg*c->rb.w[3]) / (3*c->rb.w[2]);
147      c->rb.p[3] = c->e;
148      _GSRationalBezierComputeBezierDesc (&c->rb);
149      c->isRBezierComputed = YES;
150    }
151  return _GSRationalBezierEval ( &(c->rb), (t-t0) / (1.0-t0) );
152}
153
154@interface NSAnimation (PrivateNotificationCallbacks)
155- (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification;
156- (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification;
157@end
158
159@interface NSAnimation (Private)
160- (void) _gs_didReachProgressMark: (NSAnimationProgress)progress;
161- (void) _gs_startAnimationInOwnLoop;
162- (void) _gs_startThreadedAnimation;
163- (_NSAnimationCurveDesc*) _gs_curveDesc;
164- (NSAnimationProgress) _gs_curveShift;
165@end
166
167NSComparisonResult
168nsanimation_progressMarkSorter(NSAnimationProgress first, NSAnimationProgress second)
169{
170  float diff = first - second;
171  return (NSComparisonResult)(diff / fabs(diff));
172}
173
174/* Thread locking/unlocking support macros.
175 * _isThreaded flag is an ivar that records whether the
176 * NSAnimation is running in thread mode.
177 * __gs_isLocked flag is local to each method and records
178 * whether the thread is locked and must be locked before
179 * the method exits.
180 * Both are needed because _isThreaded is reset when the
181 * NSAnimation stops : that may happen at any time between
182 * a lock/unlock pair.
183 */
184#define _NSANIMATION_LOCKING_SETUP  \
185  BOOL __gs_isLocked = NO;
186
187#define _NSANIMATION_LOCK           \
188  if (_isThreaded)                  \
189  {                                 \
190    NSAssert(__gs_isLocked == NO, NSInternalInconsistencyException); \
191    NSDebugMLLog(@"NSAnimationLock",\
192                 @"LOCK %@", [NSThread currentThread]);\
193    [_isAnimatingLock lock];        \
194    __gs_isLocked = YES;            \
195   }
196
197#define _NSANIMATION_UNLOCK         \
198  if (__gs_isLocked)                \
199  {                                 \
200    /* NSAssert(__gs_isLocked == YES, NSInternalInconsistencyException); */ \
201    NSDebugMLLog(@"NSAnimationLock",\
202                 @"UNLOCK %@", [NSThread currentThread]);\
203    __gs_isLocked = NO;             \
204    [_isAnimatingLock unlock];      \
205  }
206
207@implementation NSAnimation
208
209+ (void) initialize
210{
211  unsigned i;
212
213  for (i = 0; i < 5; i++) // compute Bezier curve parameters...
214    _gs_animationValueForCurve(&_gs_animationCurveDesc[i], 0.0, 0.0);
215  _NSAnimationDefaultRunLoopModes
216    = [[NSArray alloc] initWithObjects:
217        NSDefaultRunLoopMode,
218        NSModalPanelRunLoopMode,
219        NSEventTrackingRunLoopMode,
220        nil];
221}
222
223- (void) addProgressMark: (NSAnimationProgress)progress
224{
225  _NSANIMATION_LOCKING_SETUP;
226
227  if (progress < 0.0) progress = 0.0;
228  if (progress > 1.0) progress = 1.0;
229
230  _NSANIMATION_LOCK;
231  if (GSIArrayCount(_progressMarks) == 0)
232    { // First mark
233      GSIArrayAddItem (_progressMarks,progress);
234      NSDebugMLLog (@"NSAnimationMark",
235                    @"Insert 1st mark for %f (next:#%d)",
236                    progress, _nextMark);
237      _nextMark = (progress >= [self currentProgress])? 0 : 1;
238    }
239  else
240    {
241      unsigned index;
242      index = GSIArrayInsertionPosition (_progressMarks,
243                                         progress,
244                                         &nsanimation_progressMarkSorter);
245      if (_nextMark < GSIArrayCount(_progressMarks))
246        if (index <= _nextMark
247            && progress < GSIArrayItemAtIndex(_progressMarks,_nextMark))
248          _nextMark++;
249      GSIArrayInsertItem (_progressMarks,progress,index);
250      NSDebugMLLog (@"NSAnimationMark",
251                    @"Insert mark #%d/%d for %f (next:#%d)",
252                    index,GSIArrayCount(_progressMarks),progress,_nextMark);
253    }
254  _isCachedProgressMarkNumbersValid = NO;
255
256  _NSANIMATION_UNLOCK;
257}
258
259- (NSAnimationBlockingMode) animationBlockingMode
260{
261  NSAnimationBlockingMode m;
262  _NSANIMATION_LOCKING_SETUP;
263
264  _NSANIMATION_LOCK;
265  m = _blockingMode;
266  _NSANIMATION_UNLOCK;
267  return m;
268}
269
270- (NSAnimationCurve) animationCurve
271{
272  NSAnimationCurve c;
273  _NSANIMATION_LOCKING_SETUP;
274
275  _NSANIMATION_LOCK;
276  c = _curve;
277  _NSANIMATION_UNLOCK;
278  return c;
279}
280
281- (void) clearStartAnimation
282{
283  _NSANIMATION_LOCKING_SETUP;
284
285  _NSANIMATION_LOCK;
286  [[NSNotificationCenter defaultCenter]
287    removeObserver: self
288	      name: NSAnimationProgressMarkNotification
289	    object: _startAnimation];
290  [_startAnimation removeProgressMark: _startMark];
291  _startAnimation = nil;
292  _NSANIMATION_UNLOCK;
293}
294
295- (void) clearStopAnimation
296{
297  _NSANIMATION_LOCKING_SETUP;
298
299  _NSANIMATION_LOCK;
300  [[NSNotificationCenter defaultCenter]
301    removeObserver: self
302	      name: NSAnimationProgressMarkNotification
303	    object: _stopAnimation];
304  [_stopAnimation removeProgressMark: _stopMark];
305  _stopAnimation = nil;
306  _NSANIMATION_UNLOCK;
307}
308
309- (NSAnimationProgress) currentProgress
310{
311  NSAnimationProgress p;
312  _NSANIMATION_LOCKING_SETUP;
313
314  _NSANIMATION_LOCK;
315  p = _currentProgress;
316  _NSANIMATION_UNLOCK;
317  return p;
318}
319
320- (float) currentValue
321{
322  float value;
323  _NSANIMATION_LOCKING_SETUP;
324
325  _NSANIMATION_LOCK;
326
327  if (_delegate_animationValueForProgress)
328    { // method is cached (the animation is running)
329      NSDebugMLLog (@"NSAnimationDelegate",
330                    @"[delegate animationValueForProgress] (cached)");
331      value = (*_delegate_animationValueForProgress)
332                (_currentDelegate,
333                 @selector (animation:valueForProgress:),
334                 self, _currentProgress);
335    }
336  else // method is not cached (the animation did not start yet)
337    if ( _delegate != nil
338         && [_delegate respondsToSelector:
339               @selector (animation:valueForProgress:)] )
340      {
341        NSDebugMLLog (@"NSAnimationDelegate",
342                      @"[delegate animationValueForProgress]");
343        value = [_delegate animation: self
344                                   valueForProgress: _currentProgress];
345      }
346    else // default -- FIXME ??
347      /*    switch (_curve)
348            {
349            case NSAnimationEaseInOut:
350               case NSAnimationEaseIn:
351              case NSAnimationEaseOut:
352           case NSAnimationSpeedInOut:*/
353      value = _gs_animationValueForCurve (
354                &_curveDesc, _currentProgress, _curveProgressShift
355                );
356  /*	break;
357        case NSAnimationLinear:
358        value = _currentProgress; break;
359        }*/
360
361  _NSANIMATION_UNLOCK;
362
363  return value;
364}
365
366- (id) delegate
367{
368  id d;
369  _NSANIMATION_LOCKING_SETUP;
370
371  _NSANIMATION_LOCK;
372  d = (_delegate == nil)? nil : _delegate;
373  _NSANIMATION_UNLOCK;
374  return d;
375}
376
377- (NSTimeInterval) duration
378{
379  NSTimeInterval d;
380  _NSANIMATION_LOCKING_SETUP;
381
382  _NSANIMATION_LOCK;
383  d = _duration;
384  _NSANIMATION_UNLOCK;
385  return d;
386}
387
388- (float) frameRate
389{
390  float f;
391  _NSANIMATION_LOCKING_SETUP;
392
393  _NSANIMATION_LOCK;
394  f = _frameRate;
395  _NSANIMATION_UNLOCK;
396  return f;
397}
398
399- (id) initWithDuration: (NSTimeInterval)duration
400	 animationCurve: (NSAnimationCurve)curve
401{
402  if ((self = [super init]))
403    {
404      if (duration<=0.0)
405        [NSException raise: NSInvalidArgumentException
406                    format: @"%@ Duration must be > 0.0 (passed: %f)",self,duration];
407      _duration = duration;
408      _frameRate = GS_ANIMATION_DEFAULT_FRAME_RATE;
409      _curve = curve;
410      _curveDesc = _gs_animationCurveDesc[_curve];
411      _curveProgressShift = 0.0;
412
413      _currentProgress = 0.0;
414      _progressMarks = NSZoneMalloc ([self zone], sizeof(GSIArray_t));
415      GSIArrayInitWithZoneAndCapacity (_progressMarks, [self zone], 16);
416      _cachedProgressMarkNumbers = NULL;
417      _cachedProgressMarkNumberCount = 0;
418      _isCachedProgressMarkNumbersValid = NO;
419      _nextMark = 0;
420
421      _startAnimation = _stopAnimation = nil;
422      _startMark = _stopMark = 0.0;
423
424      _blockingMode = NSAnimationBlocking;
425      _animator = nil;
426      _isANewAnimatorNeeded = YES;
427
428      _delegate = nil;
429      _delegate_animationDidReachProgressMark =
430        (void (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
431      _delegate_animationValueForProgress =
432        (float (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
433      _delegate_animationDidEnd =
434        (void (*)(id,SEL,NSAnimation*)) NULL;
435      _delegate_animationDidStop =
436        (void (*)(id,SEL,NSAnimation*)) NULL;
437      _delegate_animationShouldStart =
438        (BOOL (*)(id,SEL,NSAnimation*)) NULL;
439
440      _isThreaded = NO;
441      _isAnimatingLock = [GSLazyRecursiveLock new];
442    }
443  return self;
444}
445
446- (id) copyWithZone: (NSZone*)zone
447{
448  NSAnimation *c = (NSAnimation*)NSCopyObject (self, 0, zone);
449
450  c->_progressMarks = GSIArrayCopyWithZone(_progressMarks, zone);
451  c->_animator = nil;
452  c->_isANewAnimatorNeeded = YES;
453  c->_isAnimatingLock = [GSLazyRecursiveLock new];
454  return c;
455}
456
457- (void) dealloc
458{
459  [self stopAnimation];
460
461  GSIArrayEmpty(_progressMarks);
462  NSZoneFree([self zone], _progressMarks);
463  if (_cachedProgressMarkNumbers != NULL)
464    {
465      unsigned i;
466
467      for (i = 0; i < _cachedProgressMarkNumberCount; i++)
468        RELEASE(_cachedProgressMarkNumbers[i]);
469      NSZoneFree([self zone], _cachedProgressMarkNumbers);
470    }
471
472  [self clearStartAnimation];
473  [self clearStopAnimation];
474
475  TEST_RELEASE(_animator);
476  RELEASE(_isAnimatingLock);
477
478  [super dealloc];
479}
480
481- (BOOL) isAnimating
482{
483  BOOL f;
484  _NSANIMATION_LOCKING_SETUP;
485
486  _NSANIMATION_LOCK;
487  f = (_animator != nil) ? [_animator isAnimationRunning] : NO;
488  _NSANIMATION_UNLOCK;
489  return f;
490}
491
492- (NSArray*) progressMarks
493{
494  NSNumber **cpmn;
495  unsigned count;
496  _NSANIMATION_LOCKING_SETUP;
497
498  _NSANIMATION_LOCK;
499
500  count = GSIArrayCount(_progressMarks);
501
502  if (!_isCachedProgressMarkNumbersValid)
503    {
504      unsigned i;
505
506      if (_cachedProgressMarkNumbers != NULL)
507        {
508          for (i = 0; i < _cachedProgressMarkNumberCount; i++)
509            RELEASE(_cachedProgressMarkNumbers[i]);
510          _cachedProgressMarkNumbers =
511           (NSNumber**)NSZoneRealloc([self zone], _cachedProgressMarkNumbers,
512                                     count * sizeof(NSNumber*));
513        }
514      else
515        {
516          _cachedProgressMarkNumbers =
517           (NSNumber**)NSZoneMalloc([self zone], count * sizeof(NSNumber*));
518        }
519
520      for (i = 0; i < count; i++)
521        {
522          _cachedProgressMarkNumbers[i] =
523           [NSNumber numberWithFloat: GSIArrayItemAtIndex (_progressMarks,i)];
524        }
525      _cachedProgressMarkNumberCount = count;
526      _isCachedProgressMarkNumbersValid = YES;
527    }
528
529  cpmn = _cachedProgressMarkNumbers;
530  _NSANIMATION_UNLOCK;
531
532  return [NSArray arrayWithObjects: cpmn count: count];
533}
534
535- (void) removeProgressMark: (NSAnimationProgress)progress
536{
537  NSUInteger index;
538  _NSANIMATION_LOCKING_SETUP;
539
540  _NSANIMATION_LOCK;
541
542  index = GSIArraySearch(_progressMarks, progress,
543                         nsanimation_progressMarkSorter);
544  if (index < GSIArrayCount(_progressMarks)
545      && progress == GSIArrayItemAtIndex (_progressMarks,index))
546    {
547      GSIArrayRemoveItemAtIndex(_progressMarks,index);
548      _isCachedProgressMarkNumbersValid = NO;
549      if (_nextMark > index) _nextMark--;
550      NSDebugMLLog(@"NSAnimationMark",@"Remove mark #%lu (%f) for (next:#%d)",
551                   (unsigned long)index, progress, _nextMark);
552    }
553  else
554    NSWarnMLog(@"Unexistent progress mark");
555
556  _NSANIMATION_UNLOCK;
557}
558
559- (NSArray*) runLoopModesForAnimating
560{
561  return nil;
562}
563
564- (void) setAnimationBlockingMode: (NSAnimationBlockingMode)mode
565{
566  _NSANIMATION_LOCKING_SETUP;
567
568  _NSANIMATION_LOCK;
569  _isANewAnimatorNeeded |= (_blockingMode != mode);
570  _blockingMode = mode;
571  _NSANIMATION_UNLOCK;
572}
573
574- (void) setAnimationCurve: (NSAnimationCurve)curve
575{
576  _NSANIMATION_LOCKING_SETUP;
577
578  _NSANIMATION_LOCK;
579
580  if (_currentProgress <= 0.0f || _currentProgress >= 1.0f)
581    {
582      _curveDesc = _gs_animationCurveDesc[curve];
583    }
584  else
585    { // FIXME ??
586      _GSRationalBezierDesc newrb;
587
588      _GSRationalBezierDesc *rb1 = &(_curveDesc.rb);
589      float t1 = (_currentProgress - _curveProgressShift) / (1.0 - _curveProgressShift);
590      _GSRationalBezierDesc *rb2 = &(_gs_animationCurveDesc[curve].rb);
591      float t2 = _currentProgress;
592      float K;
593      newrb.p[0] = _GSRationalBezierEval ( rb1,   t1        );
594      newrb.w[0] = _GSBezierEval        (&rb1->d,t1        );
595      newrb.w[1] =
596        rb1->w[1]
597        + t1*(   2*( rb1->w[2]           - rb1->w[1] )
598              + t1*( rb1->w[1]           - 2*rb1->w[2]           + rb1->w[3]           ));
599      newrb.p[1] = (
600        rb1->w[1]*rb1->p[1]
601        + t1*(   2*( rb1->w[2]*rb1->p[2] - rb1->w[1]*rb1->p[1] )
602              + t1*( rb1->w[1]*rb1->p[1] - 2*rb1->w[2]*rb1->p[2] + rb1->w[3]*rb1->p[3] ))
603        ) / newrb.w[1];
604      newrb.w[2] = rb2->w[2]           + t2*(rb2->w[3]           - rb2->w[2]          );
605      newrb.p[2] = (
606                    rb2->w[2]*rb2->p[2] + t2*(rb2->w[3]*rb2->p[3] - rb2->w[2]*rb2->p[2])
607                   ) / newrb.w[2];
608
609      // 3rd point is moved to the right by scaling : w3*p3 = w1*p1 + (w1*p1 - w0*p0)
610      K = ( 2*newrb.w[1]*newrb.p[1]-newrb.w[0]*newrb.p[0] ) / (newrb.w[2]*newrb.p[2]);
611      newrb.p[3] = rb2->p[3];
612      newrb.w[3] = rb2->w[3] * K;
613      newrb.w[2] = newrb.w[2]* K;
614
615      _GSRationalBezierComputeBezierDesc (&newrb);
616#if 0
617      NSLog (@"prgrss = %f shift = %f",_currentProgress,_curveProgressShift);
618      switch (curve)
619      { case 0:NSLog (@"EaseInOut t=%f - %f",t1,t2);break;
620        case 1:NSLog (@"EaseIn    t=%f - %f",t1,t2);break;
621        case 2:NSLog (@"EaseOut   t=%f - %f",t1,t2);break;
622        case 3:NSLog (@"Linear    t=%f - %f",t1,t2);break;
623        default:NSLog (@"???");
624      }
625      NSLog (@"a=%f b=%f c=%f d=%f",newrb.p[0],newrb.p[1],newrb.p[2],newrb.p[3]);
626      NSLog (@"  %f   %f   %f   %f",newrb.w[0],newrb.w[1],newrb.w[2],newrb.w[3]);
627#endif
628      _curveProgressShift = _currentProgress;
629      _curveDesc.rb = newrb;
630      _curveDesc.isRBezierComputed = YES;
631    }
632  _curve = curve;
633
634  _NSANIMATION_UNLOCK;
635}
636
637- (void) setCurrentProgress: (NSAnimationProgress)progress
638{
639  BOOL needSearchNextMark = NO;
640  NSAnimationProgress markedProgress;
641  _NSANIMATION_LOCKING_SETUP;
642
643  if (progress < 0.0) progress = 0.0;
644  if (progress > 1.0) progress = 1.0;
645
646  _NSANIMATION_LOCK;
647
648  // NOTE: In the case of a forward jump the marks between the
649  //       previous progress value and the new (excluded) progress
650  //       value are never reached.
651  //       In the case of a backward jump (rewind) the marks will
652  //       be reached again !
653  if (_nextMark < GSIArrayCount(_progressMarks))
654    {
655      markedProgress = GSIArrayItemAtIndex (_progressMarks,_nextMark);
656      if (markedProgress == progress)
657        [self _gs_didReachProgressMark: markedProgress];
658      else
659        {
660          // the following should never happens if the progress
661          // is reached during the normal run of the animation
662          // (method called from animatorStep)
663          if (markedProgress < progress) // forward jump ?
664            needSearchNextMark = YES;
665        }
666    }
667  needSearchNextMark |= progress < _currentProgress; // rewind ?
668
669  if (needSearchNextMark)
670    {
671      _nextMark = GSIArrayInsertionPosition (_progressMarks,progress,&nsanimation_progressMarkSorter);
672
673      if (_nextMark < GSIArrayCount(_progressMarks))
674        NSDebugMLLog(@"NSAnimationMark",@"Next mark #%d for %f",
675                     _nextMark, GSIArrayItemAtIndex(_progressMarks,_nextMark));
676    }
677
678  NSDebugMLLog(@"NSAnimation",@"Progress = %f", progress);
679  _currentProgress = progress;
680
681  if (progress >= 1.0 && _animator != nil)
682    [_animator stopAnimation];
683
684  _NSANIMATION_UNLOCK;
685}
686
687- (void) setDelegate: (id)delegate
688{
689  _NSANIMATION_LOCKING_SETUP;
690
691  _NSANIMATION_LOCK;
692  _delegate = (delegate == nil)? nil : delegate;
693  _NSANIMATION_UNLOCK;
694}
695
696- (void) setDuration: (NSTimeInterval)duration
697{
698  _NSANIMATION_LOCKING_SETUP;
699
700  if (duration<=0.0)
701    [NSException raise: NSInvalidArgumentException
702		format: @"%@ Duration must be > 0.0 (passed: %f)",self,duration];
703  _NSANIMATION_LOCK;
704  _duration = duration;
705  _NSANIMATION_UNLOCK;
706}
707
708- (void) setFrameRate: (float)fps
709{
710  _NSANIMATION_LOCKING_SETUP;
711
712  if (fps<0.0)
713    [NSException raise: NSInvalidArgumentException
714		format: @"%@ Framerate must be >= 0.0 (passed: %f)",self,fps];
715  _NSANIMATION_LOCK;
716  _isANewAnimatorNeeded |= (_frameRate != fps);
717  if ( _frameRate != fps && [self isAnimating] )
718    { // a new animator is needed *now*
719      // FIXME : should I have been smarter ?
720      [self stopAnimation];
721      [self startAnimation];
722    }
723  _frameRate = fps;
724  _NSANIMATION_UNLOCK;
725}
726
727- (void) setProgressMarks: (NSArray*)marks
728{
729  _NSANIMATION_LOCKING_SETUP;
730
731  _NSANIMATION_LOCK;
732  GSIArrayEmpty(_progressMarks);
733  _nextMark = 0;
734  if (marks != nil)
735    {
736      unsigned i, count = [marks count];
737
738      for (i = 0; i < count; i++)
739        [self addProgressMark: [(NSNumber*)[marks objectAtIndex:i] floatValue]];
740    }
741  _isCachedProgressMarkNumbersValid = NO;
742  _NSANIMATION_UNLOCK;
743}
744
745- (void) startAnimation
746{
747  unsigned i;
748
749  if ([self isAnimating])
750    return;
751
752  NSDebugMLLog(@"NSAnimationStart",@"");
753
754  for (i = 0; i < GSIArrayCount(_progressMarks); i++)
755    NSDebugMLLog(@"NSAnimationMark", @"Mark #%d : %f",
756                 i, GSIArrayItemAtIndex(_progressMarks,i));
757
758  if ([self currentProgress] >= 1.0)
759    {
760      [self setCurrentProgress: 0.0];
761      _nextMark = 0;
762    }
763
764  _curveDesc = _gs_animationCurveDesc[_curve];
765  _curveProgressShift = 0.0;
766
767  if (_delegate != nil)
768    {
769      id delegate;
770
771      NSDebugMLLog(@"NSAnimationDelegate", @"Cache delegation methods");
772      // delegation methods are cached while the animation is running
773      delegate = _delegate;
774      _delegate_animationDidReachProgressMark =
775        ([delegate respondsToSelector: @selector (animation:didReachProgressMark:)]) ?
776        (void (*)(id,SEL,NSAnimation*,NSAnimationProgress))
777        [delegate methodForSelector: @selector (animation:didReachProgressMark:)]
778        : NULL;
779      _delegate_animationValueForProgress =
780        ([delegate respondsToSelector: @selector (animation:valueForProgress:)]) ?
781        (float (*)(id,SEL,NSAnimation*,NSAnimationProgress))
782        [delegate methodForSelector: @selector (animation:valueForProgress:)]
783        : NULL;
784      _delegate_animationDidEnd =
785        ([delegate respondsToSelector: @selector (animationDidEnd:)]) ?
786        (void (*)(id,SEL,NSAnimation*))
787        [delegate methodForSelector: @selector (animationDidEnd:)]
788        : NULL;
789      _delegate_animationDidStop =
790        ([delegate respondsToSelector: @selector (animationDidStop:)]) ?
791        (void (*)(id,SEL,NSAnimation*))
792        [delegate methodForSelector: @selector (animationDidStop:)]
793        : NULL;
794      _delegate_animationShouldStart =
795        ([delegate respondsToSelector: @selector (animationShouldStart:)]) ?
796        (BOOL (*)(id,SEL,NSAnimation*))
797        [delegate methodForSelector: @selector (animationShouldStart:)]
798        : NULL;
799      NSDebugMLLog(@"NSAnimationDelegate",
800                   @"Delegation methods : %p %p %p %p %p",
801                   _delegate_animationDidReachProgressMark,
802                   _delegate_animationValueForProgress,
803                   _delegate_animationDidEnd,
804                   _delegate_animationDidStop,
805                   _delegate_animationShouldStart);
806      _currentDelegate = _delegate;
807    }
808  else
809    {
810      NSDebugMLLog(@"NSAnimationDelegate",
811                   @" No delegate : clear delegation methods");
812      _delegate_animationDidReachProgressMark =
813        (void (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
814      _delegate_animationValueForProgress =
815        (float (*)(id,SEL,NSAnimation*,NSAnimationProgress)) NULL;
816      _delegate_animationDidEnd =
817        (void (*)(id,SEL,NSAnimation*)) NULL;
818      _delegate_animationDidStop =
819        (void (*)(id,SEL,NSAnimation*)) NULL;
820      _delegate_animationShouldStart =
821        (BOOL (*)(id,SEL,NSAnimation*)) NULL;
822      _currentDelegate = nil;
823    }
824
825  if (_animator == nil || _isANewAnimatorNeeded)
826    {
827      TEST_RELEASE(_animator);
828
829      _animator = [[GSAnimator allocWithZone: [self zone]]
830                      initWithAnimation: self
831                      frameRate: _frameRate];
832      NSAssert(_animator,@"Can not create a GSAnimator");
833      NSDebugMLLog(@"NSAnimationAnimator", @"New GSAnimator: %@", _animator);
834      _isANewAnimatorNeeded = NO;
835    }
836
837  switch (_blockingMode)
838    {
839      case NSAnimationBlocking:
840        [self _gs_startAnimationInOwnLoop];
841        //[_animator setRunLoopModesForAnimating:
842        //  [NSArray arrayWithObject: NSAnimationBlockingRunLoopMode]];
843        //[_animator startAnimation];
844        break;
845      case NSAnimationNonblocking:
846        {
847          NSArray *runLoopModes;
848
849          runLoopModes = [self runLoopModesForAnimating];
850          if (runLoopModes == nil)
851            runLoopModes = _NSAnimationDefaultRunLoopModes;
852          [_animator setRunLoopModesForAnimating: runLoopModes];
853        }
854        [_animator startAnimation];
855        break;
856      case NSAnimationNonblockingThreaded:
857        _isThreaded = YES;
858        [NSThread
859          detachNewThreadSelector: @selector (_gs_startThreadedAnimation)
860                         toTarget: self
861                       withObject: nil];
862    }
863}
864
865- (void) startWhenAnimation: (NSAnimation*)animation
866	    reachesProgress: (NSAnimationProgress)start
867{
868  _NSANIMATION_LOCKING_SETUP;
869
870  _NSANIMATION_LOCK;
871
872  _startAnimation = animation;
873  _startMark = start;
874
875  [_startAnimation addProgressMark: _startMark];
876  NSDebugMLLog (@"NSAnimationMark",@"register for progress %f", start);
877  [[NSNotificationCenter defaultCenter]
878    addObserver: self
879       selector: @selector (_gs_startAnimationReachesProgressMark:)
880	   name: NSAnimationProgressMarkNotification
881	 object: _startAnimation];
882
883  _NSANIMATION_UNLOCK;
884}
885
886- (void) stopAnimation
887{
888  _NSANIMATION_LOCKING_SETUP;
889
890  if ([self isAnimating])
891    {
892      _NSANIMATION_LOCK;
893      [_animator stopAnimation];
894      _NSANIMATION_UNLOCK;
895    }
896}
897
898- (void) stopWhenAnimation: (NSAnimation*)animation
899	   reachesProgress: (NSAnimationProgress)stop
900{
901  _NSANIMATION_LOCKING_SETUP;
902
903  _NSANIMATION_LOCK;
904
905  _stopAnimation = animation;
906  _stopMark = stop;
907
908  [_stopAnimation addProgressMark: _stopMark];
909  NSDebugMLLog (@"NSAnimationMark",@"register for progress %f", stop);
910  [[NSNotificationCenter defaultCenter]
911    addObserver: self
912       selector: @selector (_gs_stopAnimationReachesProgressMark:)
913	   name: NSAnimationProgressMarkNotification
914	 object: _stopAnimation];
915
916  _NSANIMATION_UNLOCK;
917}
918
919- (void) encodeWithCoder: (NSCoder*)aCoder
920{
921  if ([aCoder allowsKeyedCoding])
922    {
923      [aCoder encodeInt: (int)[self animationCurve]
924                 forKey: @"NSAnimationAnimationCurve"];
925      [aCoder encodeDouble: [self duration]
926                    forKey: @"NSAnimationDuration"];
927    }
928  else
929    {
930      [self notImplemented: _cmd];
931    }
932}
933
934- (id) initWithCoder: (NSCoder*)aCoder
935{
936  if ([aCoder allowsKeyedCoding])
937    {
938      NSTimeInterval duration = 1.0;
939      NSAnimationCurve animationCurve = 0;
940
941      if ([aCoder containsValueForKey: @"NSAnimationAnimationCurve"])
942        {
943          animationCurve = [aCoder decodeIntForKey: @"NSAnimationAnimationCurve"];
944        }
945      if ([aCoder containsValueForKey: @"NSAnimationDuration"])
946        {
947          duration = [aCoder decodeDoubleForKey: @"NSAnimationDuration"];
948        }
949      return [self initWithDuration: duration
950                     animationCurve: animationCurve];
951    }
952  else
953    {
954      [self notImplemented: _cmd];
955    }
956  return self;
957}
958
959/*
960 * protocol GSAnimation (callbacks)
961 */
962
963- (void) animatorDidStart
964{
965  id delegate;
966  _NSANIMATION_LOCKING_SETUP;
967
968  NSDebugMLLog(@"NSAnimationAnimator",@"");
969
970  _NSANIMATION_LOCK;
971
972  delegate = _currentDelegate;
973
974  if (_delegate_animationShouldStart) // method is cached (the animation is running)
975    {
976      NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationShouldStart] (cached)");
977      _delegate_animationShouldStart (delegate,@selector(animationShouldStart:),self);
978    }
979  RETAIN (self);
980
981  _NSANIMATION_UNLOCK;
982}
983
984- (void) animatorDidStop
985{
986  id delegate;
987  _NSANIMATION_LOCKING_SETUP;
988
989  NSDebugMLLog(@"NSAnimationAnimator",@"Progress = %f", _currentProgress);
990
991  _NSANIMATION_LOCK;
992
993  delegate = _currentDelegate;
994  if (_currentProgress < 1.0)
995    {
996      if (_delegate_animationDidStop) // method is cached (the animation is running)
997        {
998          NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationDidStop] (cached)");
999          _delegate_animationDidStop (delegate,@selector(animationDidStop:),self);
1000        }
1001    }
1002  else
1003    {
1004      if (_delegate_animationDidEnd) // method is cached (the animation is running)
1005        {
1006          NSDebugMLLog(@"NSAnimationDelegate",@"[delegate animationDidEnd] (cached)");
1007          _delegate_animationDidEnd (delegate,@selector(animationDidEnd:),self);
1008        }
1009    }
1010  RELEASE (self);
1011
1012  _NSANIMATION_UNLOCK;
1013}
1014
1015- (void) animatorStep: (NSTimeInterval) elapsedTime;
1016{
1017  NSAnimationProgress progress;
1018  _NSANIMATION_LOCKING_SETUP;
1019
1020  NSDebugMLLog(@"NSAnimationAnimator", @"Elapsed time : %f", elapsedTime);
1021
1022  _NSANIMATION_LOCK;
1023
1024  progress = (elapsedTime / _duration);
1025
1026  { // have some marks been passed ?
1027    // NOTE: the case where progress == markedProgress is
1028    //       treated in [-setCurrentProgress]
1029    unsigned count = GSIArrayCount (_progressMarks);
1030    NSAnimationProgress markedProgress;
1031    while ( _nextMark < count
1032            && progress > (markedProgress = GSIArrayItemAtIndex (_progressMarks,_nextMark)) ) // is a mark reached ?
1033      {
1034        [self _gs_didReachProgressMark: markedProgress];
1035      }
1036  }
1037
1038  [self setCurrentProgress: progress];
1039
1040  _NSANIMATION_UNLOCK;
1041}
1042
1043@end //implementation NSAnimation
1044
1045@implementation NSAnimation (PrivateNotificationCallbacks)
1046
1047- (void) _gs_startAnimationReachesProgressMark: (NSNotification*)notification
1048{
1049  NSAnimation *animation;
1050  NSAnimationProgress mark;
1051  _NSANIMATION_LOCKING_SETUP;
1052
1053  _NSANIMATION_LOCK;
1054  animation = [notification object];
1055  mark = [[[notification userInfo] objectForKey: NSAnimationProgressMark] floatValue];
1056
1057  NSDebugMLLog(@"NSAnimationMark",
1058               @"Start Animation %@ reaches %f", animation, mark);
1059
1060  if ( animation == _startAnimation && mark == _startMark)
1061    {
1062  //    [self clearStartAnimation];
1063      [self startAnimation];
1064    }
1065
1066  _NSANIMATION_UNLOCK;
1067}
1068
1069
1070- (void) _gs_stopAnimationReachesProgressMark: (NSNotification*)notification
1071{
1072  NSAnimation *animation;
1073  NSAnimationProgress mark;
1074  _NSANIMATION_LOCKING_SETUP;
1075
1076  _NSANIMATION_LOCK;
1077  animation = [notification object];
1078  mark = [[[notification userInfo] objectForKey: NSAnimationProgressMark] floatValue];
1079
1080  NSDebugMLLog(@"NSAnimationMark",
1081               @"Stop Animation %@ reaches %f",animation, mark);
1082
1083
1084  if ( animation == _stopAnimation && mark == _stopMark)
1085    {
1086  //    [self clearStopAnimation];
1087      [self stopAnimation];
1088    }
1089
1090  _NSANIMATION_UNLOCK;
1091}
1092
1093@end // implementation NSAnimation (PrivateNotificationCallbacks)
1094
1095@implementation NSAnimation (Private)
1096
1097- (void) _gs_didReachProgressMark: (NSAnimationProgress) progress
1098{
1099  _NSANIMATION_LOCKING_SETUP;
1100
1101  NSDebugMLLog(@"NSAnimationMark", @"progress %f", progress);
1102
1103  _NSANIMATION_LOCK;
1104
1105  // calls delegate's method
1106  if (_delegate_animationDidReachProgressMark) // method is cached (the animation is running)
1107    {
1108      NSDebugMLLog(@"NSAnimationDelegate",
1109                   @"[delegate animationdidReachProgressMark] (cached)");
1110      _delegate_animationDidReachProgressMark (_currentDelegate,
1111                                               @selector(animation:didReachProgressMark:),
1112                                               self,progress);
1113    }
1114  else // method is not cached (the animation did not start yet)
1115    if ( _delegate != nil
1116         && [_delegate
1117              respondsToSelector: @selector(animation:didReachProgressMark:)] )
1118      {
1119        NSDebugMLLog(@"NSAnimationDelegate",
1120                     @"[delegate animationdidReachProgressMark]");
1121        [_delegate animation: self didReachProgressMark: progress];
1122      }
1123
1124  // posts a notification
1125  NSDebugMLLog(@"NSAnimationNotification",
1126               @"Post NSAnimationProgressMarkNotification : %f", progress);
1127  [[NSNotificationCenter defaultCenter]
1128    postNotificationName: NSAnimationProgressMarkNotification
1129		  object: self
1130		userInfo: [NSDictionary
1131                            dictionaryWithObject: [NSNumber numberWithFloat: progress]
1132					  forKey: NSAnimationProgressMark
1133			  ]
1134  ];
1135
1136  // skips marks with the same progress value
1137  while (
1138    (++_nextMark) < GSIArrayCount(_progressMarks)
1139    && GSIArrayItemAtIndex(_progressMarks, _nextMark) == progress
1140    )
1141  ;
1142
1143  _NSANIMATION_UNLOCK;
1144
1145  NSDebugMLLog(@"NSAnimationMark",
1146               @"Next mark #%d for %f",
1147               _nextMark, GSIArrayItemAtIndex(_progressMarks, _nextMark - 1));
1148}
1149
1150- (void) _gs_startThreadedAnimation
1151{
1152  // NSAssert(_isThreaded);
1153  CREATE_AUTORELEASE_POOL(pool);
1154  NSDebugMLLog(@"NSAnimationThread",
1155               @"Start of %@", [NSThread currentThread]);
1156  [self _gs_startAnimationInOwnLoop];
1157  NSDebugMLLog(@"NSAnimationThread",
1158               @"End of %@", [NSThread currentThread]);
1159  [pool drain];
1160  _isThreaded = NO;
1161}
1162
1163
1164- (void) _gs_startAnimationInOwnLoop
1165{
1166  NSRunLoop	*loop;
1167  NSDate *end;
1168
1169  [_animator setRunLoopModesForAnimating:
1170    [NSArray arrayWithObject: NSAnimationBlockingRunLoopMode]];
1171  [_animator startAnimation];
1172  loop = [NSRunLoop currentRunLoop];
1173  end = [NSDate distantFuture];
1174  for (;;)
1175    {
1176      if ([loop runMode: NSAnimationBlockingRunLoopMode beforeDate: end] == NO)
1177        {
1178          NSDate	*d;
1179          CREATE_AUTORELEASE_POOL(pool);
1180
1181          d = [loop limitDateForMode: NSAnimationBlockingRunLoopMode];
1182          if (d == nil)
1183            {
1184              [pool drain];
1185              break;	// No inputs and no timers.
1186            }
1187          [NSThread sleepUntilDate: d];
1188          [pool drain];
1189        }
1190    }
1191}
1192
1193- (_NSAnimationCurveDesc*) _gs_curveDesc
1194{ return &self->_curveDesc; }
1195
1196- (NSAnimationProgress) _gs_curveShift
1197{ return _curveProgressShift; }
1198
1199@end // implementation NSAnimation (Private)
1200
1201@implementation NSAnimation (GNUstep)
1202
1203- (unsigned int) frameCount
1204{
1205  unsigned c;
1206  _NSANIMATION_LOCKING_SETUP;
1207
1208  _NSANIMATION_LOCK;
1209  c = (_animator != nil)? [_animator frameCount] : 0;
1210  _NSANIMATION_UNLOCK;
1211  return c;
1212}
1213
1214- (void) resetCounters
1215{
1216  _NSANIMATION_LOCKING_SETUP;
1217
1218  _NSANIMATION_LOCK;
1219  if (_animator != nil) [_animator resetCounters];
1220  _NSANIMATION_UNLOCK;
1221}
1222
1223- (float) actualFrameRate;
1224{
1225  float r;
1226  _NSANIMATION_LOCKING_SETUP;
1227
1228  _NSANIMATION_LOCK;
1229  r = (_animator != nil)? [_animator frameRate] : 0.0;
1230  _NSANIMATION_UNLOCK;
1231  return r;
1232}
1233
1234@end
1235
1236/*=======================*
1237 * NSViewAnimation class *
1238 *=======================*/
1239
1240@interface _GSViewAnimationBaseDesc : NSObject
1241{
1242  id _target;
1243  NSRect _startFrame;
1244  NSRect _endFrame;
1245  NSString* _effect;
1246}
1247
1248- (id) initWithProperties: (NSDictionary*)properties;
1249- (void) setCurrentProgress: (float)progress;
1250- (void) setTargetFrame: (NSRect) frame;
1251
1252@end
1253
1254@interface _GSViewAnimationDesc : _GSViewAnimationBaseDesc
1255{
1256  BOOL _shouldHide;
1257  BOOL _shouldUnhide;
1258}
1259@end
1260
1261@interface _GSWindowAnimationDesc : _GSViewAnimationBaseDesc
1262{
1263  float _startAlpha;
1264}
1265@end
1266
1267@implementation _GSViewAnimationBaseDesc
1268
1269- (id) initWithProperties: (NSDictionary*)properties
1270{
1271  if ([self isMemberOfClass: [_GSViewAnimationBaseDesc class]])
1272    {
1273      NSZone* zone;
1274      id target;
1275
1276      zone = [self zone];
1277      RELEASE (self);
1278      target = [properties objectForKey: NSViewAnimationTargetKey];
1279      if (target != nil)
1280        {
1281          if ([target isKindOfClass: [NSView class]])
1282            self = [[_GSViewAnimationDesc allocWithZone: zone]
1283                      initWithProperties: properties];
1284          else if ([target isKindOfClass: [NSWindow class]])
1285            self = [(_GSWindowAnimationDesc*)[_GSWindowAnimationDesc allocWithZone: zone]
1286                      initWithProperties: properties];
1287          else
1288            [NSException
1289               raise: NSInvalidArgumentException
1290              format: @"Invalid viewAnimation property :"
1291                      @"target is neither a NSView nor a NSWindow"];
1292        }
1293      else
1294        [NSException
1295           raise: NSInvalidArgumentException
1296          format: @"Invalid viewAnimation property :"
1297                  @"target is nil"];
1298    }
1299  else
1300    { // called from a subclass
1301      if ((self = [super init]))
1302        {
1303          NSValue* startValue;
1304          NSValue*   endValue;
1305          _target    = [properties objectForKey: NSViewAnimationTargetKey];
1306          startValue = [properties objectForKey: NSViewAnimationStartFrameKey];
1307          endValue   = [properties objectForKey: NSViewAnimationEndFrameKey];
1308          _effect    = [properties objectForKey: NSViewAnimationEffectKey];
1309
1310          _startFrame = (startValue!=nil) ?
1311            [startValue rectValue]
1312            : [_target frame];
1313          _endFrame = (endValue!=nil) ?
1314            [endValue rectValue]
1315            : [_target frame];
1316        }
1317    }
1318  return self;
1319}
1320
1321- (void) setCurrentProgress: (float)progress
1322{
1323  if (progress < 1.0f)
1324    {
1325      NSRect r;
1326
1327      r.origin.x    = _startFrame.origin.x
1328        + progress*( _endFrame.origin.x - _startFrame.origin.x );
1329      r.origin.y    = _startFrame.origin.y
1330        + progress*( _endFrame.origin.y - _startFrame.origin.y );
1331      r.size.width  = _startFrame.size.width
1332        + progress*( _endFrame.size.width - _startFrame.size.width );
1333      r.size.height = _startFrame.size.height
1334        + progress*( _endFrame.size.height - _startFrame.size.height );
1335
1336      [self setTargetFrame: r];
1337
1338      if (_effect == NSViewAnimationFadeOutEffect)
1339        {
1340          [self subclassResponsibility: _cmd];
1341        }
1342      if (_effect == NSViewAnimationFadeInEffect)
1343        {
1344          [self subclassResponsibility: _cmd];
1345        }
1346    }
1347  else
1348    {
1349      [self setTargetFrame: _endFrame];
1350    }
1351}
1352
1353- (void) setTargetFrame: (NSRect)frame
1354{
1355 [self subclassResponsibility: _cmd];
1356}
1357
1358@end // implementation _GSViewAnimationDesc
1359
1360@implementation _GSViewAnimationDesc
1361
1362- (id) initWithProperties: (NSDictionary*)properties
1363{
1364  if ((self = [super initWithProperties: properties]))
1365    {
1366      _shouldHide = ([properties objectForKey: NSViewAnimationEndFrameKey] == nil);
1367      _shouldUnhide = ( _effect == NSViewAnimationFadeInEffect
1368                        && [_target isHidden]
1369                        && !_shouldHide);
1370    }
1371  return self;
1372}
1373
1374- (void) setCurrentProgress: (float)progress
1375{
1376  [super setCurrentProgress: progress];
1377  if (_effect == NSViewAnimationFadeOutEffect) {}
1378    /* ??? TODO */;
1379  if (_effect == NSViewAnimationFadeInEffect) {}
1380    /* ??? TODO */;
1381
1382  if (progress>=1.0f)
1383    {
1384      if (_shouldHide)
1385        [_target setHidden:YES];
1386      else if (_shouldUnhide)
1387        [_target setHidden:NO];
1388    }
1389}
1390
1391- (void) setTargetFrame: (NSRect)frame
1392{
1393  [_target setFrame: frame];
1394}
1395
1396@end // implementation _GSViewAnimationDesc
1397
1398@implementation _GSWindowAnimationDesc
1399
1400- (id) initWithProperties: (NSDictionary*)properties
1401{
1402  if ((self = [super initWithProperties: properties]))
1403    {
1404      _startAlpha = [_target alphaValue];
1405    }
1406  return self;
1407}
1408
1409- (void) setCurrentProgress: (float)progress
1410{
1411  [super setCurrentProgress: progress];
1412  if (_effect == NSViewAnimationFadeOutEffect)
1413    [_target setAlphaValue: _startAlpha * (1.0f - progress)];
1414  if (_effect == NSViewAnimationFadeInEffect)
1415    [_target setAlphaValue: _startAlpha + (1.0f - _startAlpha) * progress];
1416
1417  if (progress >= 1.0f)
1418    {
1419      if (_effect == NSViewAnimationFadeOutEffect)
1420        [_target orderBack: self];
1421      if (_effect == NSViewAnimationFadeInEffect)
1422        [_target orderFront: self];
1423    }
1424}
1425
1426- (void) setTargetFrame: (NSRect) frame
1427{
1428  [_target setFrame: frame display: YES];
1429}
1430
1431@end // implementation _GSWindowAnimationDesc
1432
1433@implementation NSViewAnimation
1434
1435- (id) initWithViewAnimations: (NSArray*)animations
1436{
1437  self = [self initWithDuration: 0.5 animationCurve: NSAnimationEaseInOut];
1438  if (self)
1439    {
1440      [self setAnimationBlockingMode: NSAnimationNonblocking];
1441      [self setViewAnimations: animations];
1442    }
1443  return self;
1444}
1445
1446- (void) dealloc
1447{
1448  DESTROY(_viewAnimations);
1449  DESTROY(_viewAnimationDesc);
1450  [super dealloc];
1451}
1452
1453- (void) setViewAnimations: (NSArray*)animations
1454{
1455  _NSANIMATION_LOCKING_SETUP;
1456
1457  _NSANIMATION_LOCK;
1458  if (_viewAnimations != animations)
1459    DESTROY(_viewAnimationDesc);
1460  ASSIGN(_viewAnimations, animations) ;
1461  _NSANIMATION_UNLOCK;
1462}
1463
1464- (NSArray*) viewAnimations
1465{
1466  NSArray *a;
1467  _NSANIMATION_LOCKING_SETUP;
1468
1469  _NSANIMATION_LOCK;
1470  a = _viewAnimations;
1471  _NSANIMATION_UNLOCK;
1472  return a;
1473}
1474
1475- (void) startAnimation
1476{
1477  _NSANIMATION_LOCKING_SETUP;
1478
1479  _NSANIMATION_LOCK;
1480  if (_viewAnimationDesc == nil)
1481    {
1482      unsigned int i, c;
1483
1484      c = [_viewAnimations count];
1485      _viewAnimationDesc = [[NSMutableArray alloc] initWithCapacity: c];
1486      for (i = 0; i < c; i++)
1487        {
1488          _GSViewAnimationBaseDesc *vabd;
1489
1490          vabd = [[_GSViewAnimationBaseDesc alloc]
1491                     initWithProperties: [_viewAnimations objectAtIndex:i]];
1492          [_viewAnimationDesc addObject: vabd];
1493          RELEASE(vabd);
1494        }
1495    }
1496  [super startAnimation];
1497  _NSANIMATION_UNLOCK;
1498}
1499
1500- (void) stopAnimation
1501{
1502  _NSANIMATION_LOCKING_SETUP;
1503
1504  _NSANIMATION_LOCK;
1505  [super stopAnimation];
1506  [self setCurrentProgress: 1.0];
1507  _NSANIMATION_UNLOCK;
1508}
1509
1510- (void) _gs_updateViewsWithValue: (NSNumber*) value
1511{
1512  // Runs in main thread : must not call any NSAnimation method to avoid a deadlock
1513  unsigned int i, c;
1514  float v;
1515
1516  v = [value floatValue];
1517  if (_viewAnimationDesc != nil)
1518    for (i = 0, c = [_viewAnimationDesc count]; i < c; i++)
1519      [[_viewAnimationDesc objectAtIndex: i] setCurrentProgress: v];
1520}
1521
1522
1523- (void) setCurrentProgress: (NSAnimationProgress)progress
1524{
1525  _NSANIMATION_LOCKING_SETUP;
1526
1527  _NSANIMATION_LOCK;
1528  [super setCurrentProgress: progress];
1529  [self performSelectorOnMainThread: @selector (_gs_updateViewsWithValue:)
1530                         withObject: [NSNumber numberWithFloat:[self currentValue]]
1531                      waitUntilDone: YES];
1532  _NSANIMATION_UNLOCK;
1533}
1534
1535@end // implementation NSViewAnimation
1536
1537