mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 21:17:03 +00:00
441 lines
13 KiB
Plaintext
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 "matrix(" + anAffineTransform.a + ", " + anAffineTransform.b + ", " + anAffineTransform.c + ", " + anAffineTransform.d + ", " + anAffineTransform.tx + (CPBrowserIsEngine(CPGeckoBrowserEngine) ? "px, " : ", ") + anAffineTransform.ty + (CPBrowserIsEngine(CPGeckoBrowserEngine) ? "px)" : ")");
|
|
};
|
|
|
|
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;
|
|
};
|