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