mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
Merge pull request #2532 from cacaodev/CPWindowAnimator
New: Optional declarative layout for use in CoreAnimation (css animations). Code refactor.
This commit is contained in:
@@ -81,7 +81,7 @@ CPFileAPIFeature = 31;
|
||||
|
||||
CPAltEnterTextAreaFeature = 32;
|
||||
|
||||
|
||||
CPCSSAnimationFeature = 33;
|
||||
|
||||
/*
|
||||
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
|
||||
|
||||
@@ -3,22 +3,19 @@
|
||||
@import "CPView.j"
|
||||
|
||||
@import <Foundation/CPTimer.j>
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
|
||||
@import "jshashtable.j"
|
||||
@import "CSSAnimation.j"
|
||||
|
||||
@typedef HashTable;
|
||||
@typedef Map;
|
||||
|
||||
var _CPAnimationContextStack = nil,
|
||||
_animationFlushingObserver = nil,
|
||||
_animationFrameUpdaters = {};
|
||||
_animationFlushingObserver = nil;
|
||||
|
||||
@implementation CPAnimationContext : CPObject
|
||||
{
|
||||
double _duration @accessors(property=duration);
|
||||
CAMediaTimingFunction _timingFunction @accessors(property=timingFunction);
|
||||
Function _completionHandlerAgent;
|
||||
HashTable _animationsByObject;
|
||||
Map _animationsByObject;
|
||||
}
|
||||
|
||||
+ (id)currentContext
|
||||
@@ -64,7 +61,7 @@ var _CPAnimationContextStack = nil,
|
||||
_duration = 0.0;
|
||||
_timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
_completionHandlerAgent = nil;
|
||||
_animationsByObject = new Hashtable();
|
||||
_animationsByObject = new Map();
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -86,7 +83,7 @@ var _CPAnimationContextStack = nil,
|
||||
#if (DEBUG)
|
||||
CPLog.debug("create new observer");
|
||||
#endif
|
||||
_animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback,0);
|
||||
_animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback, 0);
|
||||
CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver);
|
||||
}
|
||||
}
|
||||
@@ -135,7 +132,7 @@ var _CPAnimationContextStack = nil,
|
||||
if (!animByKeyPath)
|
||||
{
|
||||
var newAnimByKeyPath = @{aKeyPath:resolvedAction};
|
||||
_animationsByObject.put(anObject, newAnimByKeyPath);
|
||||
_animationsByObject.set(anObject, newAnimByKeyPath);
|
||||
}
|
||||
else
|
||||
[animByKeyPath setObject:resolvedAction forKey:aKeyPath];
|
||||
@@ -149,29 +146,24 @@ var _CPAnimationContextStack = nil,
|
||||
values,
|
||||
keyTimes,
|
||||
timingFunctions,
|
||||
objectId;
|
||||
needsPeriodicFrameUpdates,
|
||||
objectId = [anObject UID];
|
||||
|
||||
if (!aKeyPath || !anObject || !(animation = [anObject animationForKey:aKeyPath]) || ![animation isKindOfClass:[CAAnimation class]])
|
||||
return nil;
|
||||
|
||||
duration = [animation duration] || [self duration];
|
||||
|
||||
var needsPeriodicFrameUpdates = (((aKeyPath == @"frame" || aKeyPath == @"frameSize") &&
|
||||
([anObject hasCustomLayoutSubviews] || [anObject hasCustomDrawRect])) || [[anObject animator] wantsPeriodicFrameUpdates]) &&
|
||||
(objectId = [anObject UID]);
|
||||
needsPeriodicFrameUpdates = [[anObject animator] needsPeriodicFrameUpdatesForKeyPath:aKeyPath];
|
||||
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.increment();
|
||||
|
||||
var animatorClass = [[anObject class] animatorClass];
|
||||
|
||||
var completionFunction = function()
|
||||
{
|
||||
if (needsPeriodicFrameUpdates)
|
||||
{
|
||||
[self stopFrameUpdaterWithIdentifier:objectId];
|
||||
|
||||
if ([anObject respondsToSelector:@selector(_setForceUpdates:)])
|
||||
[anObject _setForceUpdates:YES];
|
||||
}
|
||||
[animatorClass stopUpdaterWithIdentifier:objectId];
|
||||
|
||||
if (animationCompletion)
|
||||
animationCompletion();
|
||||
@@ -179,9 +171,6 @@ var _CPAnimationContextStack = nil,
|
||||
if (needsPeriodicFrameUpdates || animationCompletion)
|
||||
[[CPRunLoop currentRunLoop] performSelectors];
|
||||
|
||||
if (needsPeriodicFrameUpdates && [anObject respondsToSelector:@selector(_setForceUpdates:)])
|
||||
[anObject _setForceUpdates:NO];
|
||||
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.decrement();
|
||||
};
|
||||
@@ -214,6 +203,7 @@ var _CPAnimationContextStack = nil,
|
||||
|
||||
return {
|
||||
object:anObject,
|
||||
root:anObject,
|
||||
keypath:animatedKeyPath,
|
||||
values:values,
|
||||
keytimes:keyTimes,
|
||||
@@ -228,7 +218,7 @@ var _CPAnimationContextStack = nil,
|
||||
if (![_CPAnimationContextStack count])
|
||||
return;
|
||||
|
||||
if (_animationsByObject.size() == 0)
|
||||
if (_animationsByObject.size == 0)
|
||||
{
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.fire();
|
||||
@@ -239,28 +229,25 @@ var _CPAnimationContextStack = nil,
|
||||
|
||||
- (void)_startAnimations
|
||||
{
|
||||
var targetViews = _animationsByObject.keys(),
|
||||
cssAnimations = [],
|
||||
var cssAnimations = [],
|
||||
timers = [];
|
||||
|
||||
[targetViews enumerateObjectsUsingBlock:function(targetView, idx, stop)
|
||||
_animationsByObject.forEach(function(animByKeyPath, targetView)
|
||||
{
|
||||
var animByKeyPath = _animationsByObject.get(targetView);
|
||||
|
||||
[animByKeyPath enumerateKeysAndObjectsUsingBlock:function(aKey, anAction, stop)
|
||||
{
|
||||
[self getAnimations:cssAnimations getTimers:timers forView:targetView usingAction:anAction rootView:targetView cssAnimate:YES];
|
||||
[self getAnimations:cssAnimations getTimers:timers usingAction:anAction cssAnimate:YES];
|
||||
}];
|
||||
});
|
||||
|
||||
_animationsByObject.remove(targetView);
|
||||
}];
|
||||
_animationsByObject.clear();
|
||||
|
||||
// start timers
|
||||
var k = timers.length;
|
||||
while(k--)
|
||||
{
|
||||
#if (DEBUG)
|
||||
CPLog.debug("START TIMER " + timers[k].identifier());
|
||||
CPLog.debug("START TIMER " + timers[k].description());
|
||||
#endif
|
||||
timers[k].start();
|
||||
}
|
||||
@@ -270,96 +257,78 @@ var _CPAnimationContextStack = nil,
|
||||
while(n--)
|
||||
{
|
||||
#if (DEBUG)
|
||||
CPLog.debug("START ANIMATION " + cssAnimations[n].animationsnames);
|
||||
CPLog.debug("START ANIMATION " + cssAnimations[n].description());
|
||||
#endif
|
||||
cssAnimations[n].start();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers forView:(CPView)aTargetView usingAction:(Object)anAction rootView:(CPView)rootView cssAnimate:(BOOL)needsCSSAnimation
|
||||
- (void)getAnimations:(CPArray)cssAnimations getTimers:(CPArray)timers usingAction:(Object)anAction cssAnimate:(BOOL)needsCSSAnimation
|
||||
{
|
||||
var keyPath = anAction.keypath,
|
||||
isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
|
||||
customLayout = [aTargetView hasCustomLayoutSubviews],
|
||||
customDrawing = [aTargetView hasCustomDrawRect],
|
||||
needsPeriodicFrameUpdates = ((isFrameKeyPath && (customLayout || customDrawing)) || [[aTargetView animator] wantsPeriodicFrameUpdates]);
|
||||
var values = anAction.values;
|
||||
|
||||
if (values.length == 2)
|
||||
{
|
||||
var start = values[0],
|
||||
end = values[1];
|
||||
|
||||
if (anAction.keypath == @"frame" && CGRectEqualToRect(start, end)
|
||||
|| anAction.keypath == @"frameSize" && CGSizeEqualToSize(start, end)
|
||||
|| anAction.keypath == @"frameOrigin" && CGPointEqualToPoint(start, end))
|
||||
return;
|
||||
}
|
||||
|
||||
var targetView = anAction.object,
|
||||
keyPath = anAction.keypath,
|
||||
isFrameKeyPath = (keyPath == @"frame" || keyPath == @"frameSize"),
|
||||
customLayout = [targetView hasCustomLayoutSubviews],
|
||||
customDrawing = [targetView hasCustomDrawRect],
|
||||
declarative_subviews_layout = (!customLayout || [targetView implementsSelector:@selector(frameRectOfView:inSuperviewSize:)]),
|
||||
needsPeriodicFrameUpdates = [[targetView animator] needsPeriodicFrameUpdatesForKeyPath:keyPath],
|
||||
timer = nil,
|
||||
animatorClass = [[targetView class] animatorClass];
|
||||
|
||||
if (needsCSSAnimation)
|
||||
{
|
||||
var identifier = [aTargetView UID],
|
||||
duration = anAction.duration,
|
||||
timingFunctions = anAction.timingfunctions,
|
||||
properties = [],
|
||||
valueFunctions = [],
|
||||
cssAnimation = nil;
|
||||
|
||||
[cssAnimations enumerateObjectsUsingBlock:function(anim, idx, stop)
|
||||
{
|
||||
if (anim.identifier == identifier)
|
||||
{
|
||||
cssAnimation = anim;
|
||||
stop(YES);
|
||||
}
|
||||
}];
|
||||
|
||||
if (cssAnimation == nil)
|
||||
{
|
||||
var domElement = [aTargetView DOMElementForKeyPath:keyPath];
|
||||
cssAnimation = new CSSAnimation(domElement, identifier);
|
||||
cssAnimations.push(cssAnimation);
|
||||
}
|
||||
|
||||
var css_mapping = [[aTargetView class] _cssPropertiesForKeyPath:keyPath];
|
||||
|
||||
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
|
||||
{
|
||||
var completionFunction = (anIndex == 0) ? anAction.completion : null;
|
||||
var property = [aDict objectForKey:@"property"],
|
||||
getter = [aDict objectForKey:@"value"];
|
||||
|
||||
cssAnimation.addPropertyAnimation(property, getter, duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction);
|
||||
}];
|
||||
[animatorClass addAnimations:cssAnimations forAction:anAction];
|
||||
}
|
||||
|
||||
if (needsPeriodicFrameUpdates)
|
||||
{
|
||||
var timer = [self addFrameUpdaterWithIdentifier:[rootView UID] forView:aTargetView keyPath:keyPath duration:anAction.duration];
|
||||
|
||||
if (timer)
|
||||
timers.push(timer);
|
||||
[animatorClass addFrameUpdaters:timers forAction:anAction];
|
||||
}
|
||||
|
||||
var subviews = [aTargetView subviews],
|
||||
count = [subviews count],
|
||||
customLayout = [aTargetView hasCustomLayoutSubviews];
|
||||
var subviews = [targetView subviews],
|
||||
count = [subviews count];
|
||||
|
||||
if (count && isFrameKeyPath)
|
||||
{
|
||||
var frameTimerId = [rootView UID],
|
||||
lastIndex = count - 1;
|
||||
|
||||
[subviews enumerateObjectsUsingBlock:function(aSubview, idx, stop)
|
||||
{
|
||||
var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
|
||||
{
|
||||
if (!declarative_subviews_layout && [aSubview autoresizingMask] == 0)
|
||||
return;
|
||||
|
||||
var action = [self actionFromAction:anAction forAnimatedSubview:aSubview],
|
||||
targetFrame = [action.values lastObject];
|
||||
|
||||
if (CGRectEqualToRect([aSubview frame], targetFrame))
|
||||
return;
|
||||
if (CGRectEqualToRect([aSubview frame], targetFrame))
|
||||
return;
|
||||
|
||||
if ([aSubview hasCustomDrawRect])
|
||||
{
|
||||
action.completion = function()
|
||||
{
|
||||
[aSubview setFrame:targetFrame];
|
||||
if ([aSubview hasCustomDrawRect])
|
||||
{
|
||||
action.completion = function()
|
||||
{
|
||||
[aSubview setFrame:targetFrame];
|
||||
#if (DEBUG)
|
||||
CPLog.debug(aSubview + " setFrame: ");
|
||||
CPLog.debug(aSubview + " setFrame: " + CPStringFromRect(targetFrame));
|
||||
#endif
|
||||
if (idx == lastIndex)
|
||||
[self stopFrameUpdaterWithIdentifier:frameTimerId];
|
||||
if (idx == count - 1)
|
||||
[animatorClass stopUpdaterWithIdentifier:[anAction.root UID]];
|
||||
};
|
||||
}
|
||||
|
||||
[self getAnimations:cssAnimations getTimers:timers forView:aSubview usingAction:action rootView:rootView cssAnimate:!customLayout];
|
||||
var animate = !needsPeriodicFrameUpdates;
|
||||
[self getAnimations:cssAnimations getTimers:timers usingAction:action cssAnimate:animate];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -367,17 +336,19 @@ var _CPAnimationContextStack = nil,
|
||||
- (Object)actionFromAction:(Object)anAction forAnimatedSubview:(CPView)aView
|
||||
{
|
||||
var targetValue = [anAction.values lastObject],
|
||||
startFrame = [aView frame],
|
||||
endFrame,
|
||||
values;
|
||||
|
||||
if (anAction.keypath == @"frame")
|
||||
if (anAction.keypath == "frame")
|
||||
targetValue = targetValue.size;
|
||||
|
||||
endFrame = [aView frameWithNewSuperviewSize:targetValue];
|
||||
values = [[aView frame], endFrame];
|
||||
endFrame = [[aView superview] frameRectOfView:aView inSuperviewSize:targetValue];
|
||||
values = [startFrame, endFrame];
|
||||
|
||||
return {
|
||||
object:aView,
|
||||
root:anAction.root,
|
||||
keypath:"frame",
|
||||
values:values,
|
||||
keytimes:[0, 1],
|
||||
@@ -386,36 +357,6 @@ var _CPAnimationContextStack = nil,
|
||||
};
|
||||
}
|
||||
|
||||
- (Function)addFrameUpdaterWithIdentifier:(CPString)anIdentifier forView:(CPView)aView keyPath:(CPString)aKeyPath duration:(float)aDuration
|
||||
{
|
||||
var frameUpdater = _animationFrameUpdaters[anIdentifier],
|
||||
result = nil;
|
||||
|
||||
if (frameUpdater == null)
|
||||
{
|
||||
frameUpdater = new FrameUpdater(anIdentifier);
|
||||
_animationFrameUpdaters[anIdentifier] = frameUpdater;
|
||||
result = frameUpdater;
|
||||
}
|
||||
|
||||
frameUpdater.addTarget(aView, aKeyPath, aDuration);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)stopFrameUpdaterWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
var frameUpdater = _animationFrameUpdaters[anIdentifier];
|
||||
|
||||
if (frameUpdater)
|
||||
{
|
||||
frameUpdater.stop();
|
||||
delete _animationFrameUpdaters[anIdentifier];
|
||||
}
|
||||
else
|
||||
CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier);
|
||||
}
|
||||
|
||||
- (void)setCompletionHandler:(Function)aCompletionHandler
|
||||
{
|
||||
if (_completionHandlerAgent)
|
||||
@@ -436,11 +377,15 @@ var _CPAnimationContextStack = nil,
|
||||
|
||||
@implementation CPView (CPAnimationContext)
|
||||
|
||||
- (CGRect)frameRectOfView:(CPView)aView inSuperviewSize:(CGSize)aSize
|
||||
{
|
||||
return [aView frameWithNewSuperviewSize:aSize];
|
||||
}
|
||||
|
||||
- (CGRect)frameWithNewSuperviewSize:(CGSize)newSize
|
||||
{
|
||||
var mask = [self autoresizingMask];
|
||||
|
||||
|
||||
if (mask == CPViewNotSizable)
|
||||
return _frame;
|
||||
|
||||
@@ -585,160 +530,3 @@ var _animationFlushingObserverCallback = function()
|
||||
_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._requestId = null;
|
||||
this._duration = 0;
|
||||
this._stop = false;
|
||||
this._targets = [];
|
||||
this._callbacks = [];
|
||||
|
||||
var frameUpdater = this;
|
||||
|
||||
this._updateFunction = function(timestamp)
|
||||
{
|
||||
if (frameUpdater._startDate == null)
|
||||
frameUpdater._startDate = timestamp;
|
||||
|
||||
if (frameUpdater._stop)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < frameUpdater._callbacks.length; i++)
|
||||
frameUpdater._callbacks[i]();
|
||||
|
||||
if (timestamp - frameUpdater._startDate < frameUpdater._duration * 1000)
|
||||
window.requestAnimationFrame(frameUpdater._updateFunction);
|
||||
};
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.start = function()
|
||||
{
|
||||
this._requestId = window.requestAnimationFrame(this._updateFunction);
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.stop = function()
|
||||
{
|
||||
// window.cancelAnimationFrame support is Chrome 24, Firefox 23, IE 10, Opera 15, Safari 6.1
|
||||
if (window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame(this._requestId);
|
||||
|
||||
this._stop = true;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.updateFunction = function()
|
||||
{
|
||||
return this._updateFunction;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.identifier = function()
|
||||
{
|
||||
return this._identifier;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.addTarget = function(target, keyPath, duration)
|
||||
{
|
||||
var callback = createUpdateFrame(target, keyPath);
|
||||
|
||||
if (callback)
|
||||
{
|
||||
this._duration = MAX(this._duration, duration);
|
||||
this._targets.push(target);
|
||||
this._callbacks.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
var createUpdateFrame = function(aView, aKeyPath)
|
||||
{
|
||||
if (aKeyPath !== "frame" && aKeyPath !== "frameSize" && aKeyPath !== "frameOrigin")
|
||||
return nil;
|
||||
|
||||
var style = getComputedStyle(aView._DOMElement),
|
||||
getCSSPropertyValue = function(prop) {
|
||||
return ROUND(parseFloat(style.getPropertyValue(prop)));
|
||||
},
|
||||
initialOrigin = CGPointMakeCopy([aView frameOrigin]),
|
||||
transformProperty = CPBrowserCSSProperty("transform"),
|
||||
updateFrame = function(timestamp)
|
||||
{
|
||||
var width = getCSSPropertyValue("width"),
|
||||
height = getCSSPropertyValue("height");
|
||||
|
||||
if (aKeyPath === "frameSize")
|
||||
{
|
||||
[aView setFrameSize:CGSizeMake(width, height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[aView _setInhibitDOMUpdates:YES];
|
||||
|
||||
var matrix = style[transformProperty].split('(')[1].split(')')[0].split(','),
|
||||
x = ROUND(initialOrigin.x + parseFloat(matrix[4])),
|
||||
y = ROUND(initialOrigin.y + parseFloat(matrix[5]));
|
||||
|
||||
if (aKeyPath === "frame")
|
||||
{
|
||||
[aView setFrame:CGRectMake(x, y, width, height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[aView setFrameOrigin:CGPointMake(x, y)];
|
||||
}
|
||||
|
||||
[aView _setInhibitDOMUpdates:NO];
|
||||
}
|
||||
|
||||
[[CPRunLoop currentRunLoop] performSelectors];
|
||||
};
|
||||
|
||||
return updateFrame;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
@import "_CPObjectAnimator.j"
|
||||
@import "CPView.j"
|
||||
@import "CPCompatibility.j"
|
||||
@import "CSSAnimation.j"
|
||||
|
||||
var DEFAULT_CSS_PROPERTIES = nil,
|
||||
FRAME_UPDATERS = {};
|
||||
|
||||
@implementation CPViewAnimator : _CPObjectAnimator
|
||||
{
|
||||
@@ -63,7 +67,9 @@
|
||||
{
|
||||
var handler = function()
|
||||
{
|
||||
[_target _setForceUpdates:YES];
|
||||
[_target performSelector:aSelector withObject:aTargetValue];
|
||||
[_target _setForceUpdates:NO];
|
||||
};
|
||||
|
||||
[self _setTargetValue:aTargetValue withKeyPath:aKeyPath fallback:handler completion:handler];
|
||||
@@ -74,7 +80,7 @@
|
||||
var animation = [_target animationForKey:aKeyPath],
|
||||
context = [CPAnimationContext currentContext];
|
||||
|
||||
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || ![_CPObjectAnimator supportsCSSAnimations])
|
||||
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || !CPFeatureIsCompatible(CPCSSAnimationFeature))
|
||||
{
|
||||
if (fallback)
|
||||
fallback();
|
||||
@@ -85,6 +91,105 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (CPDictionary)_defaultCSSProperties
|
||||
{
|
||||
if (DEFAULT_CSS_PROPERTIES == nil)
|
||||
{
|
||||
var transformProperty = "transform";
|
||||
|
||||
DEFAULT_CSS_PROPERTIES = @{
|
||||
"backgroundColor" : [@{"property":"background", "value":function(sv, val){return [val cssString];}}],
|
||||
"alphaValue" : [@{"property":"opacity"}],
|
||||
"frame" : [@{"property":transformProperty, "value":frameToCSSTranslationTransformMatrix},
|
||||
@{"property":"width", "value":transformFrameToWidth},
|
||||
@{"property":"height", "value":transformFrameToHeight}],
|
||||
"frameOrigin" : [@{"property":transformProperty, "value":frameOriginToCSSTransformMatrix}],
|
||||
"frameSize" : [@{"property":"width", "value":transformSizeToWidth},
|
||||
@{"property":"height", "value":transformSizeToHeight}]
|
||||
};
|
||||
}
|
||||
|
||||
return DEFAULT_CSS_PROPERTIES;
|
||||
}
|
||||
|
||||
+ (void)addAnimations:(CPArray)animations forAction:(id)anAction
|
||||
{
|
||||
var target = anAction.object;
|
||||
|
||||
return [self _addAnimations:animations forAction:anAction domElement:[target _DOMElement] identifier:[target UID]];
|
||||
}
|
||||
|
||||
+ (void)_addAnimations:(CPArray)animations forAction:(id)anAction domElement:(Object)aDomElement identifier:(CPString)anIdentifier
|
||||
{
|
||||
var animation = [animations objectPassingTest:function(anim, idx, stop)
|
||||
{
|
||||
return anim.identifier == anIdentifier;
|
||||
}];
|
||||
|
||||
if (animation == nil)
|
||||
{
|
||||
animation = new CSSAnimation(aDomElement, anIdentifier, [anAction.object debug_description]);
|
||||
[animations addObject:animation];
|
||||
}
|
||||
|
||||
var css_mapping = [self _cssPropertiesForKeyPath:anAction.keypath];
|
||||
|
||||
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
|
||||
{
|
||||
var completionFunction = (anIndex == 0) ? anAction.completion : null,
|
||||
property = [aDict objectForKey:@"property"],
|
||||
getter = [aDict objectForKey:@"value"];
|
||||
|
||||
animation.addPropertyAnimation(property, getter, anAction.duration, anAction.keytimes, anAction.values, anAction.timingfunctions, completionFunction);
|
||||
}];
|
||||
}
|
||||
|
||||
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
|
||||
}
|
||||
|
||||
+ (void)addFrameUpdaters:(CPArray)frameUpdaters forAction:(id)anAction
|
||||
{
|
||||
var rootIdentifier = [anAction.root UID];
|
||||
|
||||
var frameUpdater = [frameUpdaters objectPassingTest:function(updater, idx, stop)
|
||||
{
|
||||
// There is one timer, linked to the top view, that updates the whole hierarchy.
|
||||
return updater.identifier() == rootIdentifier;
|
||||
}];
|
||||
|
||||
if (frameUpdater == nil)
|
||||
{
|
||||
frameUpdater = new FrameUpdater(rootIdentifier);
|
||||
[frameUpdaters addObject:frameUpdater];
|
||||
FRAME_UPDATERS[rootIdentifier] = frameUpdater;
|
||||
}
|
||||
|
||||
frameUpdater.addTarget(anAction.object, anAction.keypath, anAction.duration);
|
||||
}
|
||||
|
||||
+ (void)stopUpdaterWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
var frameUpdater = FRAME_UPDATERS[anIdentifier];
|
||||
|
||||
if (frameUpdater)
|
||||
{
|
||||
frameUpdater.stop();
|
||||
delete FRAME_UPDATERS[anIdentifier];
|
||||
}
|
||||
else
|
||||
CPLog.warn("Could not find FrameUpdater with identifier " + anIdentifier);
|
||||
}
|
||||
|
||||
- (BOOL)needsPeriodicFrameUpdatesForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return ((aKeyPath == @"frame" || aKeyPath == @"frameSize") &&
|
||||
(([_target hasCustomLayoutSubviews] && ![_target implementsSelector:@selector(frameRectOfView:inSuperviewSize:)])
|
||||
|| [_target hasCustomDrawRect]))
|
||||
|| [self wantsPeriodicFrameUpdates];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var transformFrameToWidth = function(start, current)
|
||||
@@ -126,36 +231,8 @@ var frameToCSSTranslationTransformMatrix = function(start, current)
|
||||
return CSSStringFromCGAffineTransform(affine);
|
||||
};
|
||||
|
||||
var DEFAULT_CSS_PROPERTIES = nil;
|
||||
|
||||
@implementation CPView (CPAnimatablePropertyContainer)
|
||||
|
||||
+ (CPDictionary)_defaultCSSProperties
|
||||
{
|
||||
if (DEFAULT_CSS_PROPERTIES == nil)
|
||||
{
|
||||
var transformProperty = CPBrowserCSSProperty("transform");
|
||||
|
||||
DEFAULT_CSS_PROPERTIES = @{
|
||||
"backgroundColor" : [@{"property":"background", "value":function(sv, val){return [val cssString];}}],
|
||||
"alphaValue" : [@{"property":"opacity"}],
|
||||
"frame" : [@{"property":transformProperty, "value":frameToCSSTranslationTransformMatrix},
|
||||
@{"property":"width", "value":transformFrameToWidth},
|
||||
@{"property":"height", "value":transformFrameToHeight}],
|
||||
"frameOrigin" : [@{"property":transformProperty, "value":frameOriginToCSSTransformMatrix}],
|
||||
"frameSize" : [@{"property":"width", "value":transformSizeToWidth},
|
||||
@{"property":"height", "value":transformSizeToHeight}]
|
||||
};
|
||||
}
|
||||
|
||||
return DEFAULT_CSS_PROPERTIES;
|
||||
}
|
||||
|
||||
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
|
||||
}
|
||||
|
||||
+ (Class)animatorClass
|
||||
{
|
||||
var anim_class = CPClassFromString(CPStringFromClass(self) + "Animator");
|
||||
@@ -174,11 +251,6 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
return _animator;
|
||||
}
|
||||
|
||||
- (id)DOMElementForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return _DOMElement;
|
||||
}
|
||||
|
||||
+ (CAAnimation)defaultAnimationForKey:(CPString)aKey
|
||||
{
|
||||
// TODO: remove when supported.
|
||||
@@ -188,7 +260,7 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([self _cssPropertiesForKeyPath:aKey] !== nil)
|
||||
if ([[self animatorClass] _cssPropertiesForKeyPath:aKey] !== nil)
|
||||
return [CAAnimation animation];
|
||||
|
||||
return nil;
|
||||
@@ -217,4 +289,147 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
_animationsDictionary = [animationsDict copy];
|
||||
}
|
||||
|
||||
- (Object)_DOMElement
|
||||
{
|
||||
return _DOMElement;
|
||||
}
|
||||
|
||||
- (CPString)debug_description
|
||||
{
|
||||
return [self identifier] || [self className];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPArray (Additions)
|
||||
|
||||
- (CPArray)objectPassingTest:(Function)aFunction
|
||||
{
|
||||
var idx = [self indexOfObjectPassingTest:aFunction];
|
||||
|
||||
if (idx !== CPNotFound)
|
||||
return [self objectAtIndex:idx];
|
||||
|
||||
return nil;
|
||||
}
|
||||
@end
|
||||
|
||||
var FrameUpdater = function(anIdentifier)
|
||||
{
|
||||
this._identifier = anIdentifier;
|
||||
this._requestId = null;
|
||||
this._duration = 0;
|
||||
this._stop = false;
|
||||
this._targets = [];
|
||||
this._callbacks = [];
|
||||
|
||||
var frameUpdater = this;
|
||||
|
||||
this._updateFunction = function(timestamp)
|
||||
{
|
||||
if (frameUpdater._startDate == null)
|
||||
frameUpdater._startDate = timestamp;
|
||||
|
||||
if (frameUpdater._stop)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < frameUpdater._callbacks.length; i++)
|
||||
frameUpdater._callbacks[i]();
|
||||
|
||||
if (timestamp - frameUpdater._startDate < frameUpdater._duration * 1000)
|
||||
window.requestAnimationFrame(frameUpdater._updateFunction);
|
||||
};
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.start = function()
|
||||
{
|
||||
this._requestId = window.requestAnimationFrame(this._updateFunction);
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.stop = function()
|
||||
{
|
||||
CPLog.warn("STOP FrameUpdater" + this._identifier);
|
||||
|
||||
// window.cancelAnimationFrame support is Chrome 24, Firefox 23, IE 10, Opera 15, Safari 6.1
|
||||
if (window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame(this._requestId);
|
||||
|
||||
this._stop = true;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.updateFunction = function()
|
||||
{
|
||||
return this._updateFunction;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.identifier = function()
|
||||
{
|
||||
return this._identifier;
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.description = function()
|
||||
{
|
||||
return "<timer " + this._identifier + " " + this._targets.map(function(t){return [t debug_description];}) + ">";
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.addTarget = function(target, keyPath, duration)
|
||||
{
|
||||
var callback = createUpdateFrame(target, keyPath);
|
||||
|
||||
if (callback)
|
||||
{
|
||||
this._duration = MAX(this._duration, duration);
|
||||
this._targets.push(target);
|
||||
this._callbacks.push(callback);
|
||||
}
|
||||
};
|
||||
|
||||
var createUpdateFrame = function(aView, aKeyPath)
|
||||
{
|
||||
if (aKeyPath !== "frame" && aKeyPath !== "frameSize" && aKeyPath !== "frameOrigin")
|
||||
return nil;
|
||||
|
||||
var style = getComputedStyle([aView _DOMElement]),
|
||||
getCSSPropertyValue = function(prop) {
|
||||
return ROUND(parseFloat(style.getPropertyValue(prop)));
|
||||
},
|
||||
initialOrigin = CGPointMakeCopy([aView frameOrigin]),
|
||||
transformProperty = CPBrowserStyleProperty("transform"),
|
||||
updateFrame = function(timestamp)
|
||||
{
|
||||
if (aKeyPath === "frameSize")
|
||||
{
|
||||
var width = getCSSPropertyValue("width"),
|
||||
height = getCSSPropertyValue("height");
|
||||
|
||||
[aView setFrameSize:CGSizeMake(width, height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[aView _setInhibitDOMUpdates:YES];
|
||||
|
||||
var matrix = style[transformProperty].split('(')[1].split(')')[0].split(','),
|
||||
x = ROUND(initialOrigin.x + parseFloat(matrix[4])),
|
||||
y = ROUND(initialOrigin.y + parseFloat(matrix[5]));
|
||||
|
||||
if (aKeyPath === "frame")
|
||||
{
|
||||
var width = getCSSPropertyValue("width"),
|
||||
height = getCSSPropertyValue("height");
|
||||
|
||||
[aView setFrame:CGRectMake(x, y, width, height)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[aView setFrameOrigin:CGPointMake(x, y)];
|
||||
}
|
||||
|
||||
[aView _setInhibitDOMUpdates:NO];
|
||||
}
|
||||
|
||||
[[CPRunLoop currentRunLoop] performSelectors];
|
||||
// CPLog.debug("update " + [aView debug_description]);
|
||||
};
|
||||
|
||||
return updateFrame;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ var removeFromParent = function(aNode)
|
||||
parentNode.removeChild(aNode);
|
||||
};
|
||||
|
||||
CSSAnimation = function(aTarget/* DOM Element */, anIdentifier)
|
||||
CSSAnimation = function(aTarget/* DOM Element */, anIdentifier, aTargetName)
|
||||
{
|
||||
defineCSSProperties();
|
||||
|
||||
@@ -52,6 +52,7 @@ CSSAnimation = function(aTarget/* DOM Element */, anIdentifier)
|
||||
{
|
||||
this.target = aTarget;
|
||||
this.identifier = anIdentifier;
|
||||
this.targetName = aTargetName;
|
||||
this.animationName = animationName;
|
||||
this.listener = null;
|
||||
this.styleElement = null;
|
||||
@@ -69,6 +70,11 @@ CSSAnimation = function(aTarget/* DOM Element */, anIdentifier)
|
||||
return animation;
|
||||
};
|
||||
|
||||
CSSAnimation.prototype.description = function()
|
||||
{
|
||||
return "<animation " + this.identifier + " target=" + this.targetName + " properties=" + this.propertyanimations.map(function(anim){return anim.property;}).join(",") + " >";
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -20,12 +20,13 @@ var _supportsCSSAnimations = null;
|
||||
id <CPAnimatablePropertyContainer> _target;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsCSSAnimations
|
||||
+ (BOOL)initialize
|
||||
{
|
||||
if (_supportsCSSAnimations === null)
|
||||
_supportsCSSAnimations = CPBrowserCSSProperty("animation");
|
||||
if ([self class] !== [_CPObjectAnimator class])
|
||||
return;
|
||||
|
||||
return _supportsCSSAnimations;
|
||||
var compat = (CPBrowserCSSProperty("animation") !== nil);
|
||||
CPSetPlatformFeature(CPCSSAnimationFeature, compat);
|
||||
}
|
||||
|
||||
- (id)initWithTarget:(id)aTarget
|
||||
@@ -77,11 +78,11 @@ var _supportsCSSAnimations = null;
|
||||
var animation = [_target animationForKey:aKeyPath],
|
||||
context = [CPAnimationContext currentContext];
|
||||
|
||||
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || ![_CPObjectAnimator supportsCSSAnimations])
|
||||
if (!animation || ![animation isKindOfClass:[CAAnimation class]] || (![context duration] && ![animation duration]) || !CPFeatureIsCompatible(CPCSSAnimationFeature))
|
||||
[_target setValue:aTargetValue forKey:aKeyPath];
|
||||
else
|
||||
{
|
||||
[context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue completionHandler:function()
|
||||
[context _enqueueActionForObject:_target keyPath:aKeyPath targetValue:aTargetValue animationCompletion:function()
|
||||
{
|
||||
[_target setValue:aTargetValue forKey:aKeyPath];
|
||||
}];
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
})();
|
||||
@@ -494,3 +494,51 @@ var CPRunLoopLastNativeRunLoop = 0;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
function CFRunLoopObserver(activities, repeats, order, callout, context)
|
||||
{
|
||||
this.activities = activities;
|
||||
this.repeats = repeats;
|
||||
this.order = order;
|
||||
this.callout = callout;
|
||||
this.context = context;
|
||||
|
||||
this.isvalid = true;
|
||||
};
|
||||
|
||||
function CFRunLoopObserverCreate(activities, repeats, order, callout, context)
|
||||
{
|
||||
return new CFRunLoopObserver(activities, repeats, order, callout, context);
|
||||
};
|
||||
|
||||
function CFRunLoopAddObserver(runloop, observer, mode)
|
||||
{
|
||||
var observers = runloop._observers;
|
||||
|
||||
if (!observers)
|
||||
observers = (runloop._observers = []);
|
||||
|
||||
if (observers.indexOf(observer) == -1)
|
||||
observers.push(observer);
|
||||
};
|
||||
|
||||
function CFRunLoopObserverInvalidate(runloop, observer, mode)
|
||||
{
|
||||
CFRunLoopRemoveObserver(runloop, observer, mode);
|
||||
};
|
||||
|
||||
function CFRunLoopRemoveObserver(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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11542" systemVersion="15G1108" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1217" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment version="1050" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
<capability name="box content view" minToolsVersion="7.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -13,7 +13,7 @@
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="371">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
|
||||
@@ -347,9 +347,9 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button tag="1003" id="821">
|
||||
<rect key="frame" x="35" y="8" width="128" height="18"/>
|
||||
<rect key="frame" x="35" y="8" width="154" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Custom Drawing" bezelStyle="regularSquare" imagePosition="left" inset="2" id="822">
|
||||
<buttonCell key="cell" type="check" title="With Custom Drawing" bezelStyle="regularSquare" imagePosition="left" inset="2" id="822">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
@@ -360,7 +360,7 @@
|
||||
<button tag="1004" id="824">
|
||||
<rect key="frame" x="35" y="29" width="128" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Auto Layout" bezelStyle="regularSquare" imagePosition="left" inset="2" id="825">
|
||||
<buttonCell key="cell" type="check" title="With Autosize" bezelStyle="regularSquare" imagePosition="left" inset="2" id="825">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
|
||||
Reference in New Issue
Block a user