blob: 8bb616d423c48719f872d59e24128afbb6529c8d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#define SOLVE_EPS(dur) (1. / (1000. * (dur)))
#import <QuartzCore/CATransaction.h>
#import <QuartzCore/CADisplayLink.h>
#import "WXComponentManager.h"
#import "WXSDKInstance.h"
#import "WXComponent+Layout.h"
#import "WXComponent_internal.h"
#import "WXTransition.h"
#import "WXUtility.h"
#import "WXAssert.h"
#import "WXSDKInstance_private.h"
#import "WXLength.h"
@implementation WXTransitionInfo
@end
@interface WXTransition()
{
double ax;
double bx;
double cx;
double ay;
double by;
double cy;
float _transitionDuration;
float _transitionDelay;
NSUInteger _transitionCount;
CAMediaTimingFunction *_transitionTimingFunction;
CADisplayLink *_transitionDisplayLink;
NSMutableDictionary *_filterStyles;
NSMutableDictionary *_oldFilterStyles;
}
@property (nonatomic,weak) WXComponent *targetComponent;
@end
@implementation WXTransition
- (instancetype)initWithStyles:(NSDictionary *)styles
{
if (self = [super init]) {
NSString *property = styles[kWXTransitionProperty];
NSArray *properties = [property componentsSeparatedByString:@","];
for (NSString *string in properties) {
_transitionOptions |= [self transitionOptionsFromString:string];
}
}
return self;
}
#pragma mark - HandleStyle
- (WXTransitionOptions)transitionOptionsFromString:(NSString *)string
{
static NSDictionary<NSString*, NSNumber*> *options = @{
@"width": @(WXTransitionOptionsWidth),
@"height": @(WXTransitionOptionsHeight),
@"right": @(WXTransitionOptionsRight),
@"left": @(WXTransitionOptionsLeft),
@"bottom": @(WXTransitionOptionsBottom),
@"top": @(WXTransitionOptionsTop),
@"backgroundColor": @(WXTransitionOptionsBackgroundColor),
@"transform": @(WXTransitionOptionsTransform),
@"opacity": @(WXTransitionOptionsOpacity)
};
return options[string].integerValue;
}
- (void)_handleTransitionWithStyles:(NSDictionary *)styles resetStyles:(NSMutableArray *)resetStyles target:(WXComponent *)targetComponent
{
BOOL isRunning = [self _isTransitionRunning];
if (isRunning) {
[self _rollBackTransitionWithStyles:styles];
}
else
{
[self _suspendTransitionDisplayLink];
}
if (!targetComponent) {
return;
}
_filterStyles = _filterStyles ?:[NSMutableDictionary new];
_oldFilterStyles = _oldFilterStyles ?: [NSMutableDictionary new];
NSMutableDictionary *futileStyles = [NSMutableDictionary new];
for (NSString *key in styles) {
if (self.transitionOptions & [self transitionOptionsFromString:key]) {
[_filterStyles setObject:styles[key] forKey:key];
if (![key isEqualToString:@"transform"]) {
if (!isRunning) {
/* style value may not be in component.styles, so we must get
value from layout and convert it to style value. */
id styleValue = targetComponent.styles[key];
if (styleValue == nil) {
styleValue = [targetComponent convertLayoutValueToStyleValue:key];
}
[_oldFilterStyles setObject:styleValue forKey:key];
}
}
}
else
{
[futileStyles setObject:styles[key] forKey:key];
}
}
[self updateFutileStyles:futileStyles resetStyles:nil target:targetComponent];
_targetComponent = targetComponent;
NSMutableDictionary *componentStyles = [NSMutableDictionary dictionaryWithDictionary:styles];
[componentStyles addEntriesFromDictionary:targetComponent.styles];
_transitionDuration = componentStyles[kWXTransitionDuration] ? [WXConvert CGFloat:componentStyles[kWXTransitionDuration]] : 0;
_transitionDelay = componentStyles[kWXTransitionDelay] ? [WXConvert CGFloat:componentStyles[kWXTransitionDelay]] : 0;
_transitionTimingFunction = [WXConvert CAMediaTimingFunction:componentStyles[kWXTransitionTimingFunction]];
if (_transitionDuration == 0 ) {
[self updateFutileStyles:_filterStyles resetStyles:nil target:targetComponent];
return;
}
if (![[NSString stringWithFormat:@"%@",_transitionTimingFunction] isEqualToString: kCAMediaTimingFunctionLinear]) {
float vec[4] = {0.};
[_transitionTimingFunction getControlPointAtIndex:1 values:&vec[0]];
[_transitionTimingFunction getControlPointAtIndex:2 values:&vec[2]];
[self unitBezierp1x:vec[0] p1y:vec[1] p2x:vec[2] p2y:vec[3]];
}
[self _resloveTransitionProperty];
[self performSelector:@selector(_startTransitionDisplayLink) withObject:self afterDelay:_transitionDelay/1000];
}
- (BOOL)_hasTransitionOptionInStyles:(NSDictionary *)styles
{
for (NSString *key in styles) {
if (self.transitionOptions & [self transitionOptionsFromString:key]) {
return YES;
}
}
return NO;
}
- (void)updateFutileStyles:(NSDictionary *)styles resetStyles:(NSMutableArray *)resetStyles target:(WXComponent *)targetComponent
{
if (!targetComponent) {
return;
}
[targetComponent _updateCSSNodeStyles:styles];
[targetComponent _resetCSSNodeStyles:resetStyles];
NSDictionary* dupStyles = [NSDictionary dictionaryWithDictionary:styles];
WXPerformBlockOnMainThread(^{
[targetComponent _updateViewStyles:dupStyles];
});
}
- (void)_rollBackTransitionWithStyles:(NSDictionary *)styles
{
_transitionDuration = _transitionCount * 1000 / 60;
_transitionCount = 0;
_propertyArray = nil;
}
- (void)_resloveTransitionProperty
{
if (_filterStyles.count == 0) {
return;
}
for (NSString * name in _filterStyles.allKeys) {
[self _dealTransitionWithProperty:name];
}
}
- (void)_dealTransitionWithProperty:(NSString *)singleProperty
{
if (_filterStyles[singleProperty])
{
if (!_propertyArray) {
_propertyArray = [NSMutableArray new];
}
if ([singleProperty isEqualToString:@"backgroundColor"]) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.fromValue = [self _dealWithColor:[WXConvert UIColor:_oldFilterStyles[singleProperty]]];
info.toValue = [self _dealWithColor:[WXConvert UIColor:_filterStyles[singleProperty]]];
info.perValue = [self _calculatePerColorRGB1:info.toValue RGB2:info.fromValue];
info.propertyName = singleProperty;
[_propertyArray addObject:info];
}
else if ([singleProperty isEqualToString:@"transform"]) {
NSString *transformOrigin = _filterStyles[@"transformOrigin"];
WXTransform *wxTransform = [[WXTransform alloc] initWithCSSValue:_filterStyles[singleProperty] origin:transformOrigin instance:_targetComponent.weexInstance];
WXTransform *oldTransform = _targetComponent?_targetComponent->_transform:wxTransform;
if (wxTransform.rotateAngle != oldTransform.rotateAngle) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.rotation";
info.fromValue = @(oldTransform.rotateAngle);
info.toValue = [NSNumber numberWithDouble:wxTransform.rotateAngle];
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.rotateX != oldTransform.rotateX)
{
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.rotation.x";
info.fromValue = @(oldTransform.rotateX);
info.toValue = [NSNumber numberWithDouble:wxTransform.rotateX];
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.rotateY != oldTransform.rotateY)
{
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.rotation.y";
info.fromValue = @(oldTransform.rotateY);
info.toValue = [NSNumber numberWithDouble:wxTransform.rotateY];
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.rotateZ != oldTransform.rotateZ)
{
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.rotation.z";
info.fromValue = @(oldTransform.rotateZ);
info.toValue = [NSNumber numberWithDouble:wxTransform.rotateZ];
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.scaleX != oldTransform.scaleX) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.scale.x";
info.fromValue = @(oldTransform.scaleX);
info.toValue = @(wxTransform.scaleX);
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.scaleY != oldTransform.scaleY) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.scale.y";
info.fromValue = @(oldTransform.scaleY);
info.toValue = @(wxTransform.scaleY);
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
[_propertyArray addObject:info];
}
if (wxTransform.translateX && [wxTransform.translateX floatValue] !=[oldTransform.translateX floatValue]) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.translation.x";
info.fromValue = @([oldTransform.translateX floatValue]);
info.toValue = @([wxTransform.translateX floatValue]);
info.perValue = @([wxTransform.translateX floatValue] - [oldTransform.translateX floatValue]);
[_propertyArray addObject:info];
}
if (wxTransform.translateY && [wxTransform.translateY floatValue] !=[oldTransform.translateY floatValue]) {
WXTransitionInfo *info = [WXTransitionInfo new];
info.propertyName = @"transform.translation.y";
info.fromValue = @([oldTransform.translateY floatValue]);
info.toValue = @([wxTransform.translateY floatValue]);
info.perValue = @([wxTransform.translateY floatValue] - [oldTransform.translateY floatValue]);
[_propertyArray addObject:info];
}
_targetComponent.transform = wxTransform;
}
else
{
WXTransitionInfo *info = [WXTransitionInfo new];
info.fromValue = @(_oldFilterStyles[singleProperty] ? [WXConvert CGFloat:_oldFilterStyles[singleProperty]] : 0);
info.toValue = @(_filterStyles[singleProperty] ? [WXConvert CGFloat:_filterStyles[singleProperty]] : 0 );
info.perValue = @([info.toValue doubleValue] - [info.fromValue doubleValue]);
info.propertyName = singleProperty;
[_propertyArray addObject:info];
}
}
}
- (NSArray *)_dealWithColor:(UIColor *)color
{
CGFloat R, G, B, A;
[color getRed:&R green:&G blue:&B alpha:&A];
return @[@(R),@(G),@(B),@(A)];
}
- (NSArray *)_calculatePerColorRGB1:(NSArray *)RGB1 RGB2:(NSArray *)RGB2
{
CGFloat R = [RGB1[0] doubleValue] - [RGB2[0] doubleValue];
CGFloat G = [RGB1[1] doubleValue] - [RGB2[1] doubleValue];
CGFloat B = [RGB1[2] doubleValue] - [RGB2[2] doubleValue];
CGFloat A = [RGB1[3] doubleValue] - [RGB2[3] doubleValue];
return @[@(R),@(G),@(B),@(A)];
}
- (void)_calculatetransitionProcessingStyle
{
if (_targetComponent == nil) {
return;
}
if (_propertyArray.count == 0) {
return;
}
// Bugfix: https://github.com/apache/incubator-weex/issues/2347
NSUInteger frameCount = MAX(_transitionDuration * 60 / 1000, 1);
NSUInteger currentFrame = _transitionCount + 1;
double per = (double)currentFrame / frameCount; //linear
if (currentFrame < frameCount && ![[NSString stringWithFormat:@"%@",_transitionTimingFunction] isEqualToString: kCAMediaTimingFunctionLinear]) {
per = [self solveWithx:per epsilon:SOLVE_EPS(_transitionDuration)];
}
NSString *transformString = [NSString string];
for (WXTransitionInfo *info in _propertyArray) {
if ([info.propertyName isEqualToString:@"backgroundColor"]) {
NSArray *array = @[
@([info.fromValue[0] floatValue] + [info.perValue[0] floatValue] * per),
@([info.fromValue[1] floatValue] + [info.perValue[1] floatValue] * per),
@([info.fromValue[2] floatValue] + [info.perValue[2] floatValue] * per),
@([info.fromValue[3] floatValue] + [info.perValue[3] floatValue] * per)];
UIColor *color = [UIColor colorWithRed:[array[0] floatValue] green:[array[1] floatValue] blue:[array[2] floatValue] alpha:[array[3] floatValue]];
WXPerformBlockOnMainThread(^{
_targetComponent.view.backgroundColor = color;
[_targetComponent.view setNeedsDisplay];
});
NSString *colorString = [WXConvert HexWithColor:color];
[_oldFilterStyles setObject:colorString forKey:info.propertyName];
}
else if ([info.propertyName hasPrefix:@"transform"])
{
double currentValue = [info.fromValue doubleValue] + [info.perValue doubleValue] * per;
NSString *newString = [NSString string];
if ([info.propertyName isEqualToString:@"transform.rotation"]) {
newString = [NSString stringWithFormat:@"rotate(%lfdeg)",currentValue * 180.0 / M_PI];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.rotation.x"]) {
newString = [NSString stringWithFormat:@"rotateX(%lfdeg)",currentValue * 180.0 / M_PI];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.rotation.y"]) {
newString = [NSString stringWithFormat:@"rotateY(%lfdeg)",currentValue * 180.0 / M_PI];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.rotation.z"]) {
newString = [NSString stringWithFormat:@"rotateZ(%lfdeg)",currentValue * 180.0 / M_PI];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.scale.x"]) {
newString = [NSString stringWithFormat:@"scaleX(%lf)",currentValue];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.scale.y"]) {
newString = [NSString stringWithFormat:@"scaleY(%lf)",currentValue];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.translation.x"]) {
newString = [NSString stringWithFormat:@"translateX(%lfpx)",currentValue / (_targetComponent.weexInstance.pixelScaleFactor?_targetComponent.weexInstance.pixelScaleFactor:[WXUtility defaultPixelScaleFactor])];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
if ([info.propertyName isEqualToString:@"transform.translation.y"]) {
newString = [NSString stringWithFormat:@"translateY(%lfpx)",currentValue / (_targetComponent.weexInstance.pixelScaleFactor?_targetComponent.weexInstance.pixelScaleFactor:[WXUtility defaultPixelScaleFactor])];
transformString = [transformString stringByAppendingFormat:@" %@",newString];
}
[_oldFilterStyles setObject:transformString forKey:@"transform"];
}
else
{
double currentValue = [info.fromValue doubleValue] + [info.perValue doubleValue] * per;
[_oldFilterStyles setObject:@(currentValue) forKey:info.propertyName];
}
}
/* _oldFilterStyles could be modified in current thread while _updateViewStyles uses it in main thread.
This may lead to crash in _updateViewStyles because the dictionary items may be retained or
released multiple times by code like styles[@"transform"]. So we copy _oldFilterStyles and use a duplicate.*/
NSDictionary* dupStyles = [NSDictionary dictionaryWithDictionary:_oldFilterStyles];
WXPerformBlockOnMainThread(^{
[_targetComponent _updateViewStyles:dupStyles];
});
[_targetComponent _updateCSSNodeStyles:_oldFilterStyles];
[_targetComponent.weexInstance.componentManager startComponentTasks];
}
#pragma mark CADisplayLink
- (void)_startTransitionDisplayLink
{
WXAssertComponentThread();
if (!_transitionDisplayLink) {
_transitionDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_handleTransitionDisplayLink)];
[_transitionDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
else{
[self _awakeTransitionDisplayLink];
}
}
- (void)_stopTransitionDisplayLink
{
WXAssertComponentThread();
if (_transitionDisplayLink) {
[_transitionDisplayLink invalidate];
_transitionDisplayLink = nil;
}
}
- (BOOL)_isTransitionRunning
{
WXAssertComponentThread();
BOOL yesOrNo = NO;
if (!_transitionDisplayLink.paused && _transitionCount >= 5) {
yesOrNo = YES;
}
return yesOrNo;
}
- (void)_suspendTransitionDisplayLink
{
WXAssertComponentThread();
if(_transitionDisplayLink && !_transitionDisplayLink.paused){
_transitionDisplayLink.paused = YES;
}
}
- (void)_awakeTransitionDisplayLink
{
WXAssertComponentThread();
if (_transitionDisplayLink && _transitionDisplayLink.paused) {
_transitionDisplayLink.paused = NO;
}
}
- (void)_handleTransitionDisplayLink
{
WXAssertComponentThread();
int count = MAX(_transitionDuration * 60 / 1000, 1);
if (_transitionCount >= count) {
[self _suspendTransitionDisplayLink];
[self _resetProcessAnimationParameter];
return;
}
else
{
[self _calculatetransitionProcessingStyle];
}
_transitionCount ++;
}
- (void)_resetProcessAnimationParameter
{
_transitionCount = 0;
_transitionDuration = 0;
_propertyArray = nil;
_oldFilterStyles = nil;
_filterStyles= nil;
}
- (NSMutableDictionary *)_filterStyles
{
return self.filterStyles;
}
- (NSMutableDictionary *)_oldFilterStyles
{
return self.oldFilterStyles;
}
#pragma mark UnitBezierp
- (void)unitBezierp1x:(double)p1x p1y:(double)p1y p2x:(double)p2x p2y:(double)p2y
{
cx = 3.0 * p1x;
bx = 3.0 * (p2x - p1x) - cx;
ax = 1.0 - cx -bx;
cy = 3.0 * p1y;
by = 3.0 * (p2y - p1y) - cy;
ay = 1.0 - cy - by;
}
- (double)sampleCurveX:(double)t
{
return ((ax * t + bx) * t + cx) * t;
}
- (double)sampleCurveY:(double)t
{
return ((ay * t + by) * t + cy) * t;
}
- (double)sampleCurveDerivativeX:(double)t
{
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
- (double)solveCurveX:(double)x epsilon:(double)epsilon
{
double t0;
double t1;
double t2;
double x2;
double d2;
int i;
for (t2 = x, i = 0; i < 8; i++) {
x2 = [self sampleCurveX:t2] - x;
if (fabs (x2) < epsilon)
return t2;
d2 = [self sampleCurveDerivativeX:t2];
if (fabs(d2) < 1e-6)
break;
t2 = t2 - x2 / d2;
}
t0 = 0.0;
t1 = 1.0;
t2 = x;
if (t2 < t0)
return t0;
if (t2 > t1)
return t1;
while (t0 < t1) {
x2 = [self sampleCurveX:t2];
if (fabs(x2 - x) < epsilon)
return t2;
if (x > x2)
t0 = t2;
else
t1 = t2;
t2 = (t1 - t0) * .5 + t0;
}
return t2;
}
- (double)solveWithx:(double)x epsilon:(double)epsilon
{
return [self sampleCurveY:([self solveCurveX:x epsilon:epsilon])];
}
@end