PageRenderTime 84ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/iphone/Classes/TiAnimation.m

https://github.com/oddlyzen/titanium_mobile
Objective C | 564 lines | 449 code | 78 blank | 37 comment | 83 complexity | d02e392a0b23685b4e7f4a9f0c2b07f5 MD5 | raw file
  1. /**
  2. * Appcelerator Titanium Mobile
  3. * Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved.
  4. * Licensed under the terms of the Apache Public License
  5. * Please see the LICENSE included with this distribution for details.
  6. */
  7. #import "TiAnimation.h"
  8. #import "Ti2DMatrix.h"
  9. #import "Ti3DMatrix.h"
  10. #import "TiUtils.h"
  11. #import "TiViewProxy.h"
  12. #import "LayoutConstraint.h"
  13. #import "KrollCallback.h"
  14. #import <QuartzCore/QuartzCore.h>
  15. #ifdef DEBUG
  16. #define ANIMATION_DEBUG 0
  17. #endif
  18. @implementation TiAnimation
  19. @synthesize delegate;
  20. @synthesize zIndex, left, right, top, bottom, width, height;
  21. @synthesize duration, color, backgroundColor, opacity, opaque, view;
  22. @synthesize visible, curve, repeat, autoreverse, delay, transform, transition;
  23. @synthesize animatedView, autoreverseView, autoreverseLayout, transformMatrix, callback;
  24. -(id)initWithDictionary:(NSDictionary*)properties context:(id<TiEvaluator>)context_ callback:(KrollCallback*)callback_
  25. {
  26. if (self = [super _initWithPageContext:context_])
  27. {
  28. #define SET_FLOAT_PROP(p,d) \
  29. {\
  30. id v = d==nil ? nil : [d objectForKey:@#p];\
  31. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  32. self.p = [NSNumber numberWithFloat:[TiUtils floatValue:v]];\
  33. }\
  34. }\
  35. #define SET_INT_PROP(p,d) \
  36. {\
  37. id v = d==nil ? nil : [d objectForKey:@#p];\
  38. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  39. self.p = [NSNumber numberWithInt:[TiUtils intValue:v]];\
  40. }\
  41. }\
  42. #define SET_BOOL_PROP(p,d) \
  43. {\
  44. id v = d==nil ? nil : [d objectForKey:@#p];\
  45. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  46. self.p = [NSNumber numberWithBool:[TiUtils boolValue:v]];\
  47. }\
  48. }\
  49. #define SET_POINT_PROP(p,d) \
  50. {\
  51. id v = d==nil ? nil : [d objectForKey:@#p];\
  52. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  53. self.p = [[[TiPoint alloc] initWithPoint:[TiUtils pointValue:v]] autorelease];\
  54. }\
  55. }\
  56. #define SET_COLOR_PROP(p,d) \
  57. {\
  58. id v = d==nil ? nil : [d objectForKey:@#p];\
  59. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  60. self.p = [TiUtils colorValue:v];\
  61. }\
  62. }\
  63. #define SET_ID_PROP(p,d) \
  64. {\
  65. id v = d==nil ? nil : [d objectForKey:@#p];\
  66. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  67. self.p = v;\
  68. }\
  69. }\
  70. #define SET_PROXY_PROP(p,d) \
  71. {\
  72. id v = d==nil ? nil : [d objectForKey:@#p];\
  73. if (v!=nil && ![v isKindOfClass:[NSNull class]]) {\
  74. self.p = v;\
  75. }\
  76. }\
  77. SET_FLOAT_PROP(zIndex,properties);
  78. SET_FLOAT_PROP(left,properties);
  79. SET_FLOAT_PROP(right,properties);
  80. SET_FLOAT_PROP(top,properties);
  81. SET_FLOAT_PROP(bottom,properties);
  82. SET_FLOAT_PROP(width,properties);
  83. SET_FLOAT_PROP(height,properties);
  84. SET_FLOAT_PROP(duration,properties);
  85. SET_FLOAT_PROP(opacity,properties);
  86. SET_FLOAT_PROP(delay,properties);
  87. SET_INT_PROP(curve,properties);
  88. SET_INT_PROP(repeat,properties);
  89. SET_BOOL_PROP(visible,properties);
  90. SET_BOOL_PROP(opaque,properties);
  91. SET_BOOL_PROP(autoreverse,properties);
  92. SET_POINT_PROP(center,properties);
  93. SET_COLOR_PROP(backgroundColor,properties);
  94. SET_COLOR_PROP(color,properties);
  95. SET_ID_PROP(transform,properties);
  96. SET_INT_PROP(transition,properties);
  97. SET_PROXY_PROP(view,properties);
  98. if (context_!=nil)
  99. {
  100. callback = [[ListenerEntry alloc] initWithListener:callback_ context:context_ proxy:self];
  101. }
  102. }
  103. return self;
  104. }
  105. -(id)initWithDictionary:(NSDictionary*)properties context:(id<TiEvaluator>)context_
  106. {
  107. if (self = [self initWithDictionary:properties context:context_ callback:nil])
  108. {
  109. }
  110. return self;
  111. }
  112. -(void)dealloc
  113. {
  114. RELEASE_TO_NIL(zIndex);
  115. RELEASE_TO_NIL(left);
  116. RELEASE_TO_NIL(right);
  117. RELEASE_TO_NIL(top);
  118. RELEASE_TO_NIL(bottom);
  119. RELEASE_TO_NIL(width);
  120. RELEASE_TO_NIL(height);
  121. RELEASE_TO_NIL(duration);
  122. RELEASE_TO_NIL(center);
  123. RELEASE_TO_NIL(color);
  124. RELEASE_TO_NIL(backgroundColor);
  125. RELEASE_TO_NIL(opacity);
  126. RELEASE_TO_NIL(opaque);
  127. RELEASE_TO_NIL(visible);
  128. RELEASE_TO_NIL(curve);
  129. RELEASE_TO_NIL(repeat);
  130. RELEASE_TO_NIL(autoreverse);
  131. RELEASE_TO_NIL(delay);
  132. RELEASE_TO_NIL(transform);
  133. RELEASE_TO_NIL(transition);
  134. RELEASE_TO_NIL(callback);
  135. RELEASE_TO_NIL(view);
  136. RELEASE_TO_NIL(autoreverseView);
  137. RELEASE_TO_NIL(transformMatrix);
  138. RELEASE_TO_NIL(animatedView);
  139. [super dealloc];
  140. }
  141. +(TiAnimation*)animationFromArg:(id)args context:(id<TiEvaluator>)context create:(BOOL)yn
  142. {
  143. id arg = nil;
  144. BOOL isArray = NO;
  145. if ([args isKindOfClass:[TiAnimation class]])
  146. {
  147. return (TiAnimation*)args;
  148. }
  149. else if ([args isKindOfClass:[NSArray class]])
  150. {
  151. isArray = YES;
  152. arg = [args objectAtIndex:0];
  153. if ([arg isKindOfClass:[TiAnimation class]])
  154. {
  155. return (TiAnimation*)arg;
  156. }
  157. }
  158. else
  159. {
  160. arg = args;
  161. }
  162. if ([arg isKindOfClass:[NSDictionary class]])
  163. {
  164. NSDictionary *properties = arg;
  165. KrollCallback *cb = nil;
  166. if (isArray && [args count] > 1)
  167. {
  168. cb = [args objectAtIndex:1];
  169. ENSURE_TYPE(cb,KrollCallback);
  170. }
  171. // old school animated type properties
  172. if ([TiUtils boolValue:@"animated" properties:properties def:NO])
  173. {
  174. float duration = [TiUtils floatValue:@"animationDuration" properties:properties def:1000];
  175. UIViewAnimationTransition transition = [TiUtils intValue:@"animationStyle" properties:properties def:UIViewAnimationTransitionNone];
  176. TiAnimation *animation = [[[TiAnimation alloc] initWithDictionary:properties context:context callback:cb] autorelease];
  177. animation.duration = [NSNumber numberWithFloat:duration];
  178. animation.transition = [NSNumber numberWithInt:transition];
  179. return animation;
  180. }
  181. return [[[TiAnimation alloc] initWithDictionary:properties context:context callback:cb] autorelease];
  182. }
  183. if (yn)
  184. {
  185. return [[[TiAnimation alloc] _initWithPageContext:context] autorelease];
  186. }
  187. return nil;
  188. }
  189. -(void)setCenter:(id)center_
  190. {
  191. if (center != center_) {
  192. [center release];
  193. center = [[TiPoint alloc] initWithPoint:[TiUtils pointValue:center_]];
  194. }
  195. }
  196. -(TiPoint*)center
  197. {
  198. return center;
  199. }
  200. -(id)description
  201. {
  202. return [NSString stringWithFormat:@"[object TiAnimation<%d>]",[self hash]];
  203. }
  204. -(void)animationStarted:(NSString *)animationID context:(void *)context
  205. {
  206. #if ANIMATION_DEBUG==1
  207. NSLog(@"ANIMATION: STARTING %@, %@",self,(id)context);
  208. #endif
  209. TiAnimation* animation = (TiAnimation*)context;
  210. if (animation.delegate!=nil && [animation.delegate respondsToSelector:@selector(animationDidStart:)])
  211. {
  212. [animation.delegate performSelector:@selector(animationDidStart:) withObject:animation];
  213. }
  214. // fire the event to any listeners on the animation object
  215. if ([animation _hasListeners:@"start"])
  216. {
  217. [animation fireEvent:@"start" withObject:nil];
  218. }
  219. }
  220. -(void)animationCompleted:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
  221. {
  222. #if ANIMATION_DEBUG==1
  223. NSLog(@"ANIMATION: COMPLETED %@, %@",self,(id)context);
  224. #endif
  225. TiAnimation* animation = (TiAnimation*)context;
  226. if (animation.autoreverseView!=nil)
  227. {
  228. #define REVERSE_LAYOUT_CHANGE(a) \
  229. {\
  230. if (!TiDimensionIsUndefined(autoreverseLayout.a)) {\
  231. newLayout->a = animation.autoreverseLayout.a;\
  232. }\
  233. }
  234. if (animation.transformMatrix==nil)
  235. {
  236. animation.transformMatrix = [[Ti2DMatrix alloc] init];
  237. }
  238. [animation.autoreverseView performSelector:@selector(setTransform_:) withObject:animation.transformMatrix];
  239. LayoutConstraint* newLayout = [(TiViewProxy *)[(TiUIView*)animation.autoreverseView proxy] layoutProperties];
  240. REVERSE_LAYOUT_CHANGE(left);
  241. REVERSE_LAYOUT_CHANGE(right);
  242. REVERSE_LAYOUT_CHANGE(width);
  243. REVERSE_LAYOUT_CHANGE(height);
  244. REVERSE_LAYOUT_CHANGE(top);
  245. REVERSE_LAYOUT_CHANGE(bottom);
  246. [(TiViewProxy*)[(TiUIView*)animation.autoreverseView proxy] reposition];
  247. RELEASE_TO_NIL(animation.transformMatrix);
  248. RELEASE_TO_NIL(animation.autoreverseView);
  249. }
  250. if (animation.delegate!=nil && [animation.delegate respondsToSelector:@selector(animationWillComplete:)])
  251. {
  252. [animation.delegate animationWillComplete:self];
  253. }
  254. // fire the event and call the callback
  255. if ([animation _hasListeners:@"complete"])
  256. {
  257. [animation fireEvent:@"complete" withObject:nil];
  258. }
  259. if (animation.callback!=nil && [animation.callback context]!=nil)
  260. {
  261. [animation _fireEventToListener:@"animated" withObject:animation listener:[animation.callback listener] thisObject:nil];
  262. }
  263. // tell our view that we're done
  264. if ([(id)animation.animatedView isKindOfClass:[TiUIView class]])
  265. {
  266. TiUIView *v = (TiUIView*)animation.animatedView;
  267. [(TiViewProxy*)v.proxy animationCompleted:animation];
  268. }
  269. if (animation.delegate!=nil && [animation.delegate respondsToSelector:@selector(animationDidComplete:)])
  270. {
  271. [animation.delegate animationDidComplete:animation];
  272. }
  273. RELEASE_TO_NIL(animation.animatedView);
  274. [animation release];
  275. }
  276. -(BOOL)isTransitionAnimation
  277. {
  278. if (transition!=nil)
  279. {
  280. UIViewAnimationTransition t = [transition intValue];
  281. if (t!=0 && t!=UIViewAnimationTransitionNone)
  282. {
  283. return YES;
  284. }
  285. }
  286. return NO;
  287. }
  288. -(void)animate:(id)args
  289. {
  290. ENSURE_UI_THREAD(animate,args);
  291. #if ANIMATION_DEBUG==1
  292. NSLog(@"ANIMATION: starting %@, %@, retain: %d",self,args,[self retainCount]);
  293. #endif
  294. UIView *theview = nil;
  295. if ([args isKindOfClass:[NSArray class]])
  296. {
  297. //
  298. // this is something like:
  299. //
  300. // animation.animate(view)
  301. //
  302. // vs.
  303. //
  304. // view.animate(animation)
  305. //
  306. // which is totally fine, just hand it to the view and let him callback
  307. //
  308. theview = [args objectAtIndex:0];
  309. ENSURE_TYPE(theview,TiViewProxy);
  310. [(TiViewProxy*)theview animate:[NSArray arrayWithObject:self]];
  311. return;
  312. }
  313. else if ([args isKindOfClass:[TiViewProxy class]])
  314. {
  315. // called by the view to cause himself to be animated
  316. theview = args;
  317. }
  318. else if ([args isKindOfClass:[UIView class]])
  319. {
  320. // this is OK too
  321. theview = args;
  322. }
  323. BOOL transitionAnimation = [self isTransitionAnimation];
  324. TiUIView *view_ = transitionAnimation && view!=nil ? [view view] : [theview isKindOfClass:[TiViewProxy class]] ? [(TiViewProxy*)theview view] : (TiUIView *)theview;
  325. TiUIView *transitionView = transitionAnimation ? [theview isKindOfClass:[TiViewProxy class]] ? (TiUIView*)[(TiViewProxy*)theview view] : (TiUIView*)theview : nil;
  326. if (transitionView!=nil)
  327. {
  328. // we need to first make sure our new view that we're transitioning to is sized but we don't want
  329. // to add to the view hiearchry inside the animation block or you'll get the sizings as part of the
  330. // animation.. which we don't want
  331. TiViewProxy * ourProxy = (TiViewProxy*)[view_ proxy];
  332. LayoutConstraint *contraints = [ourProxy layoutProperties];
  333. ApplyConstraintToViewWithinViewWithBounds(contraints, view_, transitionView, transitionView.bounds, NO);
  334. [ourProxy layoutChildren:NO];
  335. }
  336. else
  337. {
  338. CALayer * modelLayer = [view_ layer];
  339. CALayer * transitionLayer = [modelLayer presentationLayer];
  340. NSArray * animationKeys = [transitionLayer animationKeys];
  341. for (NSString * thisKey in animationKeys)
  342. {
  343. [modelLayer setValue:[transitionLayer valueForKey:thisKey] forKey:thisKey];
  344. }
  345. }
  346. // hold on to our animation during the animation and until it stops
  347. [self retain];
  348. [theview retain];
  349. animatedView = theview;
  350. // Have to pass self as context because if there are two or more animations going on, the wrong
  351. // autoreverse cleanup/view release may be applied to the animation.
  352. [UIView beginAnimations:[NSString stringWithFormat:@"%X",(void *)theview] context:(void*)self];
  353. [UIView setAnimationDelegate:self];
  354. [UIView setAnimationWillStartSelector:@selector(animationStarted:context:)];
  355. [UIView setAnimationDidStopSelector:@selector(animationCompleted:finished:context:)];
  356. if (duration!=nil)
  357. {
  358. [UIView setAnimationDuration:[duration doubleValue]/1000];
  359. }
  360. else
  361. {
  362. // set a reasonable small default if the developer doesn't specify one such that
  363. // you can do animations quickly such as during drag and drop
  364. [UIView setAnimationDuration: (transitionAnimation ? 1 : 0.2)];
  365. }
  366. BOOL autoreverses = NO;
  367. if (curve!=nil)
  368. {
  369. [UIView setAnimationCurve:[curve intValue]];
  370. }
  371. if (repeat!=nil)
  372. {
  373. [UIView setAnimationRepeatCount:[repeat intValue]];
  374. }
  375. if (autoreverse!=nil)
  376. {
  377. autoreverses = [autoreverse boolValue];
  378. if (autoreverseView==nil)
  379. {
  380. autoreverseView = [view_ retain];
  381. }
  382. [UIView setAnimationRepeatAutoreverses:autoreverses];
  383. }
  384. if (delay!=nil)
  385. {
  386. [UIView setAnimationDelay:[delay doubleValue]/1000];
  387. }
  388. // NOTE: this *must* be called after the animation is setup, otherwise,
  389. // the attributes above won't be set in anything you do in the start
  390. if (delegate!=nil && [delegate respondsToSelector:@selector(animationWillStart:)])
  391. {
  392. [delegate animationWillStart:self];
  393. }
  394. if (transform!=nil)
  395. {
  396. if (autoreverses)
  397. {
  398. transformMatrix = [[(TiUIView*)view_ transformMatrix] retain];
  399. }
  400. [(TiUIView *)view_ setTransform_:transform];
  401. }
  402. if ([view_ isKindOfClass:[TiUIView class]])
  403. {
  404. TiUIView *uiview = (TiUIView*)view_;
  405. LayoutConstraint *layout = [(TiViewProxy *)[uiview proxy] layoutProperties];
  406. BOOL doReposition = NO;
  407. #define CHECK_LAYOUT_CHANGE(a) \
  408. if (a!=nil && layout!=NULL) \
  409. {\
  410. autoreverseLayout.a = layout->a; \
  411. layout->a = TiDimensionFromObject(a); \
  412. doReposition = YES;\
  413. }\
  414. else \
  415. {\
  416. autoreverseLayout.a = TiDimensionUndefined; \
  417. }
  418. CHECK_LAYOUT_CHANGE(left);
  419. CHECK_LAYOUT_CHANGE(right);
  420. CHECK_LAYOUT_CHANGE(width);
  421. CHECK_LAYOUT_CHANGE(height);
  422. CHECK_LAYOUT_CHANGE(top);
  423. CHECK_LAYOUT_CHANGE(bottom);
  424. if (zIndex!=nil)
  425. {
  426. [uiview performSelector:@selector(setZIndex_:) withObject:zIndex];
  427. }
  428. if (doReposition)
  429. {
  430. [(TiViewProxy *)[uiview proxy] reposition];
  431. }
  432. }
  433. if (center!=nil)
  434. {
  435. view_.center = [center point];
  436. }
  437. if (backgroundColor!=nil)
  438. {
  439. TiColor *color_ = [TiUtils colorValue:backgroundColor];
  440. [view_ setBackgroundColor:[color_ _color]];
  441. }
  442. if (color!=nil && [view_ respondsToSelector:@selector(setColor_:)])
  443. {
  444. [view_ performSelector:@selector(setColor_:) withObject:color];
  445. }
  446. if (opacity!=nil)
  447. {
  448. view_.alpha = [opacity floatValue];
  449. }
  450. if (opaque!=nil)
  451. {
  452. view_.opaque = [opaque boolValue];
  453. }
  454. if (visible!=nil)
  455. {
  456. view_.hidden = ![visible boolValue];
  457. }
  458. // check to see if this is a transition
  459. if (transitionAnimation)
  460. {
  461. BOOL perform = YES;
  462. // allow a delegate to control transitioning
  463. if (delegate!=nil && [delegate respondsToSelector:@selector(animationShouldTransition:)])
  464. {
  465. perform = [delegate animationShouldTransition:self];
  466. }
  467. if (perform)
  468. {
  469. [UIView setAnimationTransition:[transition intValue]
  470. forView:transitionView
  471. cache:NO]; //TODO: might need to make cache configurable
  472. // transitions are between 2 views so we need to remove existing views (normally only one)
  473. // and then we need to add our new view
  474. for (UIView *subview in [transitionView subviews])
  475. {
  476. [subview removeFromSuperview];
  477. }
  478. [transitionView addSubview:view_];
  479. }
  480. }
  481. [UIView commitAnimations];
  482. #if ANIMATION_DEBUG==1
  483. NSLog(@"ANIMATION: committed %@, %@",self,args);
  484. #endif
  485. }
  486. @end