mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
Merge pull request #2278 from cacaodev/CPAnimationContext
Animations: CPAnimationContext & animator
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
@import "CPAccordionView.j"
|
||||
@import "CPAlert.j"
|
||||
@import "CPAnimation.j"
|
||||
@import "CPAnimationContext.j"
|
||||
@import "CPAppearance.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPArrayController.j"
|
||||
@@ -107,6 +108,7 @@
|
||||
@import "CPTreeNode.j"
|
||||
@import "CPUserDefaultsController.j"
|
||||
@import "CPView.j"
|
||||
@import "CPViewAnimator.j"
|
||||
@import "CPViewAnimation.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPVisualEffectView.j"
|
||||
|
||||
@@ -369,6 +369,18 @@ function CPBrowserStyleProperty(aProperty)
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil;
|
||||
break;
|
||||
|
||||
case 'animationend':
|
||||
var candidates = {
|
||||
'WebkitAnimation' : 'webkitAnimationEnd',
|
||||
'MozAnimation' : 'animationend',
|
||||
'OAnimation' : 'oAnimationEnd',
|
||||
'msAnimation' : 'MSAnimationEnd',
|
||||
'animation' : 'animationend'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['animation']] || nil;
|
||||
break;
|
||||
|
||||
default:
|
||||
var prefixes = ["Webkit", "Moz", "O", "ms"],
|
||||
strippedProperty = aProperty.split('-').join(' '),
|
||||
|
||||
@@ -243,6 +243,9 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
CPMutableArray _trackingAreas @accessors(getter=trackingAreas, copy);
|
||||
BOOL _inhibitUpdateTrackingAreas;
|
||||
|
||||
id _animator;
|
||||
CPDictionary _animationsDictionary;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -389,6 +392,9 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
_DOMImageSizes = [];
|
||||
#endif
|
||||
|
||||
_animator = nil;
|
||||
_animationsDictionary = @{};
|
||||
|
||||
[self _setupViewFlags];
|
||||
[self _loadThemeAttributes];
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@
|
||||
*/
|
||||
@implementation CAAnimation : CPObject
|
||||
{
|
||||
BOOL _isRemovedOnCompletion;
|
||||
id _delegate;
|
||||
BOOL _isRemovedOnCompletion;
|
||||
id _delegate;
|
||||
CAMediaTimingFunction _timingFunction @accessors(property=timingFunction);
|
||||
double _duration @accessors(property=duration);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -47,8 +49,10 @@
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_isRemovedOnCompletion = YES;
|
||||
_isRemovedOnCompletion = YES;
|
||||
_timingFunction = nil;
|
||||
_duration = 0.0;
|
||||
_delegate = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -102,7 +106,7 @@
|
||||
- (CAMediaTimingFunction)timingFunction
|
||||
{
|
||||
// Linear Pacing
|
||||
return nil;
|
||||
return _timingFunction;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -127,131 +131,4 @@
|
||||
[anObject addAnimation:self forKey:aKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
@implementation CAPropertyAnimation : CAAnimation
|
||||
{
|
||||
CPString _keyPath;
|
||||
|
||||
BOOL _isCumulative;
|
||||
BOOL _isAdditive;
|
||||
}
|
||||
|
||||
+ (id)animationWithKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
var animation = [self animation];
|
||||
|
||||
[animation setKeyPath:aKeyPath];
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (void)setKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
_keyPath = aKeyPath;
|
||||
}
|
||||
|
||||
- (CPString)keyPath
|
||||
{
|
||||
return _keyPath;
|
||||
}
|
||||
|
||||
- (void)setCumulative:(BOOL)isCumulative
|
||||
{
|
||||
_isCumulative = isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)cumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)isCumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (void)setAdditive:(BOOL)isAdditive
|
||||
{
|
||||
_isAdditive = isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)additive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)isAdditive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
A CABasicAnimation is a simple animation that moves a
|
||||
CALayer from one point to another over a specified
|
||||
period of time.
|
||||
*/
|
||||
@implementation CABasicAnimation : CAPropertyAnimation
|
||||
{
|
||||
id _fromValue;
|
||||
id _toValue;
|
||||
id _byValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the starting position for the animation.
|
||||
@param aValue the animation starting position
|
||||
*/
|
||||
- (void)setFromValue:(id)aValue
|
||||
{
|
||||
_fromValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's starting position.
|
||||
*/
|
||||
- (id)fromValue
|
||||
{
|
||||
return _fromValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the ending position for the animation.
|
||||
@param aValue the animation ending position
|
||||
*/
|
||||
- (void)setToValue:(id)aValue
|
||||
{
|
||||
_toValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's ending position.
|
||||
*/
|
||||
- (id)toValue
|
||||
{
|
||||
return _toValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the optional byValue for animation interpolation.
|
||||
@param aValue the byValue
|
||||
*/
|
||||
- (void)setByValue:(id)aValue
|
||||
{
|
||||
_byValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's byValue.
|
||||
*/
|
||||
- (id)byValue
|
||||
{
|
||||
return _byValue;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -0,0 +1,84 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CAPropertyAnimation.j"
|
||||
|
||||
/*!
|
||||
A CABasicAnimation is a simple animation that moves a
|
||||
CALayer from one point to another over a specified
|
||||
period of time.
|
||||
*/
|
||||
/*!
|
||||
A CABasicAnimation is a simple animation that moves a
|
||||
CALayer from one point to another over a specified
|
||||
period of time.
|
||||
*/
|
||||
@implementation CABasicAnimation : CAPropertyAnimation
|
||||
{
|
||||
id _fromValue;
|
||||
id _toValue;
|
||||
id _byValue;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_fromValue = nil;
|
||||
_toValue = nil;
|
||||
_byValue = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the starting position for the animation.
|
||||
@param aValue the animation starting position
|
||||
*/
|
||||
- (void)setFromValue:(id)aValue
|
||||
{
|
||||
_fromValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's starting position.
|
||||
*/
|
||||
- (id)fromValue
|
||||
{
|
||||
return _fromValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the ending position for the animation.
|
||||
@param aValue the animation ending position
|
||||
*/
|
||||
- (void)setToValue:(id)aValue
|
||||
{
|
||||
_toValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's ending position.
|
||||
*/
|
||||
- (id)toValue
|
||||
{
|
||||
return _toValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the optional byValue for animation interpolation.
|
||||
@param aValue the byValue
|
||||
*/
|
||||
- (void)setByValue:(id)aValue
|
||||
{
|
||||
_byValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's byValue.
|
||||
*/
|
||||
- (id)byValue
|
||||
{
|
||||
return _byValue;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CAPropertyAnimation.j"
|
||||
|
||||
@implementation CAKeyframeAnimation : CAPropertyAnimation
|
||||
{
|
||||
CPArray _values @accessors(property=values);
|
||||
CPArray _keyTimes @accessors(property=keyTimes);
|
||||
CPArray _timingFunctions @accessors(property=timingFunctions);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_values = [CPArray array];
|
||||
_keyTimes = [CPArray array];
|
||||
_timingFunctions = [CPArray array];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CAAnimation.j"
|
||||
|
||||
@implementation CAPropertyAnimation : CAAnimation
|
||||
{
|
||||
CPString _keyPath;
|
||||
|
||||
BOOL _isCumulative;
|
||||
BOOL _isAdditive;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_keyPath = nil;
|
||||
_isCumulative = NO;
|
||||
_isAdditive = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)animationWithKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
var animation = [self animation];
|
||||
|
||||
[animation setKeyPath:aKeyPath];
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (void)setKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
_keyPath = aKeyPath;
|
||||
}
|
||||
|
||||
- (CPString)keyPath
|
||||
{
|
||||
return _keyPath;
|
||||
}
|
||||
|
||||
- (void)setCumulative:(BOOL)isCumulative
|
||||
{
|
||||
_isCumulative = isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)cumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)isCumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (void)setAdditive:(BOOL)isAdditive
|
||||
{
|
||||
_isAdditive = isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)additive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)isAdditive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,707 @@
|
||||
@import "CABasicAnimation.j"
|
||||
@import "CAKeyframeAnimation.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@import <Foundation/CPTimer.j>
|
||||
|
||||
@import "jshashtable.j"
|
||||
@import "CSSAnimation.j"
|
||||
|
||||
@typedef HashTable;
|
||||
|
||||
var _CPAnimationContextStack = nil,
|
||||
_animationFlushingObserver = nil,
|
||||
_animationFrameUpdaters = {};
|
||||
|
||||
@implementation CPAnimationContext : CPObject
|
||||
{
|
||||
double _duration @accessors(property=duration);
|
||||
CAMediaTimingFunction _timingFunction @accessors(property=timingFunction);
|
||||
Function _completionHandlerAgent;
|
||||
HashTable _animationsByObject;
|
||||
}
|
||||
|
||||
+ (id)currentContext
|
||||
{
|
||||
var contextStack = [self contextStack],
|
||||
context = [contextStack lastObject];
|
||||
|
||||
if (!context)
|
||||
{
|
||||
context = [[CPAnimationContext alloc] init];
|
||||
|
||||
[contextStack addObject:context];
|
||||
[self _scheduleAnimationContextStackFlush];
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
+ (CPArray)contextStack
|
||||
{
|
||||
if (!_CPAnimationContextStack)
|
||||
_CPAnimationContextStack = [CPArray array];
|
||||
|
||||
return _CPAnimationContextStack;
|
||||
}
|
||||
|
||||
+ (void)runAnimationGroup:(Function/*(CPAnimationContext context)*/)animationsBlock completionHandler:(Function)aCompletionHandler
|
||||
{
|
||||
[CPAnimationContext beginGrouping];
|
||||
|
||||
var context = [CPAnimationContext currentContext];
|
||||
[context setCompletionHandler:aCompletionHandler];
|
||||
|
||||
animationsBlock(context);
|
||||
|
||||
[CPAnimationContext endGrouping];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_duration = 0.0;
|
||||
_timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
_completionHandlerAgent = nil;
|
||||
_animationsByObject = new Hashtable();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var context = [[CPAnimationContext alloc] init];
|
||||
[context setDuration:[self duration]];
|
||||
[context setTimingFunction:[self timingFunction]];
|
||||
[context setCompletionHandler:[self completionHandler]];
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
+ (void)_scheduleAnimationContextStackFlush
|
||||
{
|
||||
if (!_animationFlushingObserver)
|
||||
{
|
||||
CPLog.debug("create new observer");
|
||||
_animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback,0);
|
||||
CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver);
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)beginGrouping
|
||||
{
|
||||
var newContext;
|
||||
|
||||
if ([_CPAnimationContextStack count])
|
||||
{
|
||||
var currentContext = [_CPAnimationContextStack lastObject];
|
||||
newContext = [currentContext copy];
|
||||
}
|
||||
else
|
||||
{
|
||||
newContext = [[CPAnimationContext alloc] init];
|
||||
}
|
||||
|
||||
[_CPAnimationContextStack addObject:newContext];
|
||||
}
|
||||
|
||||
+ (BOOL)endGrouping
|
||||
{
|
||||
if (![_CPAnimationContextStack count])
|
||||
return NO;
|
||||
|
||||
var context = [_CPAnimationContextStack lastObject];
|
||||
[context _flushAnimations];
|
||||
[_CPAnimationContextStack removeLastObject];
|
||||
|
||||
CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_enqueueActionForObject:(id)anObject keyPath:(id)aKeyPath targetValue:(id)aTargetValue animationCompletion:(id)animationCompletion
|
||||
{
|
||||
var resolvedAction = [self _actionForObject:anObject keyPath:aKeyPath targetValue:aTargetValue animationCompletion:animationCompletion];
|
||||
|
||||
if (!resolvedAction)
|
||||
return;
|
||||
|
||||
var animByKeyPath = _animationsByObject.get(anObject);
|
||||
|
||||
if (!animByKeyPath)
|
||||
{
|
||||
var newAnimByKeyPath = @{aKeyPath:resolvedAction};
|
||||
_animationsByObject.put(anObject, newAnimByKeyPath);
|
||||
}
|
||||
else
|
||||
[animByKeyPath setObject:resolvedAction forKey:aKeyPath];
|
||||
}
|
||||
|
||||
- (Object)_actionForObject:(id)anObject keyPath:(CPString)aKeyPath targetValue:(id)aTargetValue animationCompletion:(Function)animationCompletion
|
||||
{
|
||||
var animation,
|
||||
duration,
|
||||
animatedKeyPath,
|
||||
values,
|
||||
keyTimes,
|
||||
timingFunctions,
|
||||
objectId;
|
||||
|
||||
if (!aKeyPath || !anObject || !(animation = [anObject animationForKey:aKeyPath]) || ![animation isKindOfClass:[CAAnimation class]])
|
||||
return nil;
|
||||
|
||||
duration = [animation duration] || [self duration];
|
||||
|
||||
var needsFrameTimer = (aKeyPath == @"frame" || aKeyPath == @"frameSize") &&
|
||||
([anObject hasCustomLayoutSubviews] || [anObject hasCustomDrawRect]) &&
|
||||
(objectId = [anObject UID]);
|
||||
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.increment();
|
||||
|
||||
var completionFunction = function()
|
||||
{
|
||||
if (needsFrameTimer)
|
||||
[self stopFrameUpdaterWithIdentifier:objectId];
|
||||
else if (animationCompletion)
|
||||
animationCompletion();
|
||||
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.decrement();
|
||||
};
|
||||
|
||||
if (![animation isKindOfClass:[CAPropertyAnimation class]] || !(animatedKeyPath = [animation keyPath]))
|
||||
animatedKeyPath = aKeyPath;
|
||||
|
||||
if ([animation isKindOfClass:[CAKeyframeAnimation class]])
|
||||
{
|
||||
values = [animation values];
|
||||
keyTimes = [animation keyTimes];
|
||||
timingFunctions = [animation timingFunctionsControlPoints];
|
||||
}
|
||||
else
|
||||
{
|
||||
var isBasicAnimation = [animation isKindOfClass:[CABasicAnimation class]],
|
||||
fromValue,
|
||||
toValue;
|
||||
|
||||
if (!isBasicAnimation || (fromValue = [animation fromValue]) == nil)
|
||||
fromValue = [anObject valueForKey:animatedKeyPath];
|
||||
|
||||
if (!isBasicAnimation || (toValue = [animation toValue]) == nil)
|
||||
toValue = aTargetValue;
|
||||
|
||||
values = [fromValue, toValue];
|
||||
keyTimes = [0, 1];
|
||||
timingFunctions = isBasicAnimation ? [animation timingFunctionControlPoints] : [_timingFunction controlPoints];
|
||||
}
|
||||
|
||||
return {
|
||||
object:anObject,
|
||||
keypath:animatedKeyPath,
|
||||
values:values,
|
||||
keytimes:keyTimes,
|
||||
duration:duration,
|
||||
timingfunctions:timingFunctions,
|
||||
completion:completionFunction
|
||||
};
|
||||
}
|
||||
|
||||
- (void)_flushAnimations
|
||||
{
|
||||
if (![_CPAnimationContextStack count])
|
||||
return;
|
||||
|
||||
if (_animationsByObject.size() == 0)
|
||||
{
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.fire();
|
||||
}
|
||||
else
|
||||
[self _startAnimations];
|
||||
}
|
||||
|
||||
- (void)_startAnimations
|
||||
{
|
||||
var targetViews = _animationsByObject.keys(),
|
||||
cssAnimations = [],
|
||||
timers = [];
|
||||
|
||||
[targetViews enumerateObjectsUsingBlock:function(targetView, idx, stop)
|
||||
{
|
||||
var animByKeyPath = _animationsByObject.get(targetView);
|
||||
|
||||
[animByKeyPath enumerateKeysAndObjectsUsingBlock:function(aKey, anAction, stop)
|
||||
{
|
||||
[self getAnimations:cssAnimations getTimers:timers forView:targetView usingAction:anAction rootView:targetView cssAnimate:YES];
|
||||
}];
|
||||
|
||||
_animationsByObject.remove(targetView);
|
||||
}];
|
||||
|
||||
// start timers
|
||||
var k = timers.length;
|
||||
while(k--)
|
||||
{
|
||||
CPLog.debug("START TIMER " + timers[k].identifier());
|
||||
timers[k].start();
|
||||
}
|
||||
|
||||
// start css animations
|
||||
var n = cssAnimations.length;
|
||||
while(n--)
|
||||
{
|
||||
CPLog.debug("START ANIMATION " + cssAnimations[n].animationsnames);
|
||||
cssAnimations[n].start();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers forView:(CPView)aTargetView usingAction:(Object)anAction rootView:(CPView)rootView cssAnimate:(BOOL)needsCSSAnimation
|
||||
{
|
||||
var keyPath = anAction.keypath,
|
||||
isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
|
||||
customLayout = [aTargetView hasCustomLayoutSubviews],
|
||||
customDrawing = [aTargetView hasCustomDrawRect],
|
||||
needsFrameTimer = isFrameKeyPath && (customLayout || customDrawing);
|
||||
|
||||
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"];
|
||||
|
||||
cssAnimation.addPropertyAnimation(property, getter, duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction);
|
||||
}];
|
||||
|
||||
if (needsFrameTimer)
|
||||
cssAnimation.setRemoveAnimationPropertyOnCompletion(false);
|
||||
}
|
||||
|
||||
if (needsFrameTimer)
|
||||
{
|
||||
var timer = [self addFrameUpdaterWithIdentifier:[rootView UID] forView:aTargetView keyPath:keyPath duration:anAction.duration];
|
||||
|
||||
if (timer)
|
||||
timers.push(timer);
|
||||
}
|
||||
|
||||
var subviews = [aTargetView subviews],
|
||||
count = [subviews count];
|
||||
|
||||
if (count && isFrameKeyPath)
|
||||
{
|
||||
var frameTimerId = [rootView UID],
|
||||
lastIndex = count - 1;
|
||||
|
||||
[subviews enumerateObjectsUsingBlock:function(aSubview, idx, stop)
|
||||
{
|
||||
var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
|
||||
targetFrame = [action.values lastObject];
|
||||
|
||||
if (CGRectEqualToRect([aSubview frame], targetFrame))
|
||||
return;
|
||||
|
||||
if ([aSubview hasCustomDrawRect])
|
||||
{
|
||||
action.completion = function()
|
||||
{
|
||||
[aSubview setFrame:targetFrame];
|
||||
CPLog.debug(aSubview + " setFrame: ");
|
||||
|
||||
if (idx == lastIndex)
|
||||
[self stopFrameUpdaterWithIdentifier:frameTimerId];
|
||||
};
|
||||
}
|
||||
|
||||
[self getAnimations:cssAnimations getTimers:timers forView:aSubview usingAction:action rootView:rootView cssAnimate:!customLayout];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (Object)actionFromAction:(Object)anAction forAnimatedSubview:(CPView)aView
|
||||
{
|
||||
var targetValue = [anAction.values lastObject],
|
||||
endFrame,
|
||||
values;
|
||||
|
||||
if (anAction.keypath == @"frame")
|
||||
targetValue = targetValue.size;
|
||||
|
||||
endFrame = [aView frameWithNewSuperviewSize:targetValue];
|
||||
values = [[aView frame], endFrame];
|
||||
|
||||
return {
|
||||
object:aView,
|
||||
keypath:"frame",
|
||||
values:values,
|
||||
keytimes:[0, 1],
|
||||
duration:anAction.duration,
|
||||
timingfunctions:anAction.timingFunctions
|
||||
};
|
||||
}
|
||||
|
||||
- (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)
|
||||
_completionHandlerAgent.invalidate();
|
||||
|
||||
_completionHandlerAgent = aCompletionHandler ? (new CompletionHandlerAgent(aCompletionHandler)) : nil;
|
||||
}
|
||||
|
||||
- (void)completionHandler
|
||||
{
|
||||
if (!_completionHandlerAgent)
|
||||
return nil;
|
||||
|
||||
return _completionHandlerAgent.completionHandler();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPView (CPAnimationContext)
|
||||
|
||||
- (CGRect)frameWithNewSuperviewSize:(CGSize)newSize
|
||||
{
|
||||
var mask = [self autoresizingMask];
|
||||
|
||||
if (mask == CPViewNotSizable)
|
||||
return _frame;
|
||||
|
||||
var oldSize = _superview._frame.size,
|
||||
newFrame = CGRectMakeCopy(_frame),
|
||||
dX = newSize.width - oldSize.width,
|
||||
dY = newSize.height - oldSize.height,
|
||||
evenFractionX = 1.0 / ((mask & CPViewMinXMargin ? 1 : 0) + (mask & CPViewWidthSizable ? 1 : 0) + (mask & CPViewMaxXMargin ? 1 : 0)),
|
||||
evenFractionY = 1.0 / ((mask & CPViewMinYMargin ? 1 : 0) + (mask & CPViewHeightSizable ? 1 : 0) + (mask & CPViewMaxYMargin ? 1 : 0)),
|
||||
baseX = (mask & CPViewMinXMargin ? _frame.origin.x : 0) +
|
||||
(mask & CPViewWidthSizable ? _frame.size.width : 0) +
|
||||
(mask & CPViewMaxXMargin ? oldSize.width - _frame.size.width - _frame.origin.x : 0),
|
||||
baseY = (mask & CPViewMinYMargin ? _frame.origin.y : 0) +
|
||||
(mask & CPViewHeightSizable ? _frame.size.height : 0) +
|
||||
(mask & CPViewMaxYMargin ? oldSize.height - _frame.size.height - _frame.origin.y : 0);
|
||||
|
||||
|
||||
if (mask & CPViewMinXMargin)
|
||||
newFrame.origin.x += dX * (baseX > 0 ? _frame.origin.x / baseX : evenFractionX);
|
||||
if (mask & CPViewWidthSizable)
|
||||
newFrame.size.width += dX * (baseX > 0 ? _frame.size.width / baseX : evenFractionX);
|
||||
|
||||
if (mask & CPViewMinYMargin)
|
||||
newFrame.origin.y += dY * (baseY > 0 ? _frame.origin.y / baseY : evenFractionY);
|
||||
if (mask & CPViewHeightSizable)
|
||||
newFrame.size.height += dY * (baseY > 0 ? _frame.size.height / baseY : evenFractionY);
|
||||
|
||||
return newFrame;
|
||||
}
|
||||
|
||||
- (BOOL)hasCustomDrawRect
|
||||
{
|
||||
return self._viewClassFlags & 1;
|
||||
}
|
||||
|
||||
- (BOOL)hasCustomLayoutSubviews
|
||||
{
|
||||
return self._viewClassFlags & 2;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CAMediaTimingFunction (Additions)
|
||||
|
||||
- (CPArray)controlPoints
|
||||
{
|
||||
return [_c1x, _c1y, _c2x, _c2y];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CAAnimation (Additions)
|
||||
|
||||
- (CPArray)timingFunctionControlPoints
|
||||
{
|
||||
if (_timingFunction)
|
||||
return [_timingFunction controlPoints];
|
||||
|
||||
return [0, 0, 1, 1];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CAKeyframeAnimation (Additions)
|
||||
|
||||
- (CPArray)timingFunctionsControlPoints
|
||||
{
|
||||
var result = [CPArray array];
|
||||
|
||||
[_timingFunctions enumerateObjectsUsingBlock:function(timingFunction, idx)
|
||||
{
|
||||
[result addObject:[timingFunction controlPoints]];
|
||||
}];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CompletionHandlerAgent = function(aCompletionHandler)
|
||||
{
|
||||
this._completionHandler = aCompletionHandler;
|
||||
this.total = 0;
|
||||
this.valid = true;
|
||||
};
|
||||
|
||||
CompletionHandlerAgent.prototype.completionHandler = function()
|
||||
{
|
||||
return this._completionHandler;
|
||||
};
|
||||
|
||||
CompletionHandlerAgent.prototype.fire = function()
|
||||
{
|
||||
this._completionHandler();
|
||||
};
|
||||
|
||||
CompletionHandlerAgent.prototype.increment = function()
|
||||
{
|
||||
this.total++;
|
||||
};
|
||||
|
||||
CompletionHandlerAgent.prototype.decrement = function()
|
||||
{
|
||||
if (this.total <= 0)
|
||||
return;
|
||||
|
||||
this.total--;
|
||||
|
||||
if (this.valid && this.total == 0)
|
||||
{
|
||||
this.fire();
|
||||
}
|
||||
};
|
||||
|
||||
CompletionHandlerAgent.prototype.invalidate = function()
|
||||
{
|
||||
this.valid = false;
|
||||
};
|
||||
|
||||
var _animationFlushingObserverCallback = function()
|
||||
{
|
||||
CPLog.debug("_animationFlushingObserverCallback");
|
||||
if ([_CPAnimationContextStack count] == 1)
|
||||
{
|
||||
var context = [_CPAnimationContextStack lastObject];
|
||||
[context _flushAnimations];
|
||||
[_CPAnimationContextStack removeLastObject];
|
||||
}
|
||||
|
||||
CPLog.debug("_animationFlushingObserver "+_animationFlushingObserver+" stack:" + [_CPAnimationContextStack count]);
|
||||
|
||||
if (_animationFlushingObserver && ![_CPAnimationContextStack count])
|
||||
{
|
||||
CPLog.debug("removeObserver");
|
||||
CFRunLoopObserverInvalidate([CPRunLoop mainRunLoop], _animationFlushingObserver);
|
||||
_animationFlushingObserver = nil;
|
||||
}
|
||||
};
|
||||
|
||||
CFRunLoopObserver = function(activities, repeats, order, callout, context)
|
||||
{
|
||||
this.activities = activities;
|
||||
this.repeats = repeats;
|
||||
this.order = order;
|
||||
this.callout = callout;
|
||||
this.context = context;
|
||||
|
||||
this.isvalid = true;
|
||||
};
|
||||
|
||||
CFRunLoopObserverCreate = function(activities, repeats, order, callout, context)
|
||||
{
|
||||
return new CFRunLoopObserver(activities, repeats, order, callout, context);
|
||||
};
|
||||
|
||||
CFRunLoopAddObserver = function(runloop, observer, mode)
|
||||
{
|
||||
var observers = runloop._observers;
|
||||
|
||||
if (!observers)
|
||||
observers = (runloop._observers = []);
|
||||
|
||||
if (observers.indexOf(observer) == -1)
|
||||
observers.push(observer);
|
||||
};
|
||||
|
||||
CFRunLoopObserverInvalidate = function(runloop, observer, mode)
|
||||
{
|
||||
CFRunLoopRemoveObserver(runloop, observer, mode);
|
||||
};
|
||||
|
||||
CFRunLoopRemoveObserver = function(runloop, observer, mode)
|
||||
{
|
||||
var observers = runloop._observers;
|
||||
if (observers)
|
||||
{
|
||||
var idx = observers.indexOf(observer);
|
||||
if (idx !== -1)
|
||||
{
|
||||
observers.splice(idx, 1);
|
||||
|
||||
if (observers.length == 0)
|
||||
runloop._observers = nil;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var FrameUpdater = function(anIdentifier)
|
||||
{
|
||||
this._identifier = anIdentifier;
|
||||
this._duration = 0;
|
||||
this._stop = false;
|
||||
this._targets = [];
|
||||
this._callbacks = [];
|
||||
var frameUpdater = this;
|
||||
|
||||
this._updateFunction = function(timestamp)
|
||||
{
|
||||
if (frameUpdater._stop)
|
||||
return;
|
||||
|
||||
if (this._startDate == null)
|
||||
this._startDate = timestamp;
|
||||
|
||||
for (var i = 0; i < frameUpdater._callbacks.length; i++)
|
||||
frameUpdater._callbacks[i]();
|
||||
|
||||
if (timestamp - this._startDate < frameUpdater._duration * 1000)
|
||||
window.requestAnimationFrame(frameUpdater._updateFunction);
|
||||
};
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.start = function()
|
||||
{
|
||||
window.requestAnimationFrame(this._updateFunction);
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.stop = function()
|
||||
{
|
||||
CPLog.debug("stop FrameUpdater with id " + this.identifier());
|
||||
|
||||
this._stop = true;
|
||||
|
||||
var targets = this._targets;
|
||||
|
||||
for (var i = 0; i < targets.length; i++)
|
||||
{
|
||||
CPLog.debug(targets[i] + " Remove animation-name property");
|
||||
targets[i]._DOMElement.style.removeProperty(CPBrowserCSSProperty("animation-name"));
|
||||
}
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.updateFunction = function()
|
||||
{
|
||||
return this._updateFunction;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.identifier = function()
|
||||
{
|
||||
return this._identifier;
|
||||
};
|
||||
|
||||
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")
|
||||
return nil;
|
||||
|
||||
var style = getComputedStyle(aView._DOMElement);
|
||||
|
||||
var updateFrame = function(timestamp)
|
||||
{
|
||||
var width = ROUND(style.getPropertyCSSValue('width').getFloatValue(CSSPrimitiveValue.CSS_PX)),
|
||||
height = ROUND(style.getPropertyCSSValue('height').getFloatValue(CSSPrimitiveValue.CSS_PX));
|
||||
|
||||
if (aKeyPath == "frame")
|
||||
{
|
||||
var left = ROUND(style.getPropertyCSSValue('left').getFloatValue(CSSPrimitiveValue.CSS_PX)),
|
||||
top = ROUND(style.getPropertyCSSValue('top').getFloatValue(CSSPrimitiveValue.CSS_PX)),
|
||||
frame = CGRectMake(left, top, width, height);
|
||||
|
||||
[aView setFrame:frame];
|
||||
}
|
||||
else if (aKeyPath == "frameSize")
|
||||
{
|
||||
[aView setFrameSize:CGSizeMake(width, height)];
|
||||
}
|
||||
|
||||
[[CPRunLoop currentRunLoop] performSelectors];
|
||||
};
|
||||
|
||||
return updateFrame;
|
||||
};
|
||||
@@ -0,0 +1,222 @@
|
||||
|
||||
@import "_CPObjectAnimator.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@implementation CPViewAnimator : _CPObjectAnimator
|
||||
{
|
||||
}
|
||||
|
||||
- (void)viewWillMoveToSuperview:(CPView)aSuperview
|
||||
{
|
||||
var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"];
|
||||
|
||||
if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]])
|
||||
{
|
||||
[_target setValue:[orderInAnim fromValue] forKeyPath:[orderInAnim keyPath]];
|
||||
}
|
||||
|
||||
[_target viewWillMoveToSuperview:aSuperview];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToSuperview
|
||||
{
|
||||
var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"];
|
||||
|
||||
if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]])
|
||||
{
|
||||
[self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderIn" fallback:nil completion:function()
|
||||
{
|
||||
[_target setValue:[orderInAnim toValue] forKeyPath:[orderInAnim keyPath]];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_target viewDidMoveToSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 performSelector:aSelector withObject:aTargetValue];
|
||||
};
|
||||
|
||||
[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]) || ![_CPObjectAnimator supportsCSSAnimations])
|
||||
{
|
||||
if (fallback)
|
||||
fallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
[context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue animationCompletion:completion];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var transformOrigin = function(start, current)
|
||||
{
|
||||
return "translate(" + (current.x - start.x) + "px," + (current.y - start.y) + "px)";
|
||||
};
|
||||
|
||||
var transformFrameToTranslate = function(start, current)
|
||||
{
|
||||
return transformOrigin(start.origin, current.origin);
|
||||
};
|
||||
|
||||
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 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":transformFrameToTranslate},
|
||||
@{"property":"width", "value":transformFrameToWidth},
|
||||
@{"property":"height", "value":transformFrameToHeight}],
|
||||
"frameOrigin" : [@{"property":transformProperty, "value":transformOrigin}],
|
||||
"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");
|
||||
|
||||
if (anim_class)
|
||||
return anim_class;
|
||||
|
||||
return [[self superclass] animatorClass];
|
||||
}
|
||||
|
||||
- (id)animator
|
||||
{
|
||||
if (!_animator)
|
||||
_animator = [[[[self class] animatorClass] alloc] initWithTarget:self];
|
||||
|
||||
return _animator;
|
||||
}
|
||||
|
||||
- (id)DOMElementForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return _DOMElement;
|
||||
}
|
||||
|
||||
+ (CAAnimation)defaultAnimationForKey:(CPString)aKey
|
||||
{
|
||||
if ([self 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];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,288 @@
|
||||
|
||||
var ANIMATIONS_GLOBAL_ID = 0,
|
||||
CURRENT_ANIMATIONS = {},
|
||||
|
||||
ANIMATION_END_EVENT_NAME,
|
||||
ANIMATION__PROPERTY,
|
||||
ANIMATION_NAME_PROPERTY,
|
||||
ANIMATION_DURATION_PROPERTY,
|
||||
ANIMATION_TIMING_FUNCTION_PROPERTY,
|
||||
ANIMATION_FILL_MODE_PROPERTY,
|
||||
ANIMATION_KEYFRAMES_RULE;
|
||||
|
||||
var defineCSSProperties = function()
|
||||
{
|
||||
if (this.done)
|
||||
return;
|
||||
|
||||
ANIMATION_END_EVENT_NAME = CPBrowserStyleProperty("animationend"),
|
||||
ANIMATION_PROPERTY = CPBrowserCSSProperty("animation"),
|
||||
ANIMATION_NAME_PROPERTY = CPBrowserCSSProperty("animation-name"),
|
||||
ANIMATION_DURATION_PROPERTY = CPBrowserCSSProperty("animation-duration"),
|
||||
ANIMATION_TIMING_FUNCTION_PROPERTY = CPBrowserCSSProperty("animation-timing-function"),
|
||||
ANIMATION_FILL_MODE_PROPERTY = CPBrowserCSSProperty("animation-fill-mode"),
|
||||
ANIMATION_KEYFRAMES_RULE = "@" + ANIMATION_PROPERTY.substring(0, ANIMATION_PROPERTY.indexOf("animation")) + "keyframes";
|
||||
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
CSSAnimation = function(aTarget/*DOM Element*/, anIdentifier)
|
||||
{
|
||||
defineCSSProperties();
|
||||
|
||||
if (!anIdentifier)
|
||||
anIdentifier = ANIMATIONS_GLOBAL_ID++;
|
||||
|
||||
var animationName = "anim_" + anIdentifier,
|
||||
animation = CURRENT_ANIMATIONS[anIdentifier];
|
||||
|
||||
if (animation)
|
||||
console.warn("Animation "+ anIdentifier + " is already in use. Ignoring.");
|
||||
else
|
||||
{
|
||||
this.target = aTarget;
|
||||
this.identifier = anIdentifier;
|
||||
this.animationName = animationName;
|
||||
this.listener = null;
|
||||
this.styleElement = null;
|
||||
this.propertyanimations = [];
|
||||
this.animationsnames = [];
|
||||
this.animationstimingfunctions = [];
|
||||
this.animationsdurations = [];
|
||||
this.islive = false;
|
||||
this.didBuildDOMElements = false;
|
||||
this.removeAnimationPropertyOnCompletion = true;
|
||||
|
||||
animation = this;
|
||||
CURRENT_ANIMATIONS[anIdentifier] = animation;
|
||||
}
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.addPropertyAnimation = function(propertyName/*String*/, valueFunction/*Function*/, aDuration/*float*/, aKeyTimes/*d, [d]*/, aValues/*Array*/, aTimingFunctions/*[d,d,d,d],[[d,d,d,d]]*/, aCompletionfunction/*Function*/)
|
||||
{
|
||||
if (this.islive)
|
||||
return false;
|
||||
// TODO: If a property already exist, replace its values & valueFunctions.
|
||||
|
||||
var name = this.animationName + "_" + propertyName;
|
||||
|
||||
var animation = {name:name,
|
||||
property:propertyName,
|
||||
valuefunction:valueFunction,
|
||||
keytimes:aKeyTimes,
|
||||
values:aValues,
|
||||
duration:aDuration,
|
||||
completionfunction:aCompletionfunction};
|
||||
|
||||
var animationTimingFunction;
|
||||
if (aTimingFunctions && (aTimingFunctions[0] instanceof Array))
|
||||
{
|
||||
animation.keyframestimingFunctions = aTimingFunctions;
|
||||
// dummy timing function overriden by keyframes timings functions
|
||||
animationTimingFunction = "linear";
|
||||
}
|
||||
else
|
||||
animationTimingFunction = "cubic-bezier(" + aTimingFunctions + ")";
|
||||
|
||||
this.animationstimingfunctions.push(animationTimingFunction);
|
||||
|
||||
this.propertyanimations.push(animation);
|
||||
this.animationsnames.push(name);
|
||||
this.animationsdurations.push(aDuration + "s");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.keyFrames = function()
|
||||
{
|
||||
var keyframesRules = [];
|
||||
|
||||
var count = this.propertyanimations.length;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var animation = this.propertyanimations[i],
|
||||
property = animation.property,
|
||||
valuefunction = animation.valuefunction,
|
||||
keytimes = animation.keytimes,
|
||||
values = animation.values,
|
||||
timingFunctions = animation.keyframestimingFunctions;
|
||||
|
||||
var keyframes = [],
|
||||
keytimescount = keytimes.length,
|
||||
start_value = values[0];
|
||||
|
||||
for (var j = 0; j < keytimescount; j++)
|
||||
{
|
||||
var keytime = keytimes[j],
|
||||
value = values[j],
|
||||
timingFunction;
|
||||
|
||||
if (valuefunction !== nil)
|
||||
value = valuefunction(start_value, value);
|
||||
|
||||
var keyframeContent = property + ": " + value + ";";
|
||||
|
||||
if (timingFunctions && timingFunctions.length && (timingFunction = timingFunctions[j]))
|
||||
{
|
||||
keyframeContent += ANIMATION_TIMING_FUNCTION_PROPERTY + ":cubic-bezier(" + timingFunction + ");";
|
||||
}
|
||||
|
||||
var keyframe = "\t" + Math.round(keytime * 100) + "% {\n\t\t" + keyframeContent + "\n\t}\n";
|
||||
keyframes.push(keyframe);
|
||||
}
|
||||
// TODO ! Add keyframe rule to CPCompatibility
|
||||
var rule = ANIMATION_KEYFRAMES_RULE + " " + animation.name + " {\n" + keyframes.join(" ") + "}\n";
|
||||
keyframesRules.push(rule);
|
||||
}
|
||||
|
||||
return keyframesRules.join("\n");
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.appendKeyFramesRule = function()
|
||||
{
|
||||
var styleElement = this.createKeyFramesStyleElement(),
|
||||
keyframesText = this.keyFrames(),
|
||||
nodeText = document.createTextNode(keyframesText);
|
||||
|
||||
styleElement.appendChild(nodeText);
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.createKeyFramesStyleElement = function()
|
||||
{
|
||||
if (!this.styleElement)
|
||||
{
|
||||
var styleElement = document.createElement("style");
|
||||
styleElement.setAttribute("type", "text/css");
|
||||
|
||||
this.styleElement = styleElement;
|
||||
}
|
||||
|
||||
return this.styleElement;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.endEventListener = function()
|
||||
{
|
||||
var animation = this,
|
||||
animationsNames = this.animationsnames,
|
||||
inFlightAnimationsNames = animationsNames.slice();
|
||||
|
||||
if (!animation.listener)
|
||||
{
|
||||
var AnimationEndListener = function(event)
|
||||
{
|
||||
var idx = inFlightAnimationsNames.indexOf(event.animationName);
|
||||
if (idx !== -1)
|
||||
inFlightAnimationsNames.splice(idx, 1);
|
||||
|
||||
if (inFlightAnimationsNames.length == 0)
|
||||
{
|
||||
for (var i = 0; i < animationsNames.length; i++)
|
||||
{
|
||||
var completion = animation.completionFunctionForAnimationName(animationsNames[i]);
|
||||
if (completion)
|
||||
completion();
|
||||
}
|
||||
|
||||
var eventTarget = event.target,
|
||||
style = eventTarget.style;
|
||||
|
||||
if (animation.removeAnimationPropertyOnCompletion)
|
||||
style.removeProperty(ANIMATION_NAME_PROPERTY);
|
||||
|
||||
style.removeProperty(ANIMATION_DURATION_PROPERTY);
|
||||
style.removeProperty(ANIMATION_FILL_MODE_PROPERTY);
|
||||
style.removeProperty("-webkit-backface-visibility");
|
||||
|
||||
if (animation.animationstimingfunctions.length)
|
||||
style.removeProperty(ANIMATION_TIMING_FUNCTION_PROPERTY);
|
||||
|
||||
removeFromParent(animation.styleElement);
|
||||
|
||||
eventTarget.removeEventListener(ANIMATION_END_EVENT_NAME, AnimationEndListener);
|
||||
animation.listener = null;
|
||||
delete (CURRENT_ANIMATIONS[animation.identifier]);
|
||||
}
|
||||
};
|
||||
|
||||
this.listener = AnimationEndListener;
|
||||
}
|
||||
|
||||
return this.listener;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.completionFunctionForAnimationName = function(aName)
|
||||
{
|
||||
var propanims = this.propertyanimations,
|
||||
count = propanims.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var anim = propanims[count];
|
||||
if (anim.name == aName)
|
||||
return anim.completionfunction;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.addAnimationEndEventListener = function()
|
||||
{
|
||||
var listener = this.endEventListener();
|
||||
this.target.addEventListener(ANIMATION_END_EVENT_NAME, listener, false);
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.setTargetStyleProperties = function()
|
||||
{
|
||||
var style = this.target.style;
|
||||
|
||||
if (this.animationstimingfunctions.length)
|
||||
style.setProperty(ANIMATION_TIMING_FUNCTION_PROPERTY, this.animationstimingfunctions.join(","));
|
||||
|
||||
style.setProperty(ANIMATION_DURATION_PROPERTY, this.animationsdurations.join(","));
|
||||
|
||||
style.setProperty(ANIMATION_FILL_MODE_PROPERTY, "forwards");
|
||||
|
||||
// http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/css3-animations-the-hiccups-and-bugs-youll-want-to-avoid/
|
||||
style.setProperty("-webkit-backface-visibility", "hidden");
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.buildDOMElements = function()
|
||||
{
|
||||
this.appendKeyFramesRule();
|
||||
|
||||
this.addAnimationEndEventListener();
|
||||
|
||||
this.setTargetStyleProperties();
|
||||
|
||||
this.didBuildDOMElements = true;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.setRemoveAnimationPropertyOnCompletion = function(flag)
|
||||
{
|
||||
this.removeAnimationPropertyOnCompletion = flag;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.start = function()
|
||||
{
|
||||
if (this.propertyanimations.length == 0 || this.islive)
|
||||
return false;
|
||||
|
||||
if (!this.didBuildDOMElements)
|
||||
this.buildDOMElements();
|
||||
|
||||
this.target.style.setProperty(ANIMATION_NAME_PROPERTY, this.animationsnames.join(","));
|
||||
|
||||
this.islive = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var removeFromParent= function(aNode)
|
||||
{
|
||||
var parentNode = aNode.parentNode;
|
||||
if (parentNode)
|
||||
parentNode.removeChild(aNode);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
@import <Foundation/CPProxy.j>
|
||||
@import "CPAnimationContext.j"
|
||||
|
||||
var _supportsCSSAnimations = null;
|
||||
|
||||
@protocol CPAnimatablePropertyContainer <CPObject>
|
||||
|
||||
+ (id)defaultAnimationForKey:(CPString)key;
|
||||
- (id)animationForKey:(CPString)key;
|
||||
|
||||
- (id)animator;
|
||||
- (CPDictionary)animations;
|
||||
- (void)setAnimations:(CPDictionary)animations;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPObjectAnimator : CPProxy
|
||||
{
|
||||
id <CPAnimatablePropertyContainer> _target;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsCSSAnimations
|
||||
{
|
||||
if (_supportsCSSAnimations === null)
|
||||
_supportsCSSAnimations = CPBrowserCSSProperty("animation");
|
||||
|
||||
return _supportsCSSAnimations;
|
||||
}
|
||||
|
||||
- (id)initWithTarget:(id)aTarget
|
||||
{
|
||||
_target = aTarget;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)animator
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
return [_target isEqual:anObject];
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
- (CPMethodSignature)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
return [_target methodSignatureForSelector:aSelector];
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(CPInvocation)anInvocation
|
||||
{
|
||||
var target = [self forwardingTargetForSelector:[anInvocation selector]];
|
||||
[anInvocation invokeWithTarget:target];
|
||||
return;
|
||||
}
|
||||
|
||||
- (void)doesNotRecognizeSelector:(SEL)aSelector
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException reason:@"Animator does not recognize selector " + CPStringFromSelector(aSelector)];
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return [CPString stringWithFormat:@"%@ Animator Proxy for %@", [self class], _target];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aTargetValue forKey:(id)aKeyPath
|
||||
{
|
||||
var animation = [_target animationForKey:aKeyPath],
|
||||
context = [CPAnimationContext currentContext];
|
||||
|
||||
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || ![_CPObjectAnimator supportsCSSAnimations])
|
||||
[_target setValue:aTargetValue forKey:aKeyPath];
|
||||
else
|
||||
{
|
||||
[context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue completionHandler:function()
|
||||
{
|
||||
[_target setValue:aTargetValue forKey:aKeyPath];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* Copyright 2010 Tim Down.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* jshashtable
|
||||
*
|
||||
* jshashtable is a JavaScript implementation of a hash table. It creates a single constructor function called Hashtable
|
||||
* in the global scope.
|
||||
*
|
||||
* Author: Tim Down <tim@timdown.co.uk>
|
||||
* Version: 2.1
|
||||
* Build date: 21 March 2010
|
||||
* Website: http://www.timdown.co.uk/jshashtable
|
||||
*/
|
||||
|
||||
Hashtable = (function() {
|
||||
var FUNCTION = "function";
|
||||
|
||||
var arrayRemoveAt = (typeof Array.prototype.splice == FUNCTION) ?
|
||||
function(arr, idx) {
|
||||
arr.splice(idx, 1);
|
||||
} :
|
||||
|
||||
function(arr, idx) {
|
||||
var itemsAfterDeleted, i, len;
|
||||
if (idx === arr.length - 1) {
|
||||
arr.length = idx;
|
||||
} else {
|
||||
itemsAfterDeleted = arr.slice(idx + 1);
|
||||
arr.length = idx;
|
||||
for (i = 0, len = itemsAfterDeleted.length; i < len; ++i) {
|
||||
arr[idx + i] = itemsAfterDeleted[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function hashObject(obj) {
|
||||
var hashCode;
|
||||
if (typeof obj == "string") {
|
||||
return obj;
|
||||
} else if (typeof obj.hashCode == FUNCTION) {
|
||||
// Check the hashCode method really has returned a string
|
||||
hashCode = obj.hashCode();
|
||||
return (typeof hashCode == "string") ? hashCode : hashObject(hashCode);
|
||||
} else if (typeof obj.toString == FUNCTION) {
|
||||
return obj.toString();
|
||||
} else {
|
||||
try {
|
||||
return String(obj);
|
||||
} catch (ex) {
|
||||
// For host objects (such as ActiveObjects in IE) that have no toString() method and throw an error when
|
||||
// passed to String()
|
||||
return Object.prototype.toString.call(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function equals_fixedValueHasEquals(fixedValue, variableValue) {
|
||||
return fixedValue.equals(variableValue);
|
||||
}
|
||||
|
||||
function equals_fixedValueNoEquals(fixedValue, variableValue) {
|
||||
return (typeof variableValue.equals == FUNCTION) ?
|
||||
variableValue.equals(fixedValue) : (fixedValue === variableValue);
|
||||
}
|
||||
|
||||
function createKeyValCheck(kvStr) {
|
||||
return function(kv) {
|
||||
if (kv === null) {
|
||||
throw new Error("null is not a valid " + kvStr);
|
||||
} else if (typeof kv == "undefined") {
|
||||
throw new Error(kvStr + " must not be undefined");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");
|
||||
|
||||
/*----------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
function Bucket(hash, firstKey, firstValue, equalityFunction) {
|
||||
this[0] = hash;
|
||||
this.entries = [];
|
||||
this.addEntry(firstKey, firstValue);
|
||||
|
||||
if (equalityFunction !== null) {
|
||||
this.getEqualityFunction = function() {
|
||||
return equalityFunction;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;
|
||||
|
||||
function createBucketSearcher(mode) {
|
||||
return function(key) {
|
||||
var i = this.entries.length, entry, equals = this.getEqualityFunction(key);
|
||||
while (i--) {
|
||||
entry = this.entries[i];
|
||||
if ( equals(key, entry[0]) ) {
|
||||
switch (mode) {
|
||||
case EXISTENCE:
|
||||
return true;
|
||||
case ENTRY:
|
||||
return entry;
|
||||
case ENTRY_INDEX_AND_VALUE:
|
||||
return [ i, entry[1] ];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
function createBucketLister(entryProperty) {
|
||||
return function(aggregatedArr) {
|
||||
var startIndex = aggregatedArr.length;
|
||||
for (var i = 0, len = this.entries.length; i < len; ++i) {
|
||||
aggregatedArr[startIndex + i] = this.entries[i][entryProperty];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Bucket.prototype = {
|
||||
getEqualityFunction: function(searchValue) {
|
||||
return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;
|
||||
},
|
||||
|
||||
getEntryForKey: createBucketSearcher(ENTRY),
|
||||
|
||||
getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE),
|
||||
|
||||
removeEntryForKey: function(key) {
|
||||
var result = this.getEntryAndIndexForKey(key);
|
||||
if (result) {
|
||||
arrayRemoveAt(this.entries, result[0]);
|
||||
return result[1];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
addEntry: function(key, value) {
|
||||
this.entries[this.entries.length] = [key, value];
|
||||
},
|
||||
|
||||
keys: createBucketLister(0),
|
||||
|
||||
values: createBucketLister(1),
|
||||
|
||||
getEntries: function(entries) {
|
||||
var startIndex = entries.length;
|
||||
for (var i = 0, len = this.entries.length; i < len; ++i) {
|
||||
// Clone the entry stored in the bucket before adding to array
|
||||
entries[startIndex + i] = this.entries[i].slice(0);
|
||||
}
|
||||
},
|
||||
|
||||
containsKey: createBucketSearcher(EXISTENCE),
|
||||
|
||||
containsValue: function(value) {
|
||||
var i = this.entries.length;
|
||||
while (i--) {
|
||||
if ( value === this.entries[i][1] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Supporting functions for searching hashtable buckets
|
||||
|
||||
function searchBuckets(buckets, hash) {
|
||||
var i = buckets.length, bucket;
|
||||
while (i--) {
|
||||
bucket = buckets[i];
|
||||
if (hash === bucket[0]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBucketForHash(bucketsByHash, hash) {
|
||||
var bucket = bucketsByHash[hash];
|
||||
|
||||
// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype
|
||||
return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
function Hashtable(hashingFunctionParam, equalityFunctionParam) {
|
||||
var that = this;
|
||||
var buckets = [];
|
||||
var bucketsByHash = {};
|
||||
|
||||
var hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject;
|
||||
var equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null;
|
||||
|
||||
this.put = function(key, value) {
|
||||
checkKey(key);
|
||||
checkValue(value);
|
||||
var hash = hashingFunction(key), bucket, bucketEntry, oldValue = null;
|
||||
|
||||
// Check if a bucket exists for the bucket key
|
||||
bucket = getBucketForHash(bucketsByHash, hash);
|
||||
if (bucket) {
|
||||
// Check this bucket to see if it already contains this key
|
||||
bucketEntry = bucket.getEntryForKey(key);
|
||||
if (bucketEntry) {
|
||||
// This bucket entry is the current mapping of key to value, so replace old value and we're done.
|
||||
oldValue = bucketEntry[1];
|
||||
bucketEntry[1] = value;
|
||||
} else {
|
||||
// The bucket does not contain an entry for this key, so add one
|
||||
bucket.addEntry(key, value);
|
||||
}
|
||||
} else {
|
||||
// No bucket exists for the key, so create one and put our key/value mapping in
|
||||
bucket = new Bucket(hash, key, value, equalityFunction);
|
||||
buckets[buckets.length] = bucket;
|
||||
bucketsByHash[hash] = bucket;
|
||||
}
|
||||
return oldValue;
|
||||
};
|
||||
|
||||
this.get = function(key) {
|
||||
checkKey(key);
|
||||
|
||||
var hash = hashingFunction(key);
|
||||
|
||||
// Check if a bucket exists for the bucket key
|
||||
var bucket = getBucketForHash(bucketsByHash, hash);
|
||||
if (bucket) {
|
||||
// Check this bucket to see if it contains this key
|
||||
var bucketEntry = bucket.getEntryForKey(key);
|
||||
if (bucketEntry) {
|
||||
// This bucket entry is the current mapping of key to value, so return the value.
|
||||
return bucketEntry[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
this.containsKey = function(key) {
|
||||
checkKey(key);
|
||||
var bucketKey = hashingFunction(key);
|
||||
|
||||
// Check if a bucket exists for the bucket key
|
||||
var bucket = getBucketForHash(bucketsByHash, bucketKey);
|
||||
|
||||
return bucket ? bucket.containsKey(key) : false;
|
||||
};
|
||||
|
||||
this.containsValue = function(value) {
|
||||
checkValue(value);
|
||||
var i = buckets.length;
|
||||
while (i--) {
|
||||
if (buckets[i].containsValue(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
this.clear = function() {
|
||||
buckets.length = 0;
|
||||
bucketsByHash = {};
|
||||
};
|
||||
|
||||
this.isEmpty = function() {
|
||||
return !buckets.length;
|
||||
};
|
||||
|
||||
var createBucketAggregator = function(bucketFuncName) {
|
||||
return function() {
|
||||
var aggregated = [], i = buckets.length;
|
||||
while (i--) {
|
||||
buckets[i][bucketFuncName](aggregated);
|
||||
}
|
||||
return aggregated;
|
||||
};
|
||||
};
|
||||
|
||||
this.keys = createBucketAggregator("keys");
|
||||
this.values = createBucketAggregator("values");
|
||||
this.entries = createBucketAggregator("getEntries");
|
||||
|
||||
this.remove = function(key) {
|
||||
checkKey(key);
|
||||
|
||||
var hash = hashingFunction(key), bucketIndex, oldValue = null;
|
||||
|
||||
// Check if a bucket exists for the bucket key
|
||||
var bucket = getBucketForHash(bucketsByHash, hash);
|
||||
|
||||
if (bucket) {
|
||||
// Remove entry from this bucket for this key
|
||||
oldValue = bucket.removeEntryForKey(key);
|
||||
if (oldValue !== null) {
|
||||
// Entry was removed, so check if bucket is empty
|
||||
if (!bucket.entries.length) {
|
||||
// Bucket is empty, so remove it from the bucket collections
|
||||
bucketIndex = searchBuckets(buckets, hash);
|
||||
arrayRemoveAt(buckets, bucketIndex);
|
||||
delete bucketsByHash[hash];
|
||||
}
|
||||
}
|
||||
}
|
||||
return oldValue;
|
||||
};
|
||||
|
||||
this.size = function() {
|
||||
var total = 0, i = buckets.length;
|
||||
while (i--) {
|
||||
total += buckets[i].entries.length;
|
||||
}
|
||||
return total;
|
||||
};
|
||||
|
||||
this.each = function(callback) {
|
||||
var entries = that.entries(), i = entries.length, entry;
|
||||
while (i--) {
|
||||
entry = entries[i];
|
||||
callback(entry[0], entry[1]);
|
||||
}
|
||||
};
|
||||
|
||||
this.putAll = function(hashtable, conflictCallback) {
|
||||
var entries = hashtable.entries();
|
||||
var entry, key, value, thisValue, i = entries.length;
|
||||
var hasConflictCallback = (typeof conflictCallback == FUNCTION);
|
||||
while (i--) {
|
||||
entry = entries[i];
|
||||
key = entry[0];
|
||||
value = entry[1];
|
||||
|
||||
// Check for a conflict. The default behaviour is to overwrite the value for an existing key
|
||||
if ( hasConflictCallback && (thisValue = that.get(key)) ) {
|
||||
value = conflictCallback(key, thisValue, value);
|
||||
}
|
||||
that.put(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
this.clone = function() {
|
||||
var clone = new Hashtable(hashingFunctionParam, equalityFunctionParam);
|
||||
clone.putAll(that);
|
||||
return clone;
|
||||
};
|
||||
}
|
||||
|
||||
return Hashtable;
|
||||
})();
|
||||
@@ -200,6 +200,7 @@ var CPRunLoopLastNativeRunLoop = 0;
|
||||
|
||||
CPArray _orderedPerforms;
|
||||
int _runLoopInsuranceTimer;
|
||||
CPArray _observers;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -224,6 +225,7 @@ var CPRunLoopLastNativeRunLoop = 0;
|
||||
_timersForModes = {};
|
||||
_nativeTimersForModes = {};
|
||||
_nextTimerFireDatesForModes = {};
|
||||
_observers = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -473,6 +475,19 @@ var CPRunLoopLastNativeRunLoop = 0;
|
||||
else
|
||||
_orderedPerforms = performs;
|
||||
|
||||
if (_observers)
|
||||
{
|
||||
var count = _observers.length;
|
||||
while(count--)
|
||||
{
|
||||
var obs = _observers[count];
|
||||
obs.callout();
|
||||
|
||||
if (!obs.repeats)
|
||||
_observers.splice(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
_runLoopLock = NO;
|
||||
|
||||
return nextFireDate;
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPAnimatablePropertyContainerTest
|
||||
*
|
||||
* Created by You on December 3, 2012.
|
||||
* Copyright 2012, Your Company All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
CPLogRegister(CPLogConsole);
|
||||
|
||||
var ANIMATIONS_NAMES = ["Fade In", "Fade Out", "Background Color", "Frame Origin", "Bounce", "Frame Size", "Frame"];
|
||||
|
||||
@implementation AppController : CPObject
|
||||
{
|
||||
@outlet CPWindow theWindow; //this "outlet" is connected automatically by the Cib
|
||||
|
||||
@outlet CPBox group1Box;
|
||||
@outlet CPBox group2Box;
|
||||
|
||||
@outlet CPView animationSandbox;
|
||||
|
||||
CPView leftView;
|
||||
CPView rightView;
|
||||
|
||||
CPArray animations;
|
||||
|
||||
float duration1 @accessors;
|
||||
float duration2 @accessors;
|
||||
|
||||
CPString message1 @accessors;
|
||||
CPString message2 @accessors;
|
||||
|
||||
CPInteger selectedTimingFunction1 @accessors;
|
||||
CPInteger selectedTimingFunction2 @accessors;
|
||||
|
||||
CPString timingFunction1 @accessors;
|
||||
CPString timingFunction2 @accessors;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
animations = [CPArray array];
|
||||
|
||||
[ANIMATIONS_NAMES enumerateObjectsUsingBlock:function(anim, idx)
|
||||
{
|
||||
var dict = [CPDictionary dictionaryWithObjectsAndKeys:anim, @"name", NO, @"enabled1", NO, @"enabled2"];
|
||||
[animations addObject:dict];
|
||||
}];
|
||||
|
||||
duration1 = 1.0;
|
||||
duration2 = 1.0;
|
||||
|
||||
message1 = @"Done for View 1";
|
||||
message2 = @"Done for View 2";
|
||||
|
||||
timingFunction1 = @"0,1,1,0";
|
||||
timingFunction2 = @"0,1,1,0";
|
||||
|
||||
selectedTimingFunction1 = 1;
|
||||
selectedTimingFunction2 = 1;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
|
||||
{
|
||||
// This is called when the application is done loading.
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
// This is called when the cib is done loading.
|
||||
// You can implement this method on any object instantiated from a Cib.
|
||||
// It's a useful hook for setting up current UI values, and other things.
|
||||
|
||||
[[theWindow contentView] setBackgroundColor:[CPColor colorWithRed:1 green:238/255 blue:185/255 alpha:1]];
|
||||
[animationSandbox setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
[self revert:nil];
|
||||
// In this case, we want the window from Cib to become our full browser window
|
||||
[theWindow setFullPlatformWindow:YES];
|
||||
}
|
||||
|
||||
- (IBAction)runAnimationsGroup1:(id)sender
|
||||
{
|
||||
[[CPAnimationContext currentContext] setDuration:duration1];
|
||||
[[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction1]];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]];
|
||||
|
||||
[self runAnimationsForGroup:1];
|
||||
}
|
||||
|
||||
- (IBAction)runAnimationsGroup2:(id)sender
|
||||
{
|
||||
[[CPAnimationContext currentContext] setDuration:duration2];
|
||||
[[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:2 fromPopUp:selectedTimingFunction2]];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message2]];
|
||||
|
||||
[self runAnimationsForGroup:2];
|
||||
}
|
||||
|
||||
- (IBAction)animate:(id)sender
|
||||
{
|
||||
[[CPAnimationContext currentContext] setDuration:duration1];
|
||||
[[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction2]];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]];
|
||||
|
||||
var tag = [sender tag];
|
||||
var width = tag ? 100 : 200;
|
||||
[[sender animator] setFrameSize:CGSizeMake(width, CGRectGetHeight([sender frame]))];
|
||||
|
||||
[sender setTag:(1 - tag)];
|
||||
}
|
||||
|
||||
- (IBAction)runBothGroups:(id)sender
|
||||
{
|
||||
[CPAnimationContext runAnimationGroup:function(context)
|
||||
{
|
||||
[context setDuration:duration1];
|
||||
[context setTimingFunction:[self timingFunctionForGroup:1 fromPopUp:selectedTimingFunction1]];
|
||||
[self runAnimationsForGroup:1];
|
||||
|
||||
} completionHandler:function()
|
||||
{
|
||||
CPLogConsole(message1);
|
||||
}];
|
||||
|
||||
[CPAnimationContext runAnimationGroup:function(context)
|
||||
{
|
||||
[context setDuration:duration2];
|
||||
[context setTimingFunction:[self timingFunctionForGroup:2 fromPopUp:selectedTimingFunction2]];
|
||||
[self runAnimationsForGroup:2];
|
||||
|
||||
} completionHandler:function()
|
||||
{
|
||||
CPLogConsole(message2);
|
||||
}];
|
||||
}
|
||||
|
||||
/*
|
||||
- (IBAction)runInGroups:(id)sender
|
||||
{
|
||||
[CPAnimationContext beginGrouping];
|
||||
|
||||
[[CPAnimationContext currentContext] setDuration:duration1];
|
||||
[[CPAnimationContext currentContext] setTimingFunction:[self timingFunctionFromString:timingFunction1]];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message1]];
|
||||
[self runAnimationsForGroup:1];
|
||||
|
||||
[CPAnimationContext endGrouping];
|
||||
|
||||
|
||||
[CPAnimationContext beginGrouping];
|
||||
|
||||
[[CPAnimationContext currentContext] setDuration:duration2];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:[self completionHandlerFromString:message2]];
|
||||
[self runAnimationsForGroup:2];
|
||||
|
||||
[CPAnimationContext endGrouping];
|
||||
}
|
||||
|
||||
- (IBAction)emptyGroupTest:(id)sender
|
||||
{
|
||||
[CPAnimationContext beginGrouping];
|
||||
|
||||
[[CPAnimationContext currentContext] setDuration:10];
|
||||
[[CPAnimationContext currentContext] setCompletionHandler:function()
|
||||
{
|
||||
CPLogConsole("Context duration is 10s but no animations were set. We run the completionHandler (this message) immediately and return");
|
||||
}];
|
||||
|
||||
[CPAnimationContext endGrouping];
|
||||
}
|
||||
*/
|
||||
- (IBAction)removeFromSuperview:(id)sender
|
||||
{
|
||||
[[leftView animator] removeFromSuperview];
|
||||
[[rightView animator] removeFromSuperview];
|
||||
}
|
||||
|
||||
- (IBAction)revert:(id)sender
|
||||
{
|
||||
[self setupGroup1:nil];
|
||||
[self setupGroup2:nil];
|
||||
}
|
||||
/*
|
||||
- (IBAction)addSubview:(id)sender
|
||||
{
|
||||
[[theWindow contentView] addSubview:];
|
||||
}
|
||||
*/
|
||||
- (void)runAnimationsForGroup:(CPInteger)group
|
||||
{
|
||||
var aView = group == 1 ? leftView : rightView,
|
||||
enabledKey = @"enabled" + group;
|
||||
|
||||
var enabledIndexes = [animations indexesOfObjectsPassingTest:function(obj, idx)
|
||||
{
|
||||
return [obj objectForKey:enabledKey];
|
||||
}];
|
||||
|
||||
if ([enabledIndexes count] == 0)
|
||||
return;
|
||||
|
||||
if ([enabledIndexes containsIndex:0])
|
||||
{
|
||||
[aView setAlphaValue:0];
|
||||
[[aView animator] setAlphaValue:1];
|
||||
}
|
||||
else if ([enabledIndexes containsIndex:1])
|
||||
{
|
||||
[[aView animator] setAlphaValue:0];
|
||||
}
|
||||
|
||||
if ([enabledIndexes containsIndex:2])
|
||||
{
|
||||
[[aView animator] setBackgroundColor:[CPColor magentaColor]];
|
||||
}
|
||||
|
||||
if ([enabledIndexes containsIndex:6])
|
||||
{
|
||||
var frame = CGRectMakeCopy([aView frame]),
|
||||
origin = frame.origin,
|
||||
size = frame.size;
|
||||
|
||||
origin.x += 450;
|
||||
origin.y += 200;
|
||||
|
||||
size.width += 100;
|
||||
size.height += 100;
|
||||
|
||||
[[aView animator] setFrame:frame];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([enabledIndexes containsIndex:4])
|
||||
{
|
||||
[[aView animations] setObject:[self bounceAnimation:aView] forKey:@"frameOrigin"];
|
||||
|
||||
var origin = CGPointMakeCopy([aView frameOrigin]);
|
||||
[[aView animator] setFrameOrigin:origin];
|
||||
}
|
||||
else if ([enabledIndexes containsIndex:3])
|
||||
{
|
||||
[[aView animations] removeObjectForKey:@"frameOrigin"];
|
||||
|
||||
var origin = CGPointMakeCopy([aView frameOrigin]);
|
||||
origin.x +=550;
|
||||
origin.y +=300;
|
||||
[[aView animator] setFrameOrigin:origin];
|
||||
}
|
||||
|
||||
if ([enabledIndexes containsIndex:5])
|
||||
{
|
||||
var size = CGSizeMakeCopy([aView frameSize]);
|
||||
size.width += 300;
|
||||
size.height += 200;
|
||||
[[aView animator] setFrameSize:size];
|
||||
}
|
||||
}
|
||||
|
||||
- (CAMediaTimingFunction)controlsPointsForGroup:(CPInteger)aGroup
|
||||
{
|
||||
var aString = (aGroup == 1) ? timingFunction1 : timingFunction2;
|
||||
|
||||
if (!aString || ![aString length])
|
||||
return nil;
|
||||
|
||||
var controlsPoints = [aString componentsSeparatedByString:@","];
|
||||
|
||||
return [[controlsPoints[0] floatValue], [controlsPoints[1] floatValue], [controlsPoints[2] floatValue], [controlsPoints[3] floatValue]];
|
||||
}
|
||||
|
||||
- (CAMediaTimingFunction)timingFunctionForGroup:(CPInteger)aGroup fromPopUp:(CPInteger)selectedTag
|
||||
{
|
||||
var controlsPoints;
|
||||
|
||||
switch (selectedTag)
|
||||
{
|
||||
case 1: controlsPoints = [0, 0, 1, 1];
|
||||
break;
|
||||
case 2: controlsPoints = [0.42, 0, 1, 1];
|
||||
break;
|
||||
case 3: controlsPoints = [0, 0, 0.58, 1];
|
||||
break;
|
||||
case 4: controlsPoints = [0.42, 0, 0.58, 1];
|
||||
break;
|
||||
case 0: controlsPoints = [self controlsPointsForGroup:aGroup];
|
||||
break;
|
||||
}
|
||||
|
||||
return [CAMediaTimingFunction functionWithControlPoints:controlsPoints[0] :controlsPoints[1] :controlsPoints[2] :controlsPoints[3]];
|
||||
}
|
||||
|
||||
- (Function)completionHandlerFromString:(CPString)aMessage
|
||||
{
|
||||
if (!aMessage || ![aMessage length])
|
||||
return nil;
|
||||
|
||||
var s = new Date();
|
||||
return function()
|
||||
{
|
||||
var e = new Date() - s;
|
||||
CPLogConsole(aMessage + " in " + e + " ms");
|
||||
};
|
||||
}
|
||||
|
||||
- (CAKeyframeAnimation)bounceAnimation:(CPView)aView
|
||||
{
|
||||
var anim = [CAKeyframeAnimation animation],
|
||||
easein = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
|
||||
easeout = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
|
||||
[anim setKeyTimes:[0, 0.25, 0.5, 0.75, 1]];
|
||||
var origin = CGPointMakeCopy([aView frameOrigin]);
|
||||
[anim setValues:[origin, CGPointMake(origin.x, origin.y + 50), origin, CGPointMake(origin.x, origin.y + 25), origin]];
|
||||
[anim setTimingFunctions:[easeout, easein, easeout, easein]];
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
- (CABasicAnimation)fadeOutAnimation
|
||||
{
|
||||
var animation = [CABasicAnimation animationWithKeyPath:@"alphaValue"];
|
||||
[animation setDuration:0.2];
|
||||
[animation setFromValue:1];
|
||||
[animation setToValue:0];
|
||||
|
||||
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (IBAction)setupGroup1:(id)sender
|
||||
{
|
||||
var contentView = [group1Box contentView];
|
||||
|
||||
var hasSubviews = [[contentView viewWithTag:1000] state] * 2,
|
||||
customLayout = [[contentView viewWithTag:1001] state] * 4,
|
||||
customDraw = [[contentView viewWithTag:1002] state] * 8,
|
||||
customDrawSubviews = [[contentView viewWithTag:1003] state],
|
||||
autoLayout = [[contentView viewWithTag:1004] state],
|
||||
options = hasSubviews | customLayout | customDraw;
|
||||
|
||||
[leftView removeFromSuperview];
|
||||
leftView = [self viewWithOptions:options];
|
||||
|
||||
if (leftView)
|
||||
{
|
||||
[leftView setFrame:CGRectMake(0, 0, 200, 200)];
|
||||
[animationSandbox addSubview:leftView];
|
||||
}
|
||||
|
||||
if (hasSubviews)
|
||||
[self addSubviewsToView:leftView autoLayout:(autoLayout && !customLayout) customDrawSubviews:customDrawSubviews];
|
||||
}
|
||||
|
||||
- (IBAction)setupGroup2:(id)sender
|
||||
{
|
||||
var contentView = [group2Box contentView];
|
||||
|
||||
var hasSubviews = [[contentView viewWithTag:1000] state] * 2,
|
||||
customLayout = [[contentView viewWithTag:1001] state] * 4,
|
||||
customDraw = [[contentView viewWithTag:1002] state] * 8,
|
||||
options = hasSubviews | customLayout | customDraw;
|
||||
|
||||
[rightView removeFromSuperview];
|
||||
rightView = [self viewWithOptions:options];
|
||||
|
||||
if (rightView)
|
||||
{
|
||||
[rightView setFrame:CGRectMake(250, 0, 200, 200)];
|
||||
[animationSandbox addSubview:rightView];
|
||||
}
|
||||
|
||||
if (leftView)
|
||||
[animationSandbox addSubview:leftView];
|
||||
|
||||
if (hasSubviews)
|
||||
[self addSubviewsToView:rightView autoLayout:!customLayout customDrawSubviews:NO];
|
||||
}
|
||||
|
||||
- (void)viewWithOptions:(CPInteger)options
|
||||
{
|
||||
var view = nil,
|
||||
hasSubviews = NO;
|
||||
|
||||
switch (options)
|
||||
{
|
||||
case 0: view = [[ColorView alloc] initWithFrame:CGRectMakeZero()];
|
||||
break;
|
||||
|
||||
case 2: view = [[ColorView alloc] initWithFrame:CGRectMakeZero()];
|
||||
hasSubviews = YES;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 6: view = [[CustomLayoutView alloc] initWithFrame:CGRectMakeZero()];
|
||||
[view setBackgroundColor:[CPColor randomColor]];
|
||||
hasSubviews = YES;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
case 14: view = [[CustomLayoutDrawView alloc] initWithFrame:CGRectMakeZero()];
|
||||
hasSubviews = YES;
|
||||
break;
|
||||
|
||||
case 8: view = [[DrawView alloc] initWithFrame:CGRectMakeZero()];
|
||||
break;
|
||||
|
||||
case 10: view = [[DrawView alloc] initWithFrame:CGRectMakeZero()];
|
||||
hasSubviews = YES;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
var anims = [CPDictionary dictionaryWithObject:[self fadeOutAnimation] forKey:@"CPAnimationTriggerOrderOut"];
|
||||
[view setAnimations:anims];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
- (void)addSubviewsToView:(CPView)aView autoLayout:(BOOL)autoLayout customDrawSubviews:(BOOL)customDrawSubviews
|
||||
{
|
||||
var subViewClass = customDrawSubviews ? [DrawView class] : [ColorView class];
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var view = [[subViewClass alloc] initWithFrame:CGRectMake(50, i * 40, 80, 26)];
|
||||
if (autoLayout)
|
||||
[view setAutoresizingMask:CPViewWidthSizable|CPViewMinYMargin];
|
||||
|
||||
[aView addSubview:view];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ColorView : CPView
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
[self setBackgroundColor:[CPColor randomColor]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation DrawView : CPView
|
||||
{
|
||||
CPColor color @accessors;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
color = [CPColor randomColor];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
CGContextSetLineWidth(context, 2);
|
||||
CGContextSetStrokeColor(context, [CPColor blackColor]);
|
||||
CGContextSetFillColor(context, color);
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextFillRect(context, bounds);
|
||||
|
||||
var height = CGRectGetHeight(bounds),
|
||||
width = CGRectGetWidth(bounds);
|
||||
|
||||
CGContextStrokeLineSegments(context, [CGPointMake(10,0), CGPointMake(10, height),
|
||||
CGPointMake(0, 10), CGPointMake(CGRectGetWidth(aRect), 10),
|
||||
CGPointMake(width - 10, 0), CGPointMake(width - 10, height),
|
||||
CGPointMake(0, height - 10), CGPointMake(width, height - 10)], 8);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CustomLayoutView : CPView
|
||||
{
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var subviews = [self subviews],
|
||||
count = [subviews count] - 1;
|
||||
|
||||
var dx = (CGRectGetWidth([self frame]) - 100) / count,
|
||||
dy = (CGRectGetHeight([self frame]) - 26) / count
|
||||
|
||||
[subviews enumerateObjectsUsingBlock:function(view, idx, stop)
|
||||
{
|
||||
[view setFrameOrigin:CGPointMake(dx * idx, dy * idx)];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CustomLayoutDrawView : DrawView
|
||||
{
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var subviews = [self subviews],
|
||||
count = [subviews count] - 1;
|
||||
|
||||
var dx = (CGRectGetWidth([self frame]) - 100) / count,
|
||||
dy = (CGRectGetHeight([self frame]) - 26) / count
|
||||
|
||||
[subviews enumerateObjectsUsingBlock:function(view, idx, stop)
|
||||
{
|
||||
[view setFrameOrigin:CGPointMake(dx * idx, dy * idx)];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Main cib file base name</key>
|
||||
<string>MainMenu.cib</string>
|
||||
<key>CPBundleName</key>
|
||||
<string>CPAnimatablePropertyContainerTest</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Jakefile
|
||||
* CPAnimatablePropertyContainerTest
|
||||
*
|
||||
* Created by You on December 3, 2012.
|
||||
* Copyright 2012, Your Company All rights reserved.
|
||||
*/
|
||||
|
||||
var ENV = require("system").env,
|
||||
FILE = require("file"),
|
||||
JAKE = require("jake"),
|
||||
task = JAKE.task,
|
||||
FileList = JAKE.FileList,
|
||||
app = require("cappuccino/jake").app,
|
||||
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
|
||||
OS = require("os");
|
||||
|
||||
app ("CPAnimatablePropertyContainerTest", function(task)
|
||||
{
|
||||
task.setBuildIntermediatesPath(FILE.join("Build", "CPAnimatablePropertyContainerTest.build", configuration));
|
||||
task.setBuildPath(FILE.join("Build", configuration));
|
||||
|
||||
task.setProductName("CPAnimatablePropertyContainerTest");
|
||||
task.setIdentifier("com.yourcompany.CPAnimatablePropertyContainerTest");
|
||||
task.setVersion("1.0");
|
||||
task.setAuthor("Your Company");
|
||||
task.setEmail("feedback @nospam@ yourcompany.com");
|
||||
task.setSummary("CPAnimatablePropertyContainerTest");
|
||||
task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")));
|
||||
task.setResources(new FileList("Resources/**"));
|
||||
task.setIndexFilePath("index.html");
|
||||
task.setInfoPlistPath("Info.plist");
|
||||
task.setNib2CibFlags("-R Resources/");
|
||||
|
||||
if (configuration === "Debug")
|
||||
task.setCompilerFlags("-DDEBUG -g");
|
||||
else
|
||||
task.setCompilerFlags("-O");
|
||||
});
|
||||
|
||||
task ("default", ["CPAnimatablePropertyContainerTest"], function()
|
||||
{
|
||||
printResults(configuration);
|
||||
});
|
||||
|
||||
task ("build", ["default"]);
|
||||
|
||||
task ("debug", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Debug";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("release", function()
|
||||
{
|
||||
ENV["CONFIGURATION"] = "Release";
|
||||
JAKE.subjake(["."], "build", ENV);
|
||||
});
|
||||
|
||||
task ("run", ["debug"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Debug", "CPAnimatablePropertyContainerTest", "index.html")]);
|
||||
});
|
||||
|
||||
task ("run-release", ["release"], function()
|
||||
{
|
||||
OS.system(["open", FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest", "index.html")]);
|
||||
});
|
||||
|
||||
task ("deploy", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Deployment", "CPAnimatablePropertyContainerTest"));
|
||||
OS.system(["press", "-f", FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest"), FILE.join("Build", "Deployment", "CPAnimatablePropertyContainerTest")]);
|
||||
printResults("Deployment")
|
||||
});
|
||||
|
||||
task ("desktop", ["release"], function()
|
||||
{
|
||||
FILE.mkdirs(FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest"));
|
||||
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPAnimatablePropertyContainerTest"), FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest", "CPAnimatablePropertyContainerTest.app"));
|
||||
printResults("Desktop")
|
||||
});
|
||||
|
||||
task ("run-desktop", ["desktop"], function()
|
||||
{
|
||||
OS.system([FILE.join("Build", "Desktop", "CPAnimatablePropertyContainerTest", "CPAnimatablePropertyContainerTest.app", "Contents", "MacOS", "NativeHost"), "-i"]);
|
||||
});
|
||||
|
||||
function printResults(configuration)
|
||||
{
|
||||
print("----------------------------");
|
||||
print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPAnimatablePropertyContainerTest"));
|
||||
print("----------------------------");
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,107 @@
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<!--
|
||||
index-debug.html
|
||||
CPAnimatablePropertyContainerTest
|
||||
|
||||
Created by You on December 3, 2012.
|
||||
Copyright 2012, Your Company All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png" />
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png" />
|
||||
|
||||
<title>CPAnimatablePropertyContainerTest</title>
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks", "SomethingElse"];
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
objj_msgSend_reset();
|
||||
|
||||
// DEBUG OPTIONS:
|
||||
|
||||
// Uncomment to enable printing of backtraces on exceptions:
|
||||
objj_msgSend_decorate(objj_backtrace_decorator);
|
||||
|
||||
// Uncomment to supress exceptions that take place inside a message
|
||||
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
|
||||
|
||||
// Uncomment to enable runtime type checking:
|
||||
//objj_msgSend_decorate(objj_typecheck_decorator);
|
||||
|
||||
// Uncomment (along with both above) to print backtraces on type check errors:
|
||||
//objj_typecheck_prints_backtrace = true;
|
||||
|
||||
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
|
||||
//CPLogUnregister(CPLogDefault);
|
||||
|
||||
// Uncomment to enable a specific logger:
|
||||
//CPLogRegister(CPLogConsole);
|
||||
//CPLogRegister(CPLogPopup);
|
||||
|
||||
// Tag view DOM elements with a "data-cappuccino-view" attribute that contains
|
||||
// the class name of the view that created them. Comment this or set to false to disable.
|
||||
appkit_tag_dom_elements = true;
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="cappuccino-body">
|
||||
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type="text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading CPAnimatablePropertyContainerTest...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<!--
|
||||
index.html
|
||||
CPAnimatablePropertyContainerTest
|
||||
|
||||
Created by You on December 3, 2012.
|
||||
Copyright 2012, Your Company All rights reserved.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1" />
|
||||
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
|
||||
<link rel="apple-touch-icon" href="Resources/icon.png" />
|
||||
<link rel="apple-touch-startup-image" href="Resources/default.png" />
|
||||
|
||||
<title>CPAnimatablePropertyContainerTest</title>
|
||||
|
||||
<script type="text/javascript">
|
||||
OBJJ_MAIN_FILE = "main.j";
|
||||
</script>
|
||||
|
||||
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body{margin:0; padding:0;}
|
||||
#container {position: absolute; top:50%; left:50%;}
|
||||
#content {width:800px; text-align:center; margin-left: -400px; height:50px; margin-top:-25px; line-height: 50px;}
|
||||
#content {font-family: "Helvetica", "Arial", sans-serif; font-size: 18px; color: black; text-shadow: 0px 1px 0px white; }
|
||||
#loadgraphic {margin-right: 0.2em; margin-bottom:-2px;}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 7]>
|
||||
<STYLE type="text/css">
|
||||
#container { position: relative; top: 50%; }
|
||||
#content { position: relative;}
|
||||
</STYLE>
|
||||
<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="">
|
||||
<div id="cappuccino-body">
|
||||
<div id="loadingcontainer" style="background-color: #eeeeee; overflow:hidden; width:100%; height:100%; position: absolute; top: 0; left: 0;">
|
||||
<script type="text/javascript">
|
||||
document.write("<div id='container'><p id='content'>" +
|
||||
"<img id='loadgraphic' width='16' height='16' src='Resources/spinner.gif' /> " +
|
||||
"Loading CPAnimatablePropertyContainerTest...</p></div>");
|
||||
</script>
|
||||
|
||||
<noscript>
|
||||
<div id="container">
|
||||
<div style="width: 440px; padding: 10px 25px 20px 25px; font-family: sans-serif; background-color: #ffffff; position: relative; left: -245px; top: -120px; text-align: center; -moz-border-radius: 20px; -webkit-border-radius: 20px; color: #555555">
|
||||
<p style="line-height: 1.4em;">JavaScript is required for this site to work correctly but is either disabled or not supported by your browser.</p>
|
||||
<p style="font-size:120%; padding:10px;"><a href="http://cappuccino.org/noscript">Show me how to enable JavaScript</a></p>
|
||||
<p style="font-size:80%;">You may want to upgrade to a newer browser while you're at it:</p>
|
||||
<ul style="margin:0;padding:0; text-align: center; font-size:80%;" >
|
||||
<li style="display: inline;"><a href="http://www.apple.com/safari/download/">Safari</a></li>
|
||||
<li style="display: inline;"><a href="http://www.mozilla.com/en-US/firefox/">Firefox</a></li>
|
||||
<li style="display: inline;"><a href="http://www.google.com/chrome/">Chrome</a></li>
|
||||
<li style="display: inline;"><a href="http://www.opera.com/download/">Opera</a></li>
|
||||
<li style="display: inline;"><a href="http://www.microsoft.com/windows/downloads/ie/getitnow.mspx">Internet Explorer</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* AppController.j
|
||||
* CPAnimatablePropertyContainerTest
|
||||
*
|
||||
* Created by You on December 3, 2012.
|
||||
* Copyright 2012, Your Company All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
@import "AppController.j"
|
||||
|
||||
|
||||
function main(args, namedArgs)
|
||||
{
|
||||
CPApplicationMain(args, namedArgs);
|
||||
}
|
||||
Reference in New Issue
Block a user