Files
cappuccino/AppKit/CoreAnimation/CPViewAnimator.j
T

441 lines
13 KiB
Plaintext

@import "_CPObjectAnimator.j"
@import "CPView.j"
@import "CPCompatibility.j"
@import "CSSAnimation.j"
var DEFAULT_CSS_PROPERTIES = nil,
FRAME_UPDATERS = {};
@implementation CPViewAnimator : _CPObjectAnimator
{
BOOL _wantsPeriodicFrameUpdates @accessors(property=wantsPeriodicFrameUpdates);
}
- (id)initWithTarget:(id)aTarget
{
self = [super initWithTarget:aTarget];
_wantsPeriodicFrameUpdates = NO;
return self;
}
- (void)removeFromSuperview
{
[self _setTargetValue:nil withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd];
}
- (void)setHidden:(BOOL)shouldHide
{
if ([_target isHidden] == shouldHide)
return;
if (shouldHide == NO)
return [_target setHidden:NO];
[self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd];
}
- (void)setAlphaValue:(CGPoint)alphaValue
{
[self _setTargetValue:alphaValue withKeyPath:@"alphaValue" setter:_cmd];
}
- (void)setBackgroundColor:(CPColor)aColor
{
[self _setTargetValue:aColor withKeyPath:@"backgroundColor" setter:_cmd];
}
- (void)setFrameOrigin:(CGPoint)aFrameOrigin
{
[self _setTargetValue:aFrameOrigin withKeyPath:@"frameOrigin" setter:_cmd];
}
- (void)setFrame:(CGRect)aFrame
{
[self _setTargetValue:aFrame withKeyPath:@"frame" setter:_cmd];
}
- (void)setFrameSize:(CGSize)aFrameSize
{
[self _setTargetValue:aFrameSize withKeyPath:@"frameSize" setter:_cmd];
}
// Convenience method for the common case where the setter has zero or one argument
- (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath setter:(SEL)aSelector
{
var handler = function()
{
[_target _setForceUpdates:YES];
[_target performSelector:aSelector withObject:aTargetValue];
[_target _setForceUpdates:NO];
};
[self _setTargetValue:aTargetValue withKeyPath:aKeyPath fallback:handler completion:handler];
}
- (void)_setTargetValue:(id)aTargetValue withKeyPath:(CPString)aKeyPath fallback:(Function)fallback completion:(Function)completion
{
var animation = [_target animationForKey:aKeyPath],
context = [CPAnimationContext currentContext];
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || !CPFeatureIsCompatible(CPCSSAnimationFeature))
{
if (fallback)
fallback();
}
else
{
[context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue animationCompletion:completion];
}
}
+ (CPDictionary)_defaultCSSProperties
{
if (DEFAULT_CSS_PROPERTIES == nil)
{
var transformProperty = "transform";
DEFAULT_CSS_PROPERTIES = @{
"backgroundColor" : [@{"property":"background", "value":function(sv, val){return [val cssString];}}],
"alphaValue" : [@{"property":"opacity"}],
"frame" : [@{"property":transformProperty, "value":frameToCSSTranslationTransformMatrix},
@{"property":"width", "value":transformFrameToWidth},
@{"property":"height", "value":transformFrameToHeight}],
"frameOrigin" : [@{"property":transformProperty, "value":frameOriginToCSSTransformMatrix}],
"frameSize" : [@{"property":"width", "value":transformSizeToWidth},
@{"property":"height", "value":transformSizeToHeight}]
};
}
return DEFAULT_CSS_PROPERTIES;
}
+ (void)addAnimations:(CPArray)animations forAction:(id)anAction
{
var target = anAction.object;
return [self _addAnimations:animations forAction:anAction domElement:[target _DOMElement] identifier:[target UID]];
}
+ (void)_addAnimations:(CPArray)animations forAction:(id)anAction domElement:(Object)aDomElement identifier:(CPString)anIdentifier
{
var animation = [animations objectPassingTest:function(anim, idx, stop)
{
return anim.identifier == anIdentifier;
}];
if (animation == nil)
{
animation = new CSSAnimation(aDomElement, anIdentifier, [anAction.object debug_description]);
[animations addObject:animation];
}
var css_mapping = [self _cssPropertiesForKeyPath:anAction.keypath];
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
{
var completionFunction = (anIndex == 0) ? anAction.completion : null,
property = [aDict objectForKey:@"property"],
getter = [aDict objectForKey:@"value"];
animation.addPropertyAnimation(property, getter, anAction.duration, anAction.keytimes, anAction.values, anAction.timingfunctions, completionFunction);
}];
}
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
{
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
}
+ (void)addFrameUpdaters:(CPArray)frameUpdaters forAction:(id)anAction
{
var rootIdentifier = [anAction.root UID];
var frameUpdater = [frameUpdaters objectPassingTest:function(updater, idx, stop)
{
// There is one timer, linked to the top view, that updates the whole hierarchy.
return updater.identifier() == rootIdentifier;
}];
if (frameUpdater == nil)
{
frameUpdater = new FrameUpdater(rootIdentifier);
[frameUpdaters addObject:frameUpdater];
FRAME_UPDATERS[rootIdentifier] = frameUpdater;
}
frameUpdater.addTarget(anAction.object, anAction.keypath, anAction.duration);
}
+ (void)stopUpdaterWithIdentifier:(CPString)anIdentifier
{
var frameUpdater = FRAME_UPDATERS[anIdentifier];
if (frameUpdater)
{
frameUpdater.stop();
delete FRAME_UPDATERS[anIdentifier];
}
else
CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier);
}
- (BOOL)needsPeriodicFrameUpdatesForKeyPath:(CPString)aKeyPath
{
return ((aKeyPath == @"frame" || aKeyPath == @"frameSize") &&
(([_target hasCustomLayoutSubviews] && ![_target implementsSelector:@selector(frameRectOfView:inSuperviewSize:)])
|| [_target hasCustomDrawRect]))
|| [self wantsPeriodicFrameUpdates];
}
@end
var transformFrameToWidth = function(start, current)
{
return current.size.width + "px";
};
var transformFrameToHeight = function(start, current)
{
return current.size.height + "px";
};
var transformSizeToWidth = function(start, current)
{
return current.width + "px";
};
var transformSizeToHeight = function(start, current)
{
return current.height + "px";
};
var CSSStringFromCGAffineTransform = function(anAffineTransform)
{
return [CPString stringWithFormat:@"matrix(%d,%d,%d,%d,%d,%d)", anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty];
};
var frameOriginToCSSTransformMatrix = function(start, current)
{
var affine = CGAffineTransformMakeTranslation(current.x - start.x, current.y - start.y);
return CSSStringFromCGAffineTransform(affine);
};
var frameToCSSTranslationTransformMatrix = function(start, current)
{
var affine = CGAffineTransformMakeTranslation(current.origin.x - start.origin.x, current.origin.y - start.origin.y);
return CSSStringFromCGAffineTransform(affine);
};
@implementation CPView (CPAnimatablePropertyContainer)
+ (Class)animatorClass
{
var anim_class = CPClassFromString(CPStringFromClass(self) + "Animator");
if (anim_class)
return anim_class;
return [[self superclass] animatorClass];
}
- (id)animator
{
if (!_animator)
_animator = [[[[self class] animatorClass] alloc] initWithTarget:self];
return _animator;
}
+ (CAAnimation)defaultAnimationForKey:(CPString)aKey
{
// TODO: remove when supported.
if (aKey == @"CPAnimationTriggerOrderIn")
{
CPLog.warn("CPView animated key path CPAnimationTriggerOrderIn is not supported yet.");
return nil;
}
if ([[self animatorClass] _cssPropertiesForKeyPath:aKey] != nil)
return [CAAnimation animation];
return nil;
}
- (CAAnimation)animationForKey:(CPString)aKey
{
var animations = [self animations],
animation = nil;
if (!animations || !(animation = [animations objectForKey:aKey]))
{
animation = [[self class] defaultAnimationForKey:aKey];
}
return animation;
}
- (CPDictionary)animations
{
return _animationsDictionary;
}
- (void)setAnimations:(CPDictionary)animationsDict
{
_animationsDictionary = [animationsDict copy];
}
- (Object)_DOMElement
{
return _DOMElement;
}
- (CPString)debug_description
{
return [self identifier] || [self className];
}
@end
@implementation CPArray (Additions)
- (CPArray)objectPassingTest:(Function)aFunction
{
var idx = [self indexOfObjectPassingTest:aFunction];
if (idx !== CPNotFound)
return [self objectAtIndex:idx];
return nil;
}
@end
var FrameUpdater = function(anIdentifier)
{
this._identifier = anIdentifier;
this._requestId = null;
this._duration = 0;
this._stop = false;
this._targets = [];
this._callbacks = [];
var frameUpdater = this;
this._updateFunction = function(timestamp)
{
if (frameUpdater._startDate == null)
frameUpdater._startDate = timestamp;
if (frameUpdater._stop)
return;
for (var i = 0; i < frameUpdater._callbacks.length; i++)
frameUpdater._callbacks[i]();
if (timestamp - frameUpdater._startDate < frameUpdater._duration * 1000)
window.requestAnimationFrame(frameUpdater._updateFunction);
};
};
FrameUpdater.prototype.start = function()
{
this._requestId = window.requestAnimationFrame(this._updateFunction);
};
FrameUpdater.prototype.stop = function()
{
CPLog.warn("STOP FrameUpdater" + this._identifier);
// window.cancelAnimationFrame support is Chrome 24, Firefox 23, IE 10, Opera 15, Safari 6.1
if (window.cancelAnimationFrame)
window.cancelAnimationFrame(this._requestId);
this._stop = true;
};
FrameUpdater.prototype.updateFunction = function()
{
return this._updateFunction;
};
FrameUpdater.prototype.identifier = function()
{
return this._identifier;
};
FrameUpdater.prototype.description = function()
{
return "<timer " + this._identifier + " " + this._targets.map(function(t){return [t debug_description];}) + ">";
};
FrameUpdater.prototype.addTarget = function(target, keyPath, duration)
{
var callback = createUpdateFrame(target, keyPath);
if (callback)
{
this._duration = MAX(this._duration, duration);
this._targets.push(target);
this._callbacks.push(callback);
}
};
var createUpdateFrame = function(aView, aKeyPath)
{
if (aKeyPath !== "frame" && aKeyPath !== "frameSize" && aKeyPath !== "frameOrigin")
return nil;
var style = getComputedStyle([aView _DOMElement]),
getCSSPropertyValue = function(prop) {
return ROUND(parseFloat(style.getPropertyValue(prop)));
},
initialOrigin = CGPointMakeCopy([aView frameOrigin]),
transformProperty = CPBrowserStyleProperty("transform"),
updateFrame = function(timestamp)
{
if (aKeyPath === "frameSize")
{
var width = getCSSPropertyValue("width"),
height = getCSSPropertyValue("height");
[aView setFrameSize:CGSizeMake(width, height)];
}
else
{
[aView _setInhibitDOMUpdates:YES];
var matrix_value = style[transformProperty];
if (matrix_value && matrix_value !== 'none')
{
var matrix_array = matrix_value.split('(')[1].split(')')[0].split(','),
x = ROUND(initialOrigin.x + parseFloat(matrix_array[4])),
y = ROUND(initialOrigin.y + parseFloat(matrix_array[5]));
if (aKeyPath === "frame")
{
var width = getCSSPropertyValue("width"),
height = getCSSPropertyValue("height");
[aView setFrame:CGRectMake(x, y, width, height)];
}
else
{
[aView setFrameOrigin:CGPointMake(x, y)];
}
}
[aView _setInhibitDOMUpdates:NO];
}
[[CPRunLoop currentRunLoop] performSelectors];
// CPLog.debug("update " + [aView debug_description]);
};
return updateFrame;
};