API  0.9.10
CPAnimationContext.j
Go to the documentation of this file.
1 
2 
3 @typedef Map;
4 
5 var _CPAnimationContextStack = nil,
6  _animationFlushingObserver = nil;
7 
8 @implementation CPAnimationContext : CPObject
9 {
10  double _duration;
11  CAMediaTimingFunction _timingFunction;
12  Function _completionHandlerAgent;
13  Map _animationsByObject;
14 }
15 
17 {
18  var contextStack = [self contextStack],
19  context = [contextStack lastObject];
20 
21  if (!context)
22  {
23  context = [[CPAnimationContext alloc] init];
24 
25  [contextStack addObject:context];
26  [self _scheduleAnimationContextStackFlush];
27  }
28 
29  return context;
30 }
31 
32 + (CPArray)contextStack
33 {
34  if (!_CPAnimationContextStack)
35  _CPAnimationContextStack = [CPArray array];
36 
37  return _CPAnimationContextStack;
38 }
39 
40 + (void)runAnimationGroup:(Function/*(CPAnimationContext context)*/)animationsBlock completionHandler:(Function)aCompletionHandler
41 {
43 
44  var context = [CPAnimationContext currentContext];
45  [context setCompletionHandler:aCompletionHandler];
46 
47  animationsBlock(context);
48 
50 }
51 
52 - (id)init
53 {
54  self = [super init];
55 
56  _duration = 0.0;
57  _timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
58  _completionHandlerAgent = nil;
59  _animationsByObject = new Map();
60 
61  return self;
62 }
63 
64 - (id)copy
65 {
66  var context = [[CPAnimationContext alloc] init];
67  [context setDuration:[self duration]];
68  [context setTimingFunction:[self timingFunction]];
69  [context setCompletionHandler:[self completionHandler]];
70 
71  return context;
72 }
73 
74 + (void)_scheduleAnimationContextStackFlush
75 {
76  if (!_animationFlushingObserver)
77  {
78 #if (DEBUG)
79  CPLog.debug("create new observer");
80 #endif
81  _animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback, 0);
82  CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver);
83  }
84 }
85 
87 {
88  var newContext;
89 
90  if ([_CPAnimationContextStack count])
91  {
92  var currentContext = [_CPAnimationContextStack lastObject];
93  newContext = [currentContext copy];
94  }
95  else
96  {
97  newContext = [[CPAnimationContext alloc] init];
98  }
99 
100  [_CPAnimationContextStack addObject:newContext];
101 }
102 
103 + (BOOL)endGrouping
104 {
105  if (![_CPAnimationContextStack count])
106  return NO;
107 
108  var context = [_CPAnimationContextStack lastObject];
109  [context _flushAnimations];
110  [_CPAnimationContextStack removeLastObject];
111 
112 #if (DEBUG)
113  CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
114 #endif
115  return YES;
116 }
117 
118 - (void)_enqueueActionForObject:(id)anObject keyPath:(id)aKeyPath targetValue:(id)aTargetValue animationCompletion:(id)animationCompletion
119 {
120  var resolvedAction = [self _actionForObject:anObject keyPath:aKeyPath targetValue:aTargetValue animationCompletion:animationCompletion];
121 
122  if (!resolvedAction)
123  return;
124 
125  var animByKeyPath = _animationsByObject.get(anObject);
126 
127  if (!animByKeyPath)
128  {
129  var newAnimByKeyPath = @{aKeyPath:resolvedAction};
130  _animationsByObject.set(anObject, newAnimByKeyPath);
131  }
132  else
133  [animByKeyPath setObject:resolvedAction forKey:aKeyPath];
134 }
135 
136 - (Object)_actionForObject:(id)anObject keyPath:(CPString)aKeyPath targetValue:(id)aTargetValue animationCompletion:(Function)animationCompletion
137 {
138  var animation,
139  duration,
140  animatedKeyPath,
141  values,
142  keyTimes,
143  timingFunctions,
144  needsPeriodicFrameUpdates,
145  objectId = [anObject UID];
146 
147  if (!aKeyPath || !anObject || !(animation = [anObject animationForKey:aKeyPath]) || ![animation isKindOfClass:[CAAnimation class]])
148  return nil;
149 
150  duration = [animation duration] || [self duration];
151  needsPeriodicFrameUpdates = [[anObject animator] needsPeriodicFrameUpdatesForKeyPath:aKeyPath];
152 
153  if (_completionHandlerAgent)
154  _completionHandlerAgent.increment();
155 
156  var animatorClass = [[anObject class] animatorClass];
157 
158  var completionFunction = function()
159  {
160  if (needsPeriodicFrameUpdates)
161  [animatorClass stopUpdaterWithIdentifier:objectId];
162 
163  if (animationCompletion)
164  animationCompletion();
165 
166  if (needsPeriodicFrameUpdates || animationCompletion)
168 
169  if (_completionHandlerAgent)
170  _completionHandlerAgent.decrement();
171  };
172 
173  if (![animation isKindOfClass:[CAPropertyAnimation class]] || !(animatedKeyPath = [animation keyPath]))
174  animatedKeyPath = aKeyPath;
175 
176  if ([animation isKindOfClass:[CAKeyframeAnimation class]])
177  {
178  values = [animation values];
179  keyTimes = [animation keyTimes];
180  timingFunctions = [animation timingFunctionsControlPoints];
181  }
182  else
183  {
184  var isBasicAnimation = [animation isKindOfClass:[CABasicAnimation class]],
185  fromValue,
186  toValue;
187 
188  if (!isBasicAnimation || (fromValue = [animation fromValue]) == nil)
189  fromValue = [anObject valueForKey:animatedKeyPath];
190 
191  if (!isBasicAnimation || (toValue = [animation toValue]) == nil)
192  toValue = aTargetValue;
193 
194  values = [fromValue, toValue];
195  keyTimes = [0, 1];
196  timingFunctions = isBasicAnimation ? [animation timingFunctionControlPoints] : [_timingFunction controlPoints];
197  }
198 
199  return {
200  object:anObject,
201  root:anObject,
202  keypath:animatedKeyPath,
203  values:values,
204  keytimes:keyTimes,
206  timingfunctions:timingFunctions,
207  completion:completionFunction
208  };
209 }
210 
211 - (void)_flushAnimations
212 {
213  if (![_CPAnimationContextStack count])
214  return;
215 
216  if (_animationsByObject.size == 0)
217  {
218  if (_completionHandlerAgent)
219  _completionHandlerAgent.fire();
220  }
221  else
222  [self _startAnimations];
223 }
224 
225 - (void)_startAnimations
226 {
227  var cssAnimations = [],
228  timers = [];
229 
230  _animationsByObject.forEach(function(animByKeyPath, targetView)
231  {
232  [animByKeyPath enumerateKeysAndObjectsUsingBlock:function(aKey, anAction, stop)
233  {
234  [self getAnimations:cssAnimations getTimers:timers usingAction:anAction cssAnimate:YES];
235  }];
236  });
237 
238  _animationsByObject.clear();
239 
240 // start timers
241  var k = timers.length;
242  while(k--)
243  {
244 #if (DEBUG)
245  CPLog.debug("START TIMER " + timers[k].description());
246 #endif
247  timers[k].start();
248  }
249 
250 // start css animations
251  var n = cssAnimations.length;
252  while(n--)
253  {
254 #if (DEBUG)
255  CPLog.debug("START ANIMATION " + cssAnimations[n].description());
256 #endif
257  cssAnimations[n].start();
258  }
259 }
260 
261 - (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers usingAction:(Object)anAction cssAnimate:(BOOL)needsCSSAnimation
262 {
263  var values = anAction.values;
264 
265  if (values.length == 2)
266  {
267  var start = values[0],
268  end = values[1];
269 
270  if (anAction.keypath == @"frame" && CGRectEqualToRect(start, end)
271  || anAction.keypath == @"frameSize" && CGSizeEqualToSize(start, end)
272  || anAction.keypath == @"frameOrigin" && CGPointEqualToPoint(start, end))
273  return;
274  }
275 
276  var targetView = anAction.object,
277  keyPath = anAction.keypath,
278  isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
279  customLayout = [targetView hasCustomLayoutSubviews],
280  customDrawing = [targetView hasCustomDrawRect],
281  declarative_subviews_layout = (!customLayout || [targetView implementsSelector:@selector(frameRectOfView:inSuperviewSize:)]),
282  needsPeriodicFrameUpdates = [[targetView animator] needsPeriodicFrameUpdatesForKeyPath:keyPath],
283  timer = nil,
284  animatorClass = [[targetView class] animatorClass];
285 
286  if (needsCSSAnimation)
287  {
288  [animatorClass addAnimations:cssAnimations forAction:anAction];
289  }
290 
291  if (needsPeriodicFrameUpdates)
292  {
293  [animatorClass addFrameUpdaters:timers forAction:anAction];
294  }
295 
296  var subviews = [targetView subviews],
297  count = [subviews count];
298 
299  if (count && isFrameKeyPath)
300  {
301  [subviews enumerateObjectsUsingBlock:function(aSubview, idx, stop)
302  {
303  if (!declarative_subviews_layout && [aSubview autoresizingMask] == 0)
304  return;
305 
306  var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
307  targetFrame = [action.values lastObject];
308 
309  if (CGRectEqualToRect([aSubview frame], targetFrame))
310  return;
311 
312  if ([aSubview hasCustomDrawRect])
313  {
314  action.completion = function()
315  {
316  [aSubview setFrame:targetFrame];
317 #if (DEBUG)
318  CPLog.debug(aSubview + " setFrame: " + CPStringFromRect(targetFrame));
319 #endif
320  if (idx == count - 1)
321  [animatorClass stopUpdaterWithIdentifier:[anAction.root UID]];
322  };
323  }
324 
325  var animate = !needsPeriodicFrameUpdates;
326  [self getAnimations:cssAnimations getTimers:timers usingAction:action cssAnimate:animate];
327  }];
328  }
329 }
330 
331 - (Object)actionFromAction:(Object)anAction forAnimatedSubview:(CPView)aView
332 {
333  var targetValue = [anAction.values lastObject],
334  startFrame = [aView frame],
335  endFrame,
336  values;
337 
338  if (anAction.keypath == "frame")
339  targetValue = targetValue.size;
340 
341  endFrame = [[aView superview] frameRectOfView:aView inSuperviewSize:targetValue];
342  values = [startFrame, endFrame];
343 
344  return {
345  object:aView,
346  root:anAction.root,
347  keypath:"frame",
348  values:values,
349  keytimes:[0, 1],
350  duration:anAction.duration,
351  timingfunctions:anAction.timingfunctions
352  };
353 }
354 
355 - (void)setCompletionHandler:(Function)aCompletionHandler
356 {
357  if (_completionHandlerAgent)
358  _completionHandlerAgent.invalidate();
359 
360  _completionHandlerAgent = aCompletionHandler ? (new CompletionHandlerAgent(aCompletionHandler)) : nil;
361 }
362 
364 {
365  if (!_completionHandlerAgent)
366  return nil;
367 
368  return _completionHandlerAgent.completionHandler();
369 }
370 
371 @end
372 
374 
375 - (CGRect)frameRectOfView:(CPView)aView inSuperviewSize:(CGSize)aSize
376 {
377  return [aView frameWithNewSuperviewSize:aSize];
378 }
379 
380 - (CGRect)frameWithNewSuperviewSize:(CGSize)newSize
381 {
382  var mask = [self autoresizingMask];
383 
384  if (mask == CPViewNotSizable)
385  return _frame;
386 
387  var oldSize = _superview._frame.size,
388  newFrame = CGRectMakeCopy(_frame),
389  dX = newSize.width - oldSize.width,
390  dY = newSize.height - oldSize.height,
391  evenFractionX = 1.0 / ((mask & CPViewMinXMargin ? 1 : 0) + (mask & CPViewWidthSizable ? 1 : 0) + (mask & CPViewMaxXMargin ? 1 : 0)),
392  evenFractionY = 1.0 / ((mask & CPViewMinYMargin ? 1 : 0) + (mask & CPViewHeightSizable ? 1 : 0) + (mask & CPViewMaxYMargin ? 1 : 0)),
393  baseX = (mask & CPViewMinXMargin ? _frame.origin.x : 0) +
394  (mask & CPViewWidthSizable ? _frame.size.width : 0) +
395  (mask & CPViewMaxXMargin ? oldSize.width - _frame.size.width - _frame.origin.x : 0),
396  baseY = (mask & CPViewMinYMargin ? _frame.origin.y : 0) +
397  (mask & CPViewHeightSizable ? _frame.size.height : 0) +
398  (mask & CPViewMaxYMargin ? oldSize.height - _frame.size.height - _frame.origin.y : 0);
399 
400 
401  if (mask & CPViewMinXMargin)
402  newFrame.origin.x += dX * (baseX > 0 ? _frame.origin.x / baseX : evenFractionX);
403  if (mask & CPViewWidthSizable)
404  newFrame.size.width += dX * (baseX > 0 ? _frame.size.width / baseX : evenFractionX);
405 
406  if (mask & CPViewMinYMargin)
407  newFrame.origin.y += dY * (baseY > 0 ? _frame.origin.y / baseY : evenFractionY);
408  if (mask & CPViewHeightSizable)
409  newFrame.size.height += dY * (baseY > 0 ? _frame.size.height / baseY : evenFractionY);
410 
411  return newFrame;
412 }
413 
414 - (BOOL)hasCustomDrawRect
415 {
416  return self._viewClassFlags & 1;
417 }
418 
419 - (BOOL)hasCustomLayoutSubviews
420 {
421  return self._viewClassFlags & 2;
422 }
423 
424 @end
425 
427 
428 - (CPArray)controlPoints
429 {
430  return [_c1x, _c1y, _c2x, _c2y];
431 }
432 
433 @end
434 
435 @implementation CAAnimation (Additions)
436 
437 - (CPArray)timingFunctionControlPoints
438 {
439  if (_timingFunction)
440  return [_timingFunction controlPoints];
441 
442  return [0, 0, 1, 1];
443 }
444 
445 @end
446 
448 
449 - (CPArray)timingFunctionsControlPoints
450 {
451  var result = [CPArray array];
452 
453  [_timingFunctions enumerateObjectsUsingBlock:function(timingFunction, idx)
454  {
455  [result addObject:[timingFunction controlPoints]];
456  }];
457 
458  return result;
459 }
460 
461 @end
462 
463 var CompletionHandlerAgent = function(aCompletionHandler)
464 {
465  this._completionHandler = aCompletionHandler;
466  this.total = 0;
467  this.valid = true;
468 };
469 
470 CompletionHandlerAgent.prototype.completionHandler = function()
471 {
472  return this._completionHandler;
473 };
474 
475 CompletionHandlerAgent.prototype.fire = function()
476 {
477  this._completionHandler();
478 };
479 
480 CompletionHandlerAgent.prototype.increment = function()
481 {
482  this.total++;
483 };
484 
485 CompletionHandlerAgent.prototype.decrement = function()
486 {
487  if (this.total <= 0)
488  return;
489 
490  this.total--;
491 
492  if (this.valid && this.total == 0)
493  {
494  this.fire();
495  }
496 };
497 
498 CompletionHandlerAgent.prototype.invalidate = function()
499 {
500  this.valid = false;
501 };
502 
503 var _animationFlushingObserverCallback = function()
504 {
505 #if (DEBUG)
506  CPLog.debug("_animationFlushingObserverCallback");
507 #endif
508  if ([_CPAnimationContextStack count] == 1)
509  {
510  var context = [_CPAnimationContextStack lastObject];
511  [context _flushAnimations];
512  [_CPAnimationContextStack removeLastObject];
513  }
514 
515 #if (DEBUG)
516  CPLog.debug("_animationFlushingObserver "+_animationFlushingObserver+" stack:" + [_CPAnimationContextStack count]);
517 #endif
518 
519  if (_animationFlushingObserver && ![_CPAnimationContextStack count])
520  {
521 #if (DEBUG)
522  CPLog.debug("removeObserver");
523 #endif
524  CFRunLoopObserverInvalidate([CPRunLoop mainRunLoop], _animationFlushingObserver);
525  _animationFlushingObserver = nil;
526  }
527 };
528 
530 
534 - (double)duration
535 {
536  return _duration;
537 }
538 
542 - (void)setDuration:(double)aValue
543 {
544  _duration = aValue;
545 }
546 
551 {
552  return _timingFunction;
553 }
554 
558 - (void)setTimingFunction:(CAMediaTimingFunction)aValue
559 {
560  _timingFunction = aValue;
561 }
562 
563 @end
function CFRunLoopAddObserver(runloop, observer, mode)
Definition: CPRunLoop.j:509
function CFRunLoopObserverInvalidate(runloop, observer, mode)
Definition: CPRunLoop.j:520
CPViewWidthSizable
Definition: CPView.j:67
CPViewMaxYMargin
Definition: CPView.j:91
var CompletionHandlerAgent
The main run loop for the application.
Definition: CPRunLoop.h:2
CPString description()
Definition: CPObject.j:358
CPRunLoop currentRunLoop()
Definition: CPRunLoop.j:232
void getAnimations:getTimers:usingAction:cssAnimate:(CPArray cssAnimations, [getTimers] CPArray timers, [usingAction] Object anAction, [cssAnimate] BOOL needsCSSAnimation)
An immutable string (collection of characters).
Definition: CPString.h:2
CPViewMinXMargin
Definition: CPView.j:61
CGRect frameRectOfView:inSuperviewSize:(CPView aView, [inSuperviewSize] CGSize aSize)
CPViewMaxXMargin
Definition: CPView.j:73
global appkit_tag_dom_elements typedef _CPViewFullScreenModeState CPViewNotSizable
Definition: CPView.j:55
CAMediaTimingFunction timingFunction()
Definition: CAAnimation.j:103
void performSelectors()
Definition: CPRunLoop.j:305
typedef Map
id init()
Definition: CPObject.j:145
id functionWithName:(CPString aName)
CGRect frameWithNewSuperviewSize:(CGSize newSize)
CPViewHeightSizable
Definition: CPView.j:85
CPViewMinYMargin
Definition: CPView.j:79
FrameUpdater prototype start
Class class()
Definition: CPObject.j:179
CPView superview()
Definition: CPView.j:486
Object actionFromAction:forAnimatedSubview:(Object anAction, [forAnimatedSubview] CPView aView)
unsigned autoresizingMask()
Definition: CPView.j:1495
CGRect frame()
Definition: CPView.j:1022
CompletionHandlerAgent prototype fire
id alloc()
Definition: CPObject.j:130
Definition: CPView.j:136
CAMediaTimingFunction timingFunction()
function CFRunLoopObserverCreate(activities, repeats, order, callout, context)
Definition: CPRunLoop.j:504