diff --git a/AppKit/CoreAnimation/CPAnimationContext.j b/AppKit/CoreAnimation/CPAnimationContext.j index 022b51d8e..ac90e8943 100644 --- a/AppKit/CoreAnimation/CPAnimationContext.j +++ b/AppKit/CoreAnimation/CPAnimationContext.j @@ -3,12 +3,12 @@ @import "CPView.j" @import +@import @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; diff --git a/AppKit/CoreAnimation/CPViewAnimator.j b/AppKit/CoreAnimation/CPViewAnimator.j index 45dd5470e..3937f4a1e 100644 --- a/AppKit/CoreAnimation/CPViewAnimator.j +++ b/AppKit/CoreAnimation/CPViewAnimator.j @@ -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;