API  1.0.0
CPViewAnimator.j
Go to the documentation of this file.
1 
2 
5 
6 @implementation CPViewAnimator : _CPObjectAnimator
7 {
8  BOOL _wantsPeriodicFrameUpdates;
9 }
10 
11 - (id)initWithTarget:(id)aTarget
12 {
13  self = [super initWithTarget:aTarget];
14 
15  _wantsPeriodicFrameUpdates = NO;
16 
17  return self;
18 }
19 
20 - (void)removeFromSuperview
21 {
22  [self _setTargetValue:nil withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd];
23 }
24 
25 - (void)setHidden:(BOOL)shouldHide
26 {
27  if ([_target isHidden] == shouldHide)
28  return;
29 
30  if (shouldHide == NO)
31  return [_target setHidden:NO];
32 
33  [self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd];
34 }
35 
36 - (void)setAlphaValue:(CGPoint)alphaValue
37 {
38  [self _setTargetValue:alphaValue withKeyPath:@"alphaValue" setter:_cmd];
39 }
40 
41 - (void)setBackgroundColor:(CPColor)aColor
42 {
43  [self _setTargetValue:aColor withKeyPath:@"backgroundColor" setter:_cmd];
44 }
45 
46 - (void)setFrameOrigin:(CGPoint)aFrameOrigin
47 {
48  [self _setTargetValue:aFrameOrigin withKeyPath:@"frameOrigin" setter:_cmd];
49 }
50 
51 - (void)setFrame:(CGRect)aFrame
52 {
53  [self _setTargetValue:aFrame withKeyPath:@"frame" setter:_cmd];
54 }
55 
56 - (void)setFrameSize:(CGSize)aFrameSize
57 {
58  [self _setTargetValue:aFrameSize withKeyPath:@"frameSize" setter:_cmd];
59 }
60 
61 // Convenience method for the common case where the setter has zero or one argument
62 - (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath setter:(SEL)aSelector
63 {
64  var handler = function()
65  {
66  [_target _setForceUpdates:YES];
67  [_target performSelector:aSelector withObject:aTargetValue];
68  [_target _setForceUpdates:NO];
69  };
70 
71  [self _setTargetValue:aTargetValue withKeyPath:aKeyPath fallback:handler completion:handler];
72 }
73 
74 - (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath fallback:(Function)fallback completion:(Function)completion
75 {
76  var animation = [_target animationForKey:aKeyPath],
78 
79  if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || !CPFeatureIsCompatible(CPCSSAnimationFeature))
80  {
81  if (fallback)
82  fallback();
83  }
84  else
85  {
86  [context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue animationCompletion:completion];
87  }
88 }
89 
90 + (CPDictionary)_defaultCSSProperties
91 {
92  if (DEFAULT_CSS_PROPERTIES == nil)
93  {
94  var transformProperty = "transform";
95 
97  "backgroundColor" : [@{"property":"background", "value":function(sv, val){return [val cssString];}}],
98  "alphaValue" : [@{"property":"opacity"}],
99  "frame" : [@{"property":transformProperty, "value":frameToCSSTranslationTransformMatrix},
100  @{"property":"width", "value":transformFrameToWidth},
101  @{"property":"height", "value":transformFrameToHeight}],
102  "frameOrigin" : [@{"property":transformProperty, "value":frameOriginToCSSTransformMatrix}],
103  "frameSize" : [@{"property":"width", "value":transformSizeToWidth},
104  @{"property":"height", "value":transformSizeToHeight}]
105  };
106  }
107 
108  return DEFAULT_CSS_PROPERTIES;
109 }
110 
111 + (void)addAnimations:(CPArray)animations forAction:(id)anAction
112 {
113  var target = anAction.object;
114 
115  return [self _addAnimations:animations forAction:anAction domElement:[target _DOMElement] identifier:[target UID]];
116 }
117 
118 + (void)_addAnimations:(CPArray)animations forAction:(id)anAction domElement:(Object)aDomElement identifier:(CPString)anIdentifier
119 {
120  var animation = [animations objectPassingTest:function(anim, idx, stop)
121  {
122  return anim.identifier == anIdentifier;
123  }];
124 
125  if (animation == nil)
126  {
127  animation = new CSSAnimation(aDomElement, anIdentifier, [anAction.object debug_description]);
128  [animations addObject:animation];
129  }
130 
131  var css_mapping = [self _cssPropertiesForKeyPath:anAction.keypath];
132 
133  [css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
134  {
135  var completionFunction = (anIndex == 0) ? anAction.completion : null,
136  property = [aDict objectForKey:@"property"],
137  getter = [aDict objectForKey:@"value"];
138 
139  animation.addPropertyAnimation(property, getter, anAction.duration, anAction.keytimes, anAction.values, anAction.timingfunctions, completionFunction);
140  }];
141 }
142 
143 + (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
144 {
145  return [[self _defaultCSSProperties] objectForKey:aKeyPath];
146 }
147 
148 + (void)addFrameUpdaters:(CPArray)frameUpdaters forAction:(id)anAction
149 {
150  var rootIdentifier = [anAction.root UID];
151 
152  var frameUpdater = [frameUpdaters objectPassingTest:function(updater, idx, stop)
153  {
154  // There is one timer, linked to the top view, that updates the whole hierarchy.
155  return updater.identifier() == rootIdentifier;
156  }];
157 
158  if (frameUpdater == nil)
159  {
160  frameUpdater = new FrameUpdater(rootIdentifier);
161  [frameUpdaters addObject:frameUpdater];
162  FRAME_UPDATERS[rootIdentifier] = frameUpdater;
163  }
164 
165  frameUpdater.addTarget(anAction.object, anAction.keypath, anAction.duration);
166 }
167 
168 + (void)stopUpdaterWithIdentifier:(CPString)anIdentifier
169 {
170  var frameUpdater = FRAME_UPDATERS[anIdentifier];
171 
172  if (frameUpdater)
173  {
174  frameUpdater.stop();
175  delete FRAME_UPDATERS[anIdentifier];
176  }
177  else
178  CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier);
179 }
180 
181 - (BOOL)needsPeriodicFrameUpdatesForKeyPath:(CPString)aKeyPath
182 {
183  return ((aKeyPath == @"frame" || aKeyPath == @"frameSize") &&
184  (([_target hasCustomLayoutSubviews] && ![_target implementsSelector:@selector(frameRectOfView:inSuperviewSize:)])
185  || [_target hasCustomDrawRect]))
186  || [self wantsPeriodicFrameUpdates];
187 }
188 
189 @end
190 
191 var transformFrameToWidth = function(start, current)
192 {
193  return current.size.width + "px";
194 };
195 
196 var transformFrameToHeight = function(start, current)
197 {
198  return current.size.height + "px";
199 };
200 
201 var transformSizeToWidth = function(start, current)
202 {
203  return current.width + "px";
204 };
205 
206 var transformSizeToHeight = function(start, current)
207 {
208  return current.height + "px";
209 };
210 
211 var CSSStringFromCGAffineTransform = function(anAffineTransform)
212 {
213  return [CPString stringWithFormat:@"matrix(%d,%d,%d,%d,%d,%d)", anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty];
214 };
215 
216 var frameOriginToCSSTransformMatrix = function(start, current)
217 {
218  var affine = CGAffineTransformMakeTranslation(current.x - start.x, current.y - start.y);
219 
220  return CSSStringFromCGAffineTransform(affine);
221 };
222 
224 {
225  var affine = CGAffineTransformMakeTranslation(current.origin.x - start.origin.x, current.origin.y - start.origin.y);
226 
227  return CSSStringFromCGAffineTransform(affine);
228 };
229 
231 
232 + (Class)animatorClass
233 {
234  var anim_class = CPClassFromString(CPStringFromClass(self) + "Animator");
235 
236  if (anim_class)
237  return anim_class;
238 
239  return [[self superclass] animatorClass];
240 }
241 
242 - (id)animator
243 {
244  if (!_animator)
245  _animator = [[[[self class] animatorClass] alloc] initWithTarget:self];
246 
247  return _animator;
248 }
249 
250 + (CAAnimation)defaultAnimationForKey:(CPString)aKey
251 {
252  // TODO: remove when supported.
253  if (aKey == @"CPAnimationTriggerOrderIn")
254  {
255  CPLog.warn("CPView animated key path CPAnimationTriggerOrderIn is not supported yet.");
256  return nil;
257  }
258 
259  if ([[self animatorClass] _cssPropertiesForKeyPath:aKey] !== nil)
260  return [CAAnimation animation];
261 
262  return nil;
263 }
264 
265 - (CAAnimation)animationForKey:(CPString)aKey
266 {
267  var animations = [self animations],
268  animation = nil;
269 
270  if (!animations || !(animation = [animations objectForKey:aKey]))
271  {
272  animation = [[self class] defaultAnimationForKey:aKey];
273  }
274 
275  return animation;
276 }
277 
278 - (CPDictionary)animations
279 {
280  return _animationsDictionary;
281 }
282 
283 - (void)setAnimations:(CPDictionary)animationsDict
284 {
285  _animationsDictionary = [animationsDict copy];
286 }
287 
288 - (Object)_DOMElement
289 {
290  return _DOMElement;
291 }
292 
293 - (CPString)debug_description
294 {
295  return [self identifier] || [self className];
296 }
297 
298 @end
299 
300 @implementation CPArray (Additions)
301 
302 - (CPArray)objectPassingTest:(Function)aFunction
303 {
304  var idx = [self indexOfObjectPassingTest:aFunction];
305 
306  if (idx !== CPNotFound)
307  return [self objectAtIndex:idx];
308 
309  return nil;
310 }
311 @end
312 
313 var FrameUpdater = function(anIdentifier)
314 {
315  this._identifier = anIdentifier;
316  this._requestId = null;
317  this._duration = 0;
318  this._stop = false;
319  this._targets = [];
320  this._callbacks = [];
321 
322  var frameUpdater = this;
323 
324  this._updateFunction = function(timestamp)
325  {
326  if (frameUpdater._startDate == null)
327  frameUpdater._startDate = timestamp;
328 
329  if (frameUpdater._stop)
330  return;
331 
332  for (var i = 0; i < frameUpdater._callbacks.length; i++)
333  frameUpdater._callbacks[i]();
334 
335  if (timestamp - frameUpdater._startDate < frameUpdater._duration * 1000)
336  window.requestAnimationFrame(frameUpdater._updateFunction);
337  };
338 };
339 
340 FrameUpdater.prototype.start = function()
341 {
342  this._requestId = window.requestAnimationFrame(this._updateFunction);
343 };
344 
345 FrameUpdater.prototype.stop = function()
346 {
347  CPLog.warn("STOP FrameUpdater" + this._identifier);
348 
349  // window.cancelAnimationFrame support is Chrome 24, Firefox 23, IE 10, Opera 15, Safari 6.1
350  if (window.cancelAnimationFrame)
351  window.cancelAnimationFrame(this._requestId);
352 
353  this._stop = true;
354 };
355 
356 FrameUpdater.prototype.updateFunction = function()
357 {
358  return this._updateFunction;
359 };
360 
361 FrameUpdater.prototype.identifier = function()
362 {
363  return this._identifier;
364 };
365 
366 FrameUpdater.prototype.description = function()
367 {
368  return "<timer " + this._identifier + " " + this._targets.map(function(t){return [t debug_description];}) + ">";
369 };
370 
371 FrameUpdater.prototype.addTarget = function(target, keyPath, duration)
372 {
373  var callback = createUpdateFrame(target, keyPath);
374 
375  if (callback)
376  {
377  this._duration = MAX(this._duration, duration);
378  this._targets.push(target);
379  this._callbacks.push(callback);
380  }
381 };
382 
383 var createUpdateFrame = function(aView, aKeyPath)
384 {
385  if (aKeyPath !== "frame" && aKeyPath !== "frameSize" && aKeyPath !== "frameOrigin")
386  return nil;
387 
388  var style = getComputedStyle([aView _DOMElement]),
389  getCSSPropertyValue = function(prop) {
390  return ROUND(parseFloat(style.getPropertyValue(prop)));
391  },
392  initialOrigin = CGPointMakeCopy([aView frameOrigin]),
393  transformProperty = CPBrowserStyleProperty("transform"),
394  updateFrame = function(timestamp)
395  {
396  if (aKeyPath === "frameSize")
397  {
398  var width = getCSSPropertyValue("width"),
399  height = getCSSPropertyValue("height");
400 
401  [aView setFrameSize:CGSizeMake(width, height)];
402  }
403  else
404  {
405  [aView _setInhibitDOMUpdates:YES];
406 
407  var matrix_value = style[transformProperty];
408 
409  if (matrix_value && matrix_value !== 'none')
410  {
411  var matrix_array = matrix_value.split('(')[1].split(')')[0].split(','),
412  x = ROUND(initialOrigin.x + parseFloat(matrix_array[4])),
413  y = ROUND(initialOrigin.y + parseFloat(matrix_array[5]));
414 
415  if (aKeyPath === "frame")
416  {
417  var width = getCSSPropertyValue("width"),
418  height = getCSSPropertyValue("height");
419 
420  [aView setFrame:CGRectMake(x, y, width, height)];
421  }
422  else
423  {
424  [aView setFrameOrigin:CGPointMake(x, y)];
425  }
426  }
427 
428  [aView _setInhibitDOMUpdates:NO];
429  }
430 
432  // CPLog.debug("update " + [aView debug_description]);
433  };
434 
435  return updateFrame;
436 };
437 
439 
443 - (BOOL)wantsPeriodicFrameUpdates
444 {
445  return _wantsPeriodicFrameUpdates;
446 }
447 
451 - (void)setWantsPeriodicFrameUpdates:(BOOL)aValue
452 {
453  _wantsPeriodicFrameUpdates = aValue;
454 }
455 
456 @end
BOOL isHidden()
Definition: CALayer.j:597
function CPStringFromClass(aClass)
Definition: CPObjJRuntime.j:38
var transformSizeToHeight
The main run loop for the application.
Definition: CPRunLoop.h:2
int width
var FrameUpdater
var DEFAULT_CSS_PROPERTIES
Definition: CPViewAnimator.j:3
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CPRunLoop currentRunLoop()
Definition: CPRunLoop.j:232
CPCSSAnimationFeature
CPDictionary animations()
CSSAnimation
Definition: CSSAnimation.j:37
BOOL wantsPeriodicFrameUpdates()
An immutable string (collection of characters).
Definition: CPString.h:2
var CSSStringFromCGAffineTransform
function CPFeatureIsCompatible(aFeature)
function CGAffineTransformMakeTranslation(tx, ty)
var frameOriginToCSSTransformMatrix
function CPBrowserStyleProperty(aProperty)
void performSelectors()
Definition: CPRunLoop.j:305
var transformFrameToWidth
CPNotFound
Definition: CPObjJRuntime.j:62
var transformFrameToHeight
FrameUpdater prototype start
var FRAME_UPDATERS
Definition: CPViewAnimator.j:4
var frameToCSSTranslationTransformMatrix
var createUpdateFrame
var transformSizeToWidth
function CPClassFromString(aClassName)
Definition: CPObjJRuntime.j:33
CPDictionary copy()
Definition: CPDictionary.j:292
id stringWithFormat:(CPString format, [,] ...)
Definition: CPString.j:166