Move css animation creation & frame updaters to CPViewAnimator

Reason: • consistency : animations handled by animator.
• Views can now animate several DOM elements. For example
a view with a nine part image need to animate its sub divs.

• Do not create any css animation when the start and end values are
equal and there is no intermediate values.

+ This commit adds an optional method to CPView
- (CGRect)frameRectOfView:(CPView)aView inSuperviewSize:(CGSize)aSize
Returns the frame of a view given its superview frame. This will allow
views implementing -layoutSubviews to gain an optional declarative
layout. The animation system will use this method to compute the end
value of an animation.
This commit is contained in:
cacaodev committed 2017-03-02 10:30:09 +01:00
1 parent cebd51aac1
commit 6bf2d6c1ab
2 files changed
+159 -122

No files matched your search

+45 -88
View File
@@ -3,12 +3,12 @@
@import "CPView.j"
@import <Foundation/CPTimer.j>
@import <Foundation/CPRunLoop.j>
@typedef Map;
var _CPAnimationContextStack = nil,
_animationFlushingObserver = nil,
_animationFrameUpdaters = {};
_animationFlushingObserver = nil;
@implementation CPAnimationContext : CPObject
{
@@ -146,8 +146,8 @@ var _CPAnimationContextStack = nil,
values,
keyTimes,
timingFunctions,
objectId;
needsPeriodicFrameUpdates,
objectId = [anObject UID];
if (!aKeyPath || !anObject || !(animation = [anObject animationForKey:aKeyPath]) || ![animation isKindOfClass:[CAAnimation class]])
return nil;
@@ -158,13 +158,12 @@ var _CPAnimationContextStack = nil,
if (_completionHandlerAgent)
_completionHandlerAgent.increment();
var animatorClass = [[anObject class] animatorClass];
var completionFunction = function()
{
if (needsPeriodicFrameUpdates)
{
[self stopFrameUpdaterWithIdentifier:objectId];
}
[animatorClass stopUpdaterWithIdentifier:objectId];
if (animationCompletion)
animationCompletion();
@@ -266,67 +265,50 @@ var _CPAnimationContextStack = nil,
- (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers usingAction:(Object)anAction cssAnimate:(BOOL)needsCSSAnimation
{
isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
customLayout = [aTargetView hasCustomLayoutSubviews],
customDrawing = [aTargetView hasCustomDrawRect],
var values = anAction.values;
if (values.length == 2)
{
var start = values[0],
end = values[1];
if (anAction.keypath == @"frame" && CGRectEqualToRect(start, end)
|| anAction.keypath == @"frameSize" && CGSizeEqualToSize(start, end)
|| anAction.keypath == @"frameOrigin" && CGPointEqualToPoint(start, end))
return;
}
var targetView = anAction.object,
keyPath = anAction.keypath,
isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
customLayout = [targetView hasCustomLayoutSubviews],
customDrawing = [targetView hasCustomDrawRect],
declarative_subviews_layout = (!customLayout || [targetView implementsSelector:@selector(frameRectOfView:inSuperviewSize:)]),
needsPeriodicFrameUpdates = [[targetView animator] needsPeriodicFrameUpdatesForKeyPath:keyPath],
timer = nil,
animatorClass = [[targetView class] animatorClass];
if (needsCSSAnimation)
{
var identifier = [aTargetView UID],
duration = anAction.duration,
timingFunctions = anAction.timingfunctions,
properties = [],
valueFunctions = [],
cssAnimation = nil;
[cssAnimations enumerateObjectsUsingBlock:function(anim, idx, stop)
{
if (anim.identifier == identifier)
{
cssAnimation = anim;
stop(YES);
}
}];
if (cssAnimation == nil)
{
var domElement = [aTargetView DOMElementForKeyPath:keyPath];
cssAnimation = new CSSAnimation(domElement, identifier);
cssAnimations.push(cssAnimation);
}
var css_mapping = [[aTargetView class] _cssPropertiesForKeyPath:keyPath];
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
{
var completionFunction = (anIndex == 0) ? anAction.completion : null;
var property = [aDict objectForKey:@"property"],
getter = [aDict objectForKey:@"value"];
var targetView = anAction.object,
keyPath = anAction.keypath,
needsPeriodicFrameUpdates = [[targetView animator] needsPeriodicFrameUpdatesForKeyPath:keyPath],
cssAnimation.addPropertyAnimation(property, getter, duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction);
}];
[animatorClass addAnimations:cssAnimations forAction:anAction];
}
if (needsPeriodicFrameUpdates)
{
var timer = [self addFrameUpdaterWithIdentifier:[rootView UID] forView:aTargetView keyPath:keyPath duration:anAction.duration];
if (timer)
timers.push(timer);
[animatorClass addFrameUpdaters:timers forAction:anAction];
}
var subviews = [aTargetView subviews],
count = [subviews count],
customLayout = [aTargetView hasCustomLayoutSubviews];
var subviews = [targetView subviews],
count = [subviews count];
if (count && isFrameKeyPath)
{
[subviews enumerateObjectsUsingBlock:function(aSubview, idx, stop)
{
var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
{
if (!declarative_subviews_layout && [aSubview autoresizingMask] == 0)
return;
var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
targetFrame = [action.values lastObject];
if (CGRectEqualToRect([aSubview frame], targetFrame))
@@ -354,14 +336,15 @@ var _CPAnimationContextStack = nil,
- (Object)actionFromAction:(Object)anAction forAnimatedSubview:(CPView)aView
{
var targetValue = [anAction.values lastObject],
startFrame = [aView frame],
endFrame,
values;
if (anAction.keypath == "frame")
targetValue = targetValue.size;
endFrame = [aView frameWithNewSuperviewSize:targetValue];
values = [[aView frame], endFrame];
endFrame = [[aView superview] frameRectOfView:aView inSuperviewSize:targetValue];
values = [startFrame, endFrame];
return {
object:aView,
@@ -374,36 +357,6 @@ var _CPAnimationContextStack = nil,
};
}
- (Function)addFrameUpdaterWithIdentifier:(CPString)anIdentifier forView:(CPView)aView keyPath:(CPString)aKeyPath duration:(float)aDuration
{
var frameUpdater = _animationFrameUpdaters[anIdentifier],
result = nil;
if (frameUpdater == null)
{
frameUpdater = new FrameUpdater(anIdentifier);
_animationFrameUpdaters[anIdentifier] = frameUpdater;
result = frameUpdater;
}
frameUpdater.addTarget(aView, aKeyPath, aDuration);
return result;
}
- (void)stopFrameUpdaterWithIdentifier:(CPString)anIdentifier
{
var frameUpdater = _animationFrameUpdaters[anIdentifier];
if (frameUpdater)
{
frameUpdater.stop();
delete _animationFrameUpdaters[anIdentifier];
}
else
CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier);
}
- (void)setCompletionHandler:(Function)aCompletionHandler
{
if (_completionHandlerAgent)
@@ -424,11 +377,15 @@ var _CPAnimationContextStack = nil,
@implementation CPView (CPAnimationContext)
- (CGRect)frameRectOfView:(CPView)aView inSuperviewSize:(CGSize)aSize
{
return [aView frameWithNewSuperviewSize:aSize];
}
- (CGRect)frameWithNewSuperviewSize:(CGSize)newSize
{
var mask = [self autoresizingMask];
if (mask == CPViewNotSizable)
return _frame;
+114 -34
View File
@@ -2,6 +2,10 @@
@import "_CPObjectAnimator.j"
@import "CPView.j"
@import "CPCompatibility.j"
@import "CSSAnimation.j"
var DEFAULT_CSS_PROPERTIES = nil,
FRAME_UPDATERS = {};
@implementation CPViewAnimator : _CPObjectAnimator
{
@@ -87,6 +91,97 @@
}
}
+ (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") &&
@@ -136,36 +231,8 @@ var frameToCSSTranslationTransformMatrix = function(start, current)
return CSSStringFromCGAffineTransform(affine);
};
var DEFAULT_CSS_PROPERTIES = nil;
@implementation CPView (CPAnimatablePropertyContainer)
+ (CPDictionary)_defaultCSSProperties
{
if (DEFAULT_CSS_PROPERTIES == nil)
{
var transformProperty = CPBrowserCSSProperty("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;
}
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
{
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
}
+ (Class)animatorClass
{
var anim_class = CPClassFromString(CPStringFromClass(self) + "Animator");
@@ -184,11 +251,6 @@ var DEFAULT_CSS_PROPERTIES = nil;
return _animator;
}
- (id)DOMElementForKeyPath:(CPString)aKeyPath
{
return _DOMElement;
}
+ (CAAnimation)defaultAnimationForKey:(CPString)aKey
{
// TODO: remove when supported.
@@ -198,7 +260,7 @@ var DEFAULT_CSS_PROPERTIES = nil;
return nil;
}
if ([self _cssPropertiesForKeyPath:aKey] !== nil)
if ([[self animatorClass] _cssPropertiesForKeyPath:aKey] !== nil)
return [CAAnimation animation];
return nil;
@@ -227,6 +289,11 @@ var DEFAULT_CSS_PROPERTIES = nil;
_animationsDictionary = [animationsDict copy];
}
- (Object)_DOMElement
{
return _DOMElement;
}
- (CPString)debug_description
{
return [self identifier] || [self className];
@@ -234,6 +301,19 @@ var DEFAULT_CSS_PROPERTIES = nil;
@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;