Created
July 2, 2012 09:32
-
-
Save yomybaby/3032286 to your computer and use it in GitHub Desktop.
titanium disable clipping option for iOS view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Appcelerator Titanium Mobile | |
* Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. | |
* Licensed under the terms of the Apache Public License | |
* Please see the LICENSE included with this distribution for details. | |
*/ | |
#import "TiProxy.h" | |
#import "TiUIView.h" | |
#import "TiRect.h" | |
#import <pthread.h> | |
/** | |
Protocol for views that can receive keyboard focus. | |
*/ | |
@protocol TiKeyboardFocusableView | |
#pragma mark Public Titanium APIs. | |
/** | |
Tells the view to focus. | |
@param args Unused. | |
*/ | |
- (void)focus:(id)args; | |
/** | |
Tells the view to stop generating focus/blur events. This should not be | |
JS-accessable, and is meant to handle tableview and layout issues. | |
*/ | |
@property(nonatomic,readwrite,assign) BOOL suppressFocusEvents; | |
/** | |
Tells the view to blur. | |
@param args Unused. | |
*/ | |
- (void)blur:(id)args; | |
#pragma mark Private internal APIs. | |
/** | |
Returns keyboard accessory view. | |
*/ | |
@property(nonatomic,readonly) UIView * keyboardAccessoryView; | |
/** | |
Returns keyboard accessory height. | |
*/ | |
@property(nonatomic,readonly) CGFloat keyboardAccessoryHeight; | |
@end | |
/* | |
This Protocol will be implemented by objects that want to | |
monitor views not in the normal view heirarchy. | |
*/ | |
@protocol TiProxyObserver | |
@optional | |
-(void)proxyDidRelayout:(id)sender; | |
@end | |
#pragma mark dirtyflags used by TiViewProxy | |
#define NEEDS_LAYOUT_CHILDREN 1 | |
//Set this flag to true to disable instant updates | |
static const BOOL ENFORCE_BATCH_UPDATE = NO; | |
enum | |
{ | |
TiRefreshViewPosition = 2, | |
TiRefreshViewChildrenPosition, | |
TiRefreshViewZIndex, | |
TiRefreshViewSize, | |
TiRefreshViewEnqueued, | |
}; | |
@class TiAction, TiBlob; | |
//For TableRows, we need to have minimumParentHeightForWidth: | |
/** | |
The class represents a proxy that is attached to a view. | |
The class is not intended to be overriden. | |
*/ | |
@interface TiViewProxy : TiProxy<LayoutAutosizing> | |
{ | |
@protected | |
//TODO: Actually have a rhyme and reason on keeping things @protected vs @private. | |
//For now, for sake of proper value grouping, we're all under one roof. | |
#pragma mark Layout properties | |
LayoutConstraint layoutProperties; | |
int vzIndex; | |
BOOL hidden; //This is the boolean version of ![TiUtils boolValue:visible def:yes] | |
//And has nothing to do with whether or not it's onscreen or | |
#pragma mark Parent/Children relationships | |
TiViewProxy *parent; | |
pthread_rwlock_t childrenLock; | |
NSMutableArray *children; | |
NSMutableArray *pendingAdds; | |
#pragma mark Visual components | |
TiUIView *view; | |
UIBarButtonItem * barButtonItem; | |
#pragma mark Layout caches that can be recomputed | |
CGFloat verticalLayoutBoundary; | |
CGFloat horizontalLayoutBoundary; | |
CGFloat horizontalLayoutRowHeight; //Note, this has nothing to do with table views. | |
int lastChildArranged; | |
CGRect sandboxBounds; | |
CGPoint positionCache; //Recomputed and stored when position changes. | |
CGRect sizeCache; //Recomputed and stored when size changes. | |
UIViewAutoresizing autoresizeCache; //Changed by repositioning or resizing. | |
BOOL parentVisible; | |
//In most cases, this is the same as [parent parentVisible] && ![parent hidden] | |
//However, in the case of windows attached to the root view, the parent is ALWAYS visible. | |
//That is, will be true if and only if all parents are visible or are the root controller. | |
//Use parentWillShow and parentWillHide to set this. | |
#pragma mark Housecleaning that is set and used | |
NSRecursiveLock *destroyLock; | |
BOOL windowOpened; | |
BOOL windowOpening; | |
int dirtyflags; //For atomic actions, best to be explicit about the 32 bitness. | |
BOOL viewInitialized; | |
BOOL repositioning; | |
BOOL isUsingBarButtonItem; | |
//This flag is set to true on startLayout() call and false on finishLayout() call | |
BOOL updateStarted; | |
BOOL allowLayoutUpdate; | |
NSMutableDictionary *layoutPropDictionary; | |
id observer; | |
#pragma customizing for clipping | |
BOOL clippingDisable; | |
} | |
#pragma mark public API | |
@property(nonatomic,readonly) TiRect * size; | |
@property(nonatomic,readonly) TiRect * rect; | |
/* | |
Provides access to z-index value. | |
*/ | |
@property(nonatomic,readwrite,assign) int vzIndex; | |
/** | |
Provides access to visibility of parent view proxy. | |
*/ | |
@property(nonatomic,readwrite,assign) BOOL parentVisible; // For tableview magic ONLY | |
/** | |
Returns children view proxies for the proxy. | |
*/ | |
@property(nonatomic,readonly) NSArray *children; | |
-(void)startLayout:(id)arg; | |
-(void)finishLayout:(id)arg; | |
-(void)updateLayout:(id)arg; | |
-(void)setTempProperty:(id)propVal forKey:(id)propName; | |
-(void)processTempProperties:(NSDictionary*)arg; | |
-(void)setProxyObserver:(id)arg; | |
/** | |
Tells the view proxy to add a child proxy. | |
@param arg A single proxy to add or NSArray of proxies. | |
*/ | |
-(void)add:(id)arg; | |
/** | |
Tells the view proxy to remove a child proxy. | |
@param arg A single proxy to remove. | |
*/ | |
-(void)remove:(id)arg; | |
/** | |
Tells the view proxy to set visibility on a child proxy to _YES_. | |
@param arg A single proxy to show. | |
*/ | |
-(void)show:(id)arg; | |
/** | |
Tells the view proxy to set visibility on a child proxy to _NO_. | |
@param arg A single proxy to hide. | |
*/ | |
-(void)hide:(id)arg; | |
/** | |
Tells the view proxy to run animation on its view. | |
@param arg An animation object. | |
*/ | |
-(void)animate:(id)arg; | |
-(void)setTop:(id)value; | |
-(void)setBottom:(id)value; | |
-(void)setLeft:(id)value; | |
-(void)setRight:(id)value; | |
-(void)setWidth:(id)value; | |
-(void)setHeight:(id)value; | |
-(void)setZIndex:(id)value; | |
-(id)zIndex; | |
// See the code for setValue:forUndefinedKey: for why we can't have this | |
//-(void)setLayout:(id)value; | |
-(void)setMinWidth:(id)value; | |
-(void)setMinHeight:(id)value; | |
-(void)setCenter:(id)value; | |
-(NSMutableDictionary*)center; | |
-(id)animatedCenter; | |
-(void)setBackgroundGradient:(id)arg; | |
-(TiBlob*)toImage:(id)args; | |
#pragma mark nonpublic accessors not related to Housecleaning | |
/** | |
Provides access to parent proxy of the view proxy. | |
@see add: | |
@see remove: | |
@see children | |
*/ | |
@property(nonatomic, assign) TiViewProxy *parent; | |
//TODO: make this a proper readwrite property declaration. | |
/** | |
Provides access to layout properties of the underlying view. | |
*/ | |
@property(nonatomic,readonly,assign) LayoutConstraint * layoutProperties; | |
/** | |
Provides access to sandbox bounds of the underlying view. | |
*/ | |
@property(nonatomic,readwrite,assign) CGRect sandboxBounds; | |
//This is unaffected by parentVisible. So if something is truely visible, it'd be [self visible] && parentVisible. | |
-(void)setHidden:(BOOL)newHidden withArgs:(id)args; | |
@property(nonatomic,retain) UIBarButtonItem * barButtonItem; | |
-(TiUIView *)barButtonViewForSize:(CGSize)bounds; | |
//NOTE: DO NOT SET VIEW UNLESS IN A TABLE VIEW, AND EVEN THEN. | |
@property(nonatomic,readwrite,retain)TiUIView * view; | |
/** | |
Returns language conversion table. | |
Subclasses may override. | |
@return The dictionary | |
*/ | |
-(NSMutableDictionary*)langConversionTable; | |
#pragma mark Methods subclasses should override for behavior changes | |
/** | |
Whether or not the view proxy can have non Ti-Views which have to be pushed to the bottom when adding children. | |
**This method is only meant for legacy classes. New classes must implement the proper wrapperView code** | |
Subclasses may override. | |
@return _NO_ if the view proxy can have non Ti-Views in its view heirarchy | |
*/ | |
-(BOOL)optimizeSubviewInsertion; | |
/** | |
Whether or not the view proxy needs to suppress relayout. | |
Subclasses may override. | |
@return _YES_ if relayout should be suppressed, _NO_ otherwise. | |
*/ | |
-(BOOL)suppressesRelayout; | |
/** | |
Whether or not the view proxy supports navigation bar positioning. | |
Subclasses may override. | |
@return _YES_ if navigation bar positioning is supported, _NO_ otherwise. | |
*/ | |
-(BOOL)supportsNavBarPositioning; | |
/** | |
Whether or not the view proxy can have a UIController object in its parent view. | |
Subclasses may override. | |
@return _YES_ if the view proxy can have a UIController object in its parent view | |
*/ | |
-(BOOL)canHaveControllerParent; | |
/** | |
Whether or not the view proxy should detach its view on unload. | |
Subclasses may override. | |
@return _YES_ if the view should be detached, _NO_ otherwise. | |
*/ | |
-(BOOL)shouldDetachViewOnUnload; | |
/** | |
Returns parent view for child proxy. | |
The method is used in cases when proxies heirarchy is different from views hierarchy. | |
Subclasses may override. | |
@param child The child view proxy for which return the parent view. | |
@return The parent view | |
*/ | |
-(UIView *)parentViewForChild:(TiViewProxy *)child; | |
#pragma mark Event trigger methods | |
/** | |
Tells the view proxy that the attached window will open. | |
@see windowDidOpen | |
*/ | |
-(void)windowWillOpen; | |
/** | |
Tells the view proxy that the attached window did open. | |
@see windowWillOpen | |
*/ | |
-(void)windowDidOpen; | |
/** | |
Tells the view proxy that the attached window will close. | |
@see windowDidClose | |
*/ | |
-(void)windowWillClose; | |
/** | |
Tells the view proxy that the attached window did close. | |
@see windowWillClose | |
*/ | |
-(void)windowDidClose; | |
/** | |
Tells the view proxy that its properties are about to change. | |
@see didFirePropertyChanges | |
*/ | |
-(void)willFirePropertyChanges; | |
/** | |
Tells the view proxy that its properties are changed. | |
@see willFirePropertyChanges | |
*/ | |
-(void)didFirePropertyChanges; | |
/** | |
Tells the view proxy that a view will be attached to it. | |
@see viewDidAttach | |
*/ | |
-(void)viewWillAttach; // Need this for video player & possibly other classes which override newView | |
/** | |
Tells the view proxy that a view was attached to it. | |
@see viewWillAttach | |
*/ | |
-(void)viewDidAttach; | |
/** | |
Tells the view proxy that a view will be detached from it. | |
@see viewDidDetach | |
*/ | |
-(void)viewWillDetach; | |
/** | |
Tells the view proxy that a view was detached from it. | |
@see viewWillDetach | |
*/ | |
-(void)viewDidDetach; | |
/** | |
Tells the view proxy that parent will appear | |
@see UIViewController viewWillAppear. | |
*/ | |
-(void)parentWillAppear:(id)args; | |
/** | |
Tells the view proxy that parent did appear | |
@see UIViewController viewDidAppear. | |
*/ | |
-(void)parentDidAppear:(id)args; | |
/** | |
Tells the view proxy that parent will disappear | |
@see UIViewController viewWillDisappear. | |
*/ | |
-(void)parentWillDisappear:(id)args; | |
/** | |
Tells the view proxy that parent did appear | |
@see UIViewController viewDidDisappear. | |
*/ | |
-(void)parentDidDisappear:(id)args; | |
#pragma mark Housecleaning state accessors | |
//TODO: Sounds like the redundancy department of redundancy was here. | |
/** | |
Whether or not a view is attached to the view proxy. | |
@return _YES_ if the view proxy has a view attached to it, _NO_ otherwise. | |
*/ | |
-(BOOL)viewAttached; | |
/** | |
Whether or not the view proxy has been initialized. | |
@return _YES_ if the view proxy has been initialized, _NO_ otherwise. | |
*/ | |
-(BOOL)viewInitialized; | |
/** | |
Whether or not the view proxy has been completely set up. | |
@return _YES_ if the view proxy has been initialized and its view has a superview and non-empty bounds, _NO_ otherwise. | |
*/ | |
-(BOOL)viewReady; | |
/** | |
Whether or not a window attached to the view proxy has been opened. | |
@return _YES_ if the view proxy's window has been opened, _NO_ otherwise. | |
*/ | |
-(BOOL)windowHasOpened; | |
/** | |
Whether or not a window attached to the view proxy is currently being opened. | |
@return _YES_ if the view proxy's window is being opened, _NO_ otherwise. | |
*/ | |
-(BOOL)windowIsOpening; | |
/** | |
Whether or not the view proxy is using a bar button item. | |
@return _YES_ if a bar button item is used, _NO_ otherwise. | |
*/ | |
-(BOOL)isUsingBarButtonItem; | |
-(CGRect)appFrame; //TODO: Why is this here? It doesn't have anything to do with a specific instance. | |
#pragma mark Building up and tearing down | |
-(void)firePropertyChanges; | |
/** | |
Returns a ne view corresponding to the view proxy. | |
@return The created view. | |
*/ | |
-(TiUIView*)newView; | |
/** | |
Tells the view proxy to detach its view. | |
*/ | |
-(void)detachView; | |
-(void)destroy; | |
/** | |
Tells the view proxy to remove its bar button item. | |
*/ | |
-(void)removeBarButtonView; | |
#pragma mark Callbacks | |
/** | |
Tells the view proxy that its view animation did complete. | |
@param animation The completed animation | |
*/ | |
-(void)animationCompleted:(TiAnimation*)animation; | |
/** | |
Tells the view attached to the view proxy to perform a selector with given arguments. | |
@param selector The selector to perform. | |
@param object The argument for the method performed. | |
@param create The flag to create the view if the one is not attached. | |
@param wait The flag to wait till the operation completes. | |
*/ | |
-(void)makeViewPerformSelector:(SEL)selector withObject:(id)object createIfNeeded:(BOOL)create waitUntilDone:(BOOL)wait; | |
#pragma mark Layout events, internal and external | |
/** | |
Tells the view proxy that the attached view size will change. | |
*/ | |
-(void)willChangeSize; | |
/** | |
Tells the view proxy that the attached view position will change. | |
*/ | |
-(void)willChangePosition; | |
/** | |
Tells the view proxy that the attached view z-index will change. | |
*/ | |
-(void)willChangeZIndex; | |
/** | |
Tells the view proxy that the attached view layout will change. | |
*/ | |
-(void)willChangeLayout; | |
/** | |
Tells the view proxy that the attached view will show. | |
*/ | |
-(void)willShow; | |
/** | |
Tells the view proxy that the attached view will hide. | |
*/ | |
-(void)willHide; | |
/** | |
Tells the view proxy that the attached view contents will change. | |
*/ | |
-(void)contentsWillChange; | |
/** | |
Tells the view proxy that the attached view's parent size will change. | |
*/ | |
-(void)parentSizeWillChange; | |
/** | |
Tells the view proxy that the attached view's parent will change position and size. | |
*/ | |
-(void)parentWillRelay; | |
/** | |
Tells the view proxy that the attached view's parent will show. | |
*/ | |
-(void)parentWillShow; | |
/** | |
Tells the view proxy that the attached view's parent will hide. | |
*/ | |
-(void)parentWillHide; | |
#pragma mark Layout actions | |
-(void)refreshView:(TiUIView *)transferView; | |
/** | |
Tells the view proxy to force size refresh of the attached view. | |
*/ | |
-(void)refreshSize; | |
/** | |
Tells the view proxy to force position refresh of the attached view. | |
*/ | |
-(void)refreshPosition; | |
/** | |
Puts the view in the layout queue for rendering. | |
*/ | |
-(void)willEnqueue; | |
//Unlike the other layout actions, this one is done by the parent of the one called by refreshView. | |
//This is the effect of refreshing the Z index via careful view placement. | |
-(void)insertSubview:(UIView *)childView forProxy:(TiViewProxy *)childProxy; | |
#pragma mark Layout commands that need refactoring out | |
-(void)determineSandboxBounds; | |
/** | |
Tells the view to layout its children. | |
@param optimize Internal use only. Always specify _NO_. | |
*/ | |
-(void)layoutChildren:(BOOL)optimize; | |
/** | |
Tells the view to layout its children only if there were any layout changes. | |
*/ | |
-(void)layoutChildrenIfNeeded; | |
-(void)layoutChild:(TiViewProxy*)child optimize:(BOOL)optimize withMeasuredBounds:(CGRect)bounds; | |
-(NSArray*)measureChildren:(NSArray*)childArray; | |
-(CGRect)computeChildSandbox:(TiViewProxy*)child withBounds:(CGRect)bounds; | |
/** | |
Tells the view to adjust its size and position according to the current layout constraints. | |
*/ | |
-(void)relayout; | |
-(void)reposition; //Todo: Replace | |
-(BOOL)willBeRelaying; //Todo: Replace | |
/** | |
Tells the view that its child view size will change. | |
@param child The child view | |
*/ | |
-(void)childWillResize:(TiViewProxy *)child; //Todo: Replace | |
@end | |
#define USE_VIEW_FOR_METHOD(resultType,methodname,inputType) \ | |
-(resultType) methodname: (inputType)value \ | |
{ \ | |
return [[self view] methodname:value]; \ | |
} | |
#define USE_VIEW_FOR_VERIFY_WIDTH USE_VIEW_FOR_METHOD(CGFloat,verifyWidth,CGFloat) | |
#define USE_VIEW_FOR_VERIFY_HEIGHT USE_VIEW_FOR_METHOD(CGFloat,verifyHeight,CGFloat) | |
#define USE_VIEW_FOR_CONTENT_WIDTH USE_VIEW_FOR_METHOD(CGFloat,contentWidthForWidth,CGFloat) | |
#define USE_VIEW_FOR_CONTENT_HEIGHT USE_VIEW_FOR_METHOD(CGFloat,contentHeightForWidth,CGFloat) | |
#define DECLARE_VIEW_CLASS_FOR_NEWVIEW(viewClass) \ | |
-(TiUIView*)newView \ | |
{ \ | |
return [[viewClass alloc] init]; \ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Appcelerator Titanium Mobile | |
* Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. | |
* Licensed under the terms of the Apache Public License | |
* Please see the LICENSE included with this distribution for details. | |
*/ | |
#import "TiViewProxy.h" | |
#import "LayoutConstraint.h" | |
#import "TiApp.h" | |
#import "TiBlob.h" | |
#import "TiLayoutQueue.h" | |
#import "TiAction.h" | |
#import "TiStylesheet.h" | |
#import "TiLocale.h" | |
#import "TiUIView.h" | |
#import <QuartzCore/QuartzCore.h> | |
#import <libkern/OSAtomic.h> | |
#import <pthread.h> | |
#define IGNORE_IF_NOT_OPENED if (!windowOpened||[self viewAttached]==NO) return; | |
@implementation TiViewProxy | |
#pragma mark public API | |
@synthesize vzIndex, parentVisible; | |
-(void)setVzIndex:(int)newZindex | |
{ | |
if(newZindex == vzIndex) | |
{ | |
return; | |
} | |
vzIndex = newZindex; | |
[self replaceValue:NUMINT(vzIndex) forKey:@"vzIndex" notification:NO]; | |
[self willChangeZIndex]; | |
} | |
@synthesize children; | |
-(NSArray*)children | |
{ | |
NSArray* copy = nil; | |
pthread_rwlock_rdlock(&childrenLock); | |
if (windowOpened==NO && children==nil && pendingAdds!=nil) | |
{ | |
copy = [pendingAdds mutableCopy]; | |
} | |
else { | |
copy = [children mutableCopy]; | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
return ((copy != nil) ? [copy autorelease] : [NSMutableArray array]); | |
} | |
-(void)setVisible:(NSNumber *)newVisible withObject:(id)args | |
{ | |
[self setHidden:![TiUtils boolValue:newVisible def:YES] withArgs:args]; | |
[self replaceValue:newVisible forKey:@"visible" notification:YES]; | |
} | |
-(void)setTempProperty:(id)propVal forKey:(id)propName { | |
if (layoutPropDictionary == nil) { | |
layoutPropDictionary = [[NSMutableDictionary alloc] init]; | |
} | |
if (propVal != nil && propName != nil) { | |
[layoutPropDictionary setObject:propVal forKey:propName]; | |
} | |
} | |
-(void)setProxyObserver:(id)arg | |
{ | |
observer = arg; | |
} | |
-(void)processTempProperties:(NSDictionary*)arg | |
{ | |
//arg will be non nil when called from updateLayout | |
if (arg != nil) { | |
NSEnumerator *enumerator = [arg keyEnumerator]; | |
id key; | |
while ((key = [enumerator nextObject])) { | |
[self setTempProperty:[arg objectForKey:key] forKey:key]; | |
} | |
} | |
if (layoutPropDictionary != nil) { | |
[self setValuesForKeysWithDictionary:layoutPropDictionary]; | |
RELEASE_TO_NIL(layoutPropDictionary); | |
} | |
} | |
-(void)startLayout:(id)arg | |
{ | |
updateStarted = YES; | |
allowLayoutUpdate = NO; | |
} | |
-(void)finishLayout:(id)arg | |
{ | |
updateStarted = NO; | |
allowLayoutUpdate = YES; | |
[self processTempProperties:nil]; | |
allowLayoutUpdate = NO; | |
} | |
-(void)updateLayout:(id)arg | |
{ | |
id val = nil; | |
if ([arg isKindOfClass:[NSArray class]]) { | |
val = [arg objectAtIndex:0]; | |
} | |
else | |
{ | |
val = arg; | |
} | |
updateStarted = NO; | |
allowLayoutUpdate = YES; | |
ENSURE_TYPE_OR_NIL(val, NSDictionary); | |
[self processTempProperties:val]; | |
allowLayoutUpdate = NO; | |
} | |
-(void)add:(id)arg | |
{ | |
// allow either an array of arrays or an array of single proxy | |
if ([arg isKindOfClass:[NSArray class]]) | |
{ | |
for (id a in arg) | |
{ | |
[self add:a]; | |
} | |
return; | |
} | |
if ([NSThread isMainThread]) | |
{ | |
pthread_rwlock_wrlock(&childrenLock); | |
if (children==nil) | |
{ | |
children = [[NSMutableArray alloc] initWithObjects:arg,nil]; | |
} | |
else | |
{ | |
[children addObject:arg]; | |
} | |
//Turn on clipping because I have children | |
//yomybaby added | |
if(clippingDisable != YES){ | |
[self view].clipsToBounds = YES; | |
} | |
//origin | |
//[self view].clipsToBounds = YES; | |
//end | |
pthread_rwlock_unlock(&childrenLock); | |
[arg setParent:self]; | |
[self contentsWillChange]; | |
if(parentVisible && !hidden) | |
{ | |
[arg parentWillShow]; | |
} | |
// only call layout if the view is attached | |
// Maybe need to call layout children instead for non absolute layout | |
[self layoutChild:arg optimize:NO withMeasuredBounds:[[self size] rect]]; | |
} | |
else | |
{ | |
[self rememberProxy:arg]; | |
if (windowOpened) | |
{ | |
TiThreadPerformOnMainThread(^{[self add:arg];}, NO); | |
return; | |
} | |
pthread_rwlock_wrlock(&childrenLock); | |
if (pendingAdds==nil) | |
{ | |
pendingAdds = [[NSMutableArray arrayWithObject:arg] retain]; | |
} | |
else | |
{ | |
[pendingAdds addObject:arg]; | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
[arg setParent:self]; | |
} | |
} | |
-(void)remove:(id)arg | |
{ | |
ENSURE_SINGLE_ARG(arg,TiViewProxy); | |
ENSURE_UI_THREAD_1_ARG(arg); | |
pthread_rwlock_wrlock(&childrenLock); | |
if ([children containsObject:arg]) | |
{ | |
[children removeObject:arg]; | |
} | |
else if ([pendingAdds containsObject:arg]) | |
{ | |
[pendingAdds removeObject:arg]; | |
} | |
else | |
{ | |
pthread_rwlock_unlock(&childrenLock); | |
DebugLog(@"[WARN] Called remove for %@ on %@, but %@ isn't a child or has already been removed.",arg,self,arg); | |
return; | |
} | |
[self contentsWillChange]; | |
if(parentVisible && !hidden) | |
{ | |
[arg parentWillHide]; | |
} | |
if ([children count]==0) | |
{ | |
RELEASE_TO_NIL(children); | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
[arg setParent:nil]; | |
if (view!=nil) | |
{ | |
TiUIView *childView = [(TiViewProxy *)arg view]; | |
BOOL layoutNeedsRearranging = !TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle); | |
if ([NSThread isMainThread]) | |
{ | |
[childView removeFromSuperview]; | |
if (layoutNeedsRearranging) | |
{ | |
[self layoutChildren:NO]; | |
} | |
} | |
else | |
{ | |
TiThreadPerformOnMainThread(^{ | |
[childView removeFromSuperview]; | |
if (layoutNeedsRearranging) | |
{ | |
[self layoutChildren:NO]; | |
} | |
}, NO); | |
} | |
} | |
//Yes, we're being really lazy about letting this go. This is intentional. | |
[self forgetProxy:arg]; | |
} | |
-(void)show:(id)arg | |
{ | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
[self setHidden:NO withArgs:arg]; | |
[self replaceValue:NUMBOOL(YES) forKey:@"visible" notification:YES]; | |
}); | |
} | |
-(void)hide:(id)arg | |
{ | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
[self setHidden:YES withArgs:arg]; | |
[self replaceValue:NUMBOOL(NO) forKey:@"visible" notification:YES]; | |
}); | |
} | |
-(void)animate:(id)arg | |
{ | |
TiAnimation * newAnimation = [TiAnimation animationFromArg:arg context:[self executionContext] create:NO]; | |
[self rememberProxy:newAnimation]; | |
TiThreadPerformOnMainThread(^{ | |
[parent contentsWillChange]; | |
if ([view superview]==nil) | |
{ | |
VerboseLog(@"Entering animation without a superview Parent is %@, props are %@",parent,dynprops); | |
[parent childWillResize:self]; | |
} | |
[self windowWillOpen]; // we need to manually attach the window if you're animating | |
[parent layoutChildrenIfNeeded]; | |
[[self view] animate:newAnimation]; | |
}, NO); | |
} | |
-(void)setAnimation:(id)arg | |
{ //We don't actually store the animation this way. | |
//Because the setter doesn't have the argument array, we will be passing a nonarray to animate: | |
//In this RARE case, this is okay, because TiAnimation animationFromArg handles with or without array. | |
[self animate:arg]; | |
} | |
#define CHECK_LAYOUT_UPDATE(layoutName,value) \ | |
if (ENFORCE_BATCH_UPDATE) { \ | |
if (updateStarted) { \ | |
[self setTempProperty:value forKey:@#layoutName]; \ | |
return; \ | |
} \ | |
else if(!allowLayoutUpdate){ \ | |
return; \ | |
} \ | |
} | |
#define LAYOUTPROPERTIES_SETTER_IGNORES_AUTO(methodName,layoutName,converter,postaction) \ | |
-(void)methodName:(id)value \ | |
{ \ | |
CHECK_LAYOUT_UPDATE(layoutName,value) \ | |
TiDimension result = converter(value);\ | |
if ( TiDimensionIsDip(result) || TiDimensionIsPercent(result) ) {\ | |
layoutProperties.layoutName = result;\ | |
}\ | |
else {\ | |
if (!TiDimensionIsUndefined(result)) {\ | |
DebugLog(@"[WARN] Invalid value %@ specified for property %@",[TiUtils stringValue:value],@#layoutName); \ | |
} \ | |
layoutProperties.layoutName = TiDimensionUndefined;\ | |
}\ | |
[self replaceValue:value forKey:@#layoutName notification:YES]; \ | |
postaction; \ | |
} | |
#define LAYOUTPROPERTIES_SETTER(methodName,layoutName,converter,postaction) \ | |
-(void)methodName:(id)value \ | |
{ \ | |
CHECK_LAYOUT_UPDATE(layoutName,value) \ | |
layoutProperties.layoutName = converter(value); \ | |
[self replaceValue:value forKey:@#layoutName notification:YES]; \ | |
postaction; \ | |
} | |
#define LAYOUTFLAGS_SETTER(methodName,layoutName,flagName,postaction) \ | |
-(void)methodName:(id)value \ | |
{ \ | |
CHECK_LAYOUT_UPDATE(layoutName,value) \ | |
layoutProperties.layoutFlags.flagName = [TiUtils boolValue:value]; \ | |
[self replaceValue:value forKey:@#layoutName notification:NO]; \ | |
postaction; \ | |
} | |
LAYOUTPROPERTIES_SETTER_IGNORES_AUTO(setTop,top,TiDimensionFromObject,[self willChangePosition]) | |
LAYOUTPROPERTIES_SETTER_IGNORES_AUTO(setBottom,bottom,TiDimensionFromObject,[self willChangePosition]) | |
LAYOUTPROPERTIES_SETTER_IGNORES_AUTO(setLeft,left,TiDimensionFromObject,[self willChangePosition]) | |
LAYOUTPROPERTIES_SETTER_IGNORES_AUTO(setRight,right,TiDimensionFromObject,[self willChangePosition]) | |
LAYOUTPROPERTIES_SETTER(setWidth,width,TiDimensionFromObject,[self willChangeSize]) | |
LAYOUTPROPERTIES_SETTER(setHeight,height,TiDimensionFromObject,[self willChangeSize]) | |
// See below for how we handle setLayout | |
//LAYOUTPROPERTIES_SETTER(setLayout,layoutStyle,TiLayoutRuleFromObject,[self willChangeLayout]) | |
LAYOUTPROPERTIES_SETTER(setMinWidth,minimumWidth,TiFixedValueRuleFromObject,[self willChangeSize]) | |
LAYOUTPROPERTIES_SETTER(setMinHeight,minimumHeight,TiFixedValueRuleFromObject,[self willChangeSize]) | |
LAYOUTFLAGS_SETTER(setHorizontalWrap,horizontalWrap,horizontalWrap,[self willChangeLayout]) | |
// Special handling to try and avoid Apple's detection of private API 'layout' | |
-(void)setValue:(id)value forUndefinedKey:(NSString *)key | |
{ | |
if ([key isEqualToString:[@"lay" stringByAppendingString:@"out"]]) { | |
//CAN NOT USE THE MACRO | |
if (ENFORCE_BATCH_UPDATE) { | |
if (updateStarted) { | |
[self setTempProperty:value forKey:key]; \ | |
return; | |
} | |
else if(!allowLayoutUpdate){ | |
return; | |
} | |
} | |
layoutProperties.layoutStyle = TiLayoutRuleFromObject(value); | |
[self replaceValue:value forKey:[@"lay" stringByAppendingString:@"out"] notification:YES]; | |
[self willChangeLayout]; | |
return; | |
} | |
[super setValue:value forUndefinedKey:key]; | |
} | |
-(TiRect*)size | |
{ | |
TiRect *rect = [[TiRect alloc] init]; | |
if ([self viewAttached]) { | |
[self makeViewPerformSelector:@selector(fillBoundsToRect:) withObject:rect createIfNeeded:YES waitUntilDone:YES]; | |
id defaultUnit = [[NSUserDefaults standardUserDefaults] objectForKey:@"ti.ui.defaultunit"]; | |
if ([defaultUnit isKindOfClass:[NSString class]]) { | |
[rect convertToUnit:defaultUnit]; | |
} | |
} | |
else { | |
[rect setRect:CGRectZero]; | |
} | |
return [rect autorelease]; | |
} | |
-(TiRect*)rect | |
{ | |
TiRect *rect = [[TiRect alloc] init]; | |
if ([self viewAttached]) { | |
__block CGRect viewRect; | |
__block CGPoint viewPosition; | |
__block CGAffineTransform viewTransform; | |
__block CGPoint viewAnchor; | |
TiThreadPerformOnMainThread(^{ | |
TiUIView * ourView = [self view]; | |
viewRect = [ourView bounds]; | |
viewPosition = [ourView center]; | |
viewTransform = [ourView transform]; | |
viewAnchor = [[ourView layer] anchorPoint]; | |
}, YES); | |
viewRect.origin = CGPointMake(-viewAnchor.x*viewRect.size.width, -viewAnchor.y*viewRect.size.height); | |
viewRect = CGRectApplyAffineTransform(viewRect, viewTransform); | |
viewRect.origin.x += viewPosition.x; | |
viewRect.origin.y += viewPosition.y; | |
[rect setRect:viewRect]; | |
id defaultUnit = [[NSUserDefaults standardUserDefaults] objectForKey:@"ti.ui.defaultunit"]; | |
if ([defaultUnit isKindOfClass:[NSString class]]) { | |
[rect convertToUnit:defaultUnit]; | |
} | |
} | |
else { | |
[rect setRect:CGRectZero]; | |
} | |
return [rect autorelease]; | |
} | |
-(id)zIndex | |
{ | |
return [self valueForUndefinedKey:@"zindex_"]; | |
} | |
-(void)setZIndex:(id)value | |
{ | |
CHECK_LAYOUT_UPDATE(zIndex, value); | |
if ([value respondsToSelector:@selector(intValue)]) { | |
[self setVzIndex:[TiUtils intValue:value]]; | |
[self replaceValue:value forKey:@"zindex_" notification:NO]; | |
} | |
} | |
-(NSMutableDictionary*)center | |
{ | |
NSMutableDictionary* result = [[[NSMutableDictionary alloc] init] autorelease]; | |
id xVal = [self valueForUndefinedKey:@"centerX_"]; | |
if (xVal != nil) { | |
[result setObject:xVal forKey:@"x"]; | |
} | |
id yVal = [self valueForUndefinedKey:@"centerY_"]; | |
if (yVal != nil) { | |
[result setObject:yVal forKey:@"y"]; | |
} | |
if ([[result allKeys] count] > 0) { | |
return result; | |
} | |
return nil; | |
} | |
-(void)setCenter:(id)value | |
{ | |
CHECK_LAYOUT_UPDATE(center, value); | |
if ([value isKindOfClass:[NSDictionary class]]) | |
{ | |
TiDimension result; | |
id obj = [value objectForKey:@"x"]; | |
if (obj != nil) { | |
[self replaceValue:obj forKey:@"centerX_" notification:NO]; | |
result = TiDimensionFromObject(obj); | |
if ( TiDimensionIsDip(result) || TiDimensionIsPercent(result) ) { | |
layoutProperties.centerX = result; | |
} | |
else { | |
layoutProperties.centerX = TiDimensionUndefined; | |
} | |
} | |
obj = [value objectForKey:@"y"]; | |
if (obj != nil) { | |
[self replaceValue:obj forKey:@"centerY_" notification:NO]; | |
result = TiDimensionFromObject(obj); | |
if ( TiDimensionIsDip(result) || TiDimensionIsPercent(result) ) { | |
layoutProperties.centerY = result; | |
} | |
else { | |
layoutProperties.centerY = TiDimensionUndefined; | |
} | |
} | |
} else if ([value isKindOfClass:[TiPoint class]]) { | |
CGPoint p = [value point]; | |
layoutProperties.centerX = TiDimensionDip(p.x); | |
layoutProperties.centerY = TiDimensionDip(p.y); | |
} else { | |
layoutProperties.centerX = TiDimensionUndefined; | |
layoutProperties.centerY = TiDimensionUndefined; | |
} | |
[self willChangePosition]; | |
} | |
-(id)animatedCenter | |
{ | |
if (![self viewAttached]) | |
{ | |
return nil; | |
} | |
__block CGPoint result; | |
TiThreadPerformOnMainThread(^{ | |
UIView * ourView = view; | |
CALayer * ourLayer = [ourView layer]; | |
CALayer * animatedLayer = [ourLayer presentationLayer]; | |
if (animatedLayer !=nil) { | |
result = [animatedLayer position]; | |
} | |
else { | |
result = [ourLayer position]; | |
} | |
}, YES); | |
//TODO: Should this be a TiPoint? If so, the accessor fetcher might try to | |
//hold onto the point, which is undesired. | |
return [NSDictionary dictionaryWithObjectsAndKeys:NUMFLOAT(result.x),@"x",NUMFLOAT(result.y),@"y", nil]; | |
} | |
-(void)setBackgroundGradient:(id)arg | |
{ | |
TiGradient * newGradient = [TiGradient gradientFromObject:arg proxy:self]; | |
[self replaceValue:newGradient forKey:@"backgroundGradient" notification:YES]; | |
} | |
-(TiBlob*)toImage:(id)args | |
{ | |
KrollCallback *callback = nil; | |
BOOL honorScale = NO; | |
NSObject *obj = nil; | |
if( [args count] > 0) { | |
obj = [args objectAtIndex:0]; | |
if (obj == [NSNull null]) { | |
obj = nil; | |
} | |
if( [args count] > 1) { | |
honorScale = [TiUtils boolValue:[args objectAtIndex:1] def:NO]; | |
} | |
} | |
callback = (KrollCallback*)obj; | |
TiBlob *blob = [[[TiBlob alloc] init] autorelease]; | |
// we spin on the UI thread and have him convert and then add back to the blob | |
// if you pass a callback function, we'll run the render asynchronously, if you | |
// don't, we'll do it synchronously | |
TiThreadPerformOnMainThread(^{ | |
[self windowWillOpen]; | |
TiUIView *myview = [self view]; | |
CGSize size = myview.bounds.size; | |
if (CGSizeEqualToSize(size, CGSizeZero) || size.width==0 || size.height==0) | |
{ | |
CGFloat width = [self autoWidthForSize:CGSizeMake(1000,1000)]; | |
CGFloat height = [self autoHeightForSize:CGSizeMake(width,0)]; | |
if (width > 0 && height > 0) | |
{ | |
size = CGSizeMake(width, height); | |
} | |
if (CGSizeEqualToSize(size, CGSizeZero) || width==0 || height == 0) | |
{ | |
size = [UIScreen mainScreen].bounds.size; | |
} | |
CGRect rect = CGRectMake(0, 0, size.width, size.height); | |
[TiUtils setView:myview positionRect:rect]; | |
} | |
UIGraphicsBeginImageContextWithOptions(size, [myview.layer isOpaque], (honorScale ? 0.0 : 1.0)); | |
[myview.layer renderInContext:UIGraphicsGetCurrentContext()]; | |
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); | |
[blob setImage:image]; | |
[blob setMimeType:@"image/png" type:TiBlobTypeImage]; | |
UIGraphicsEndImageContext(); | |
if (callback != nil) | |
{ | |
NSDictionary *event = [NSDictionary dictionaryWithObject:blob forKey:@"blob"]; | |
[self _fireEventToListener:@"blob" withObject:event listener:callback thisObject:nil]; | |
} | |
}, (callback==nil)); | |
return blob; | |
} | |
-(TiPoint*)convertPointToView:(id)args | |
{ | |
id arg1 = nil; | |
TiViewProxy* arg2 = nil; | |
ENSURE_ARG_AT_INDEX(arg1, args, 0, NSObject); | |
ENSURE_ARG_AT_INDEX(arg2, args, 1, TiViewProxy); | |
BOOL validPoint; | |
CGPoint oldPoint = [TiUtils pointValue:arg1 valid:&validPoint]; | |
if (!validPoint) { | |
[self throwException:TiExceptionInvalidType subreason:@"Parameter is not convertable to a TiPoint" location:CODELOCATION]; | |
} | |
__block BOOL validView = NO; | |
__block CGPoint p; | |
dispatch_sync(dispatch_get_main_queue(), ^{ | |
if ([self viewAttached] && self.view.window && [arg2 viewAttached] && arg2.view.window) { | |
validView = YES; | |
p = [self.view convertPoint:oldPoint toView:arg2.view]; | |
} | |
}); | |
if (!validView) { | |
return (TiPoint*)[NSNull null]; | |
} | |
return [[[TiPoint alloc] initWithPoint:p] autorelease]; | |
} | |
#pragma mark nonpublic accessors not related to Housecleaning | |
@synthesize parent, barButtonItem; | |
-(void)setParent:(TiViewProxy*)parent_ | |
{ | |
parent = parent_; | |
if (parent_!=nil && [parent windowHasOpened]) | |
{ | |
[self windowWillOpen]; | |
} | |
} | |
-(LayoutConstraint *)layoutProperties | |
{ | |
return &layoutProperties; | |
} | |
@synthesize sandboxBounds; | |
-(void)setClippingDisable:(BOOL)value | |
{ | |
clippingDisable = value; | |
} | |
-(void)setHidden:(BOOL)newHidden withArgs:(id)args | |
{ | |
if(hidden == newHidden) | |
{ | |
return; | |
} | |
hidden = newHidden; | |
//TODO: If we have an animated show, hide, or setVisible, here's the spot for it. | |
if(parentVisible) | |
{ | |
if (hidden) | |
{ | |
[self willHide]; | |
} | |
else | |
{ | |
[self willShow]; | |
} | |
} | |
} | |
-(CGFloat)autoWidthForSize:(CGSize)size | |
{ | |
CGFloat suggestedWidth = size.width; | |
//This is the content width, which is implemented by widgets | |
CGFloat contentWidth = -1.0; | |
if ([self respondsToSelector:@selector(contentWidthForWidth:)]) { | |
contentWidth = [self contentWidthForWidth:suggestedWidth]; | |
} | |
BOOL isHorizontal = TiLayoutRuleIsHorizontal(layoutProperties.layoutStyle); | |
CGFloat result = 0.0; | |
CGRect bounds = CGRectZero; | |
if (isHorizontal) { | |
bounds.size.width = size.width; | |
bounds.size.height = size.height; | |
verticalLayoutBoundary = 0; | |
horizontalLayoutBoundary = 0; | |
horizontalLayoutRowHeight = 0; | |
} | |
CGRect sandBox = CGRectZero; | |
CGFloat thisWidth = 0.0; | |
pthread_rwlock_rdlock(&childrenLock); | |
NSArray* subproxies = [self children]; | |
for (TiViewProxy * thisChildProxy in subproxies) | |
{ | |
if (isHorizontal) { | |
sandBox = CGRectZero; | |
sandBox = [self computeChildSandbox:thisChildProxy withBounds:bounds]; | |
thisWidth = sandBox.origin.x + sandBox.size.width; | |
} | |
else { | |
thisWidth = [thisChildProxy minimumParentWidthForSize:size]; | |
} | |
if(result<thisWidth) | |
{ | |
result = thisWidth; | |
} | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
if (result < contentWidth) { | |
result = contentWidth; | |
} | |
if([self respondsToSelector:@selector(verifyWidth:)]) | |
{ | |
result = [self verifyWidth:result]; | |
} | |
return result; | |
} | |
-(CGFloat)autoHeightForSize:(CGSize)size | |
{ | |
CGFloat width = size.width; | |
//This is the content width, which is implemented by widgets | |
CGFloat contentHeight = -1.0; | |
if ([self respondsToSelector:@selector(contentHeightForWidth:)]) { | |
contentHeight = [self contentHeightForWidth:width]; | |
} | |
BOOL isAbsolute = TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle); | |
CGFloat result=0.0; | |
CGRect bounds = CGRectZero; | |
if (!isAbsolute) { | |
bounds.size.width = size.width; | |
bounds.size.height = size.height; | |
verticalLayoutBoundary = 0; | |
horizontalLayoutBoundary = 0; | |
horizontalLayoutRowHeight = 0; | |
} | |
CGRect sandBox = CGRectZero; | |
CGFloat thisHeight = 0.0; | |
pthread_rwlock_rdlock(&childrenLock); | |
NSArray* array = windowOpened ? children : pendingAdds; | |
for (TiViewProxy * thisChildProxy in array) | |
{ | |
if (!isAbsolute) { | |
sandBox = CGRectZero; | |
sandBox = [self computeChildSandbox:thisChildProxy withBounds:bounds]; | |
thisHeight = sandBox.origin.y + sandBox.size.height; | |
} | |
else { | |
thisHeight = [thisChildProxy minimumParentHeightForSize:size]; | |
} | |
if(result<thisHeight) | |
{ | |
result = thisHeight; | |
} | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
//result += currentRowHeight; | |
if (result < contentHeight) { | |
result = contentHeight; | |
} | |
if([self respondsToSelector:@selector(verifyHeight:)]) | |
{ | |
result = [self verifyHeight:result]; | |
} | |
return result; | |
} | |
-(CGFloat)minimumParentWidthForSize:(CGSize)size | |
{ | |
CGFloat suggestedWidth = size.width; | |
BOOL followsFillBehavior = TiDimensionIsAutoFill([self defaultAutoWidthBehavior:nil]); | |
BOOL recheckForFill = NO; | |
CGFloat offset = TiDimensionCalculateValue(layoutProperties.left, size.width) | |
+ TiDimensionCalculateValue(layoutProperties.right, size.width); | |
CGFloat offset2 = TiDimensionCalculateValue(layoutProperties.top, size.height) | |
+ TiDimensionCalculateValue(layoutProperties.bottom, size.height); | |
CGFloat result = offset; | |
if (TiDimensionIsDip(layoutProperties.width) || TiDimensionIsPercent(layoutProperties.width)) | |
{ | |
result += TiDimensionCalculateValue(layoutProperties.width, suggestedWidth); | |
} | |
else if (TiDimensionIsAutoFill(layoutProperties.width) || (TiDimensionIsAuto(layoutProperties.width) && followsFillBehavior) ) | |
{ | |
recheckForFill = YES; | |
result += [self autoWidthForSize:CGSizeMake(size.width - offset, size.height - offset2)]; | |
} | |
else if (TiDimensionIsUndefined(layoutProperties.width)) | |
{ | |
if (!TiDimensionIsUndefined(layoutProperties.left) && !TiDimensionIsUndefined(layoutProperties.centerX) ) { | |
result += 2 * ( TiDimensionCalculateValue(layoutProperties.centerX, suggestedWidth) - TiDimensionCalculateValue(layoutProperties.left, suggestedWidth) ); | |
} | |
else if (!TiDimensionIsUndefined(layoutProperties.left) && !TiDimensionIsUndefined(layoutProperties.right) ) { | |
result += TiDimensionCalculateMargins(layoutProperties.left, layoutProperties.right, suggestedWidth); | |
} | |
else if (!TiDimensionIsUndefined(layoutProperties.centerX) && !TiDimensionIsUndefined(layoutProperties.right) ) { | |
result += 2 * ( size.width - TiDimensionCalculateValue(layoutProperties.right, suggestedWidth) - TiDimensionCalculateValue(layoutProperties.centerX, suggestedWidth)); | |
} | |
else { | |
recheckForFill = followsFillBehavior; | |
result += [self autoWidthForSize:CGSizeMake(size.width - offset, size.height - offset2)]; | |
} | |
} | |
else | |
{ | |
result += [self autoWidthForSize:CGSizeMake(size.width - offset, size.height - offset2)]; | |
} | |
if (recheckForFill && (result < suggestedWidth) ) { | |
result = suggestedWidth; | |
} | |
return result; | |
} | |
-(CGFloat)minimumParentHeightForSize:(CGSize)size | |
{ | |
CGFloat suggestedHeight = size.height; | |
BOOL followsFillBehavior = TiDimensionIsAutoFill([self defaultAutoHeightBehavior:nil]); | |
BOOL recheckForFill = NO; | |
//Ensure that autoHeightForSize is called with the lowest limiting bound | |
CGFloat desiredWidth = MIN([self minimumParentWidthForSize:size],size.width); | |
CGFloat offset = TiDimensionCalculateValue(layoutProperties.left, size.width) | |
+ TiDimensionCalculateValue(layoutProperties.right, size.width); | |
CGFloat offset2 = TiDimensionCalculateValue(layoutProperties.top, suggestedHeight) | |
+ TiDimensionCalculateValue(layoutProperties.bottom, suggestedHeight); | |
CGFloat result = offset2; | |
if (TiDimensionIsDip(layoutProperties.height) || TiDimensionIsPercent(layoutProperties.height)) { | |
result += TiDimensionCalculateValue(layoutProperties.height, suggestedHeight); | |
} | |
else if (TiDimensionIsAutoFill(layoutProperties.height) || (TiDimensionIsAuto(layoutProperties.height) && followsFillBehavior) ) | |
{ | |
recheckForFill = YES; | |
result += [self autoHeightForSize:CGSizeMake(desiredWidth - offset, size.height - offset2)]; | |
} | |
else if (TiDimensionIsUndefined(layoutProperties.height)) | |
{ | |
if (!TiDimensionIsUndefined(layoutProperties.top) && !TiDimensionIsUndefined(layoutProperties.centerY) ) { | |
result += 2 * ( TiDimensionCalculateValue(layoutProperties.centerY, suggestedHeight) - TiDimensionCalculateValue(layoutProperties.top, suggestedHeight) ); | |
} | |
else if (!TiDimensionIsUndefined(layoutProperties.top) && !TiDimensionIsUndefined(layoutProperties.bottom) ) { | |
result += TiDimensionCalculateMargins(layoutProperties.top, layoutProperties.bottom, suggestedHeight); | |
} | |
else if (!TiDimensionIsUndefined(layoutProperties.centerY) && !TiDimensionIsUndefined(layoutProperties.bottom) ) { | |
result += 2 * ( suggestedHeight - TiDimensionCalculateValue(layoutProperties.bottom, suggestedHeight) - TiDimensionCalculateValue(layoutProperties.centerY, suggestedHeight)); | |
} | |
else { | |
recheckForFill = followsFillBehavior; | |
result += [self autoHeightForSize:CGSizeMake(desiredWidth - offset, size.height - offset2)]; | |
} | |
} | |
else | |
{ | |
result += [self autoHeightForSize:CGSizeMake(desiredWidth - offset, size.height - offset2)]; | |
} | |
if (recheckForFill && (result < suggestedHeight) ) { | |
result = suggestedHeight; | |
} | |
return result; | |
} | |
-(UIBarButtonItem*)barButtonItem | |
{ | |
if (barButtonItem == nil) | |
{ | |
isUsingBarButtonItem = YES; | |
barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:[self barButtonViewForSize:CGSizeZero]]; | |
} | |
return barButtonItem; | |
} | |
- (TiUIView *)barButtonViewForSize:(CGSize)bounds | |
{ | |
TiUIView * barButtonView = [self view]; | |
//TODO: This logic should have a good place in case that refreshLayout is used. | |
LayoutConstraint barButtonLayout = layoutProperties; | |
if (TiDimensionIsUndefined(barButtonLayout.width)) | |
{ | |
barButtonLayout.width = TiDimensionAutoSize; | |
} | |
if (TiDimensionIsUndefined(barButtonLayout.height)) | |
{ | |
barButtonLayout.height = TiDimensionAutoSize; | |
} | |
if ( (bounds.width == 0) && !(TiDimensionIsDip(barButtonLayout.width) ) ) { | |
bounds.width = [self autoWidthForSize:CGSizeMake(1000, 1000)]; | |
barButtonLayout.width = TiDimensionDip(bounds.width); | |
} | |
if ( (bounds.height == 0) && !(TiDimensionIsDip(barButtonLayout.height) ) ) { | |
bounds.height = [self autoHeightForSize:CGSizeMake(bounds.width, 1000)]; | |
barButtonLayout.height = TiDimensionDip(bounds.height); | |
} | |
CGRect barBounds; | |
barBounds.origin = CGPointZero; | |
barBounds.size = SizeConstraintViewWithSizeAddingResizing(&barButtonLayout, self, bounds, NULL); | |
[TiUtils setView:barButtonView positionRect:barBounds]; | |
[barButtonView setAutoresizingMask:UIViewAutoresizingNone]; | |
//Ensure all the child views are laid out as well | |
[self windowWillOpen]; | |
[self setParentVisible:YES]; | |
[self layoutChildren:NO]; | |
return barButtonView; | |
} | |
#pragma mark Recognizers | |
-(void)recognizedPinch:(UIPinchGestureRecognizer*)recognizer | |
{ | |
NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys: | |
NUMDOUBLE(recognizer.scale), @"scale", | |
NUMDOUBLE(recognizer.velocity), @"velocity", | |
nil]; | |
[self fireEvent:@"pinch" withObject:event]; | |
} | |
-(void)recognizedLongPress:(UILongPressGestureRecognizer*)recognizer | |
{ | |
if ([recognizer state] == UIGestureRecognizerStateBegan) { | |
CGPoint p = [recognizer locationInView:self.view]; | |
NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys: | |
NUMFLOAT(p.x), @"x", | |
NUMFLOAT(p.y), @"y", | |
nil]; | |
[self fireEvent:@"longpress" withObject:event]; | |
} | |
} | |
-(TiUIView*)view | |
{ | |
if (view == nil) | |
{ | |
WARN_IF_BACKGROUND_THREAD_OBJ | |
#ifdef VERBOSE | |
if(![NSThread isMainThread]) | |
{ | |
NSLog(@"[WARN] Break here"); | |
} | |
#endif | |
// on open we need to create a new view | |
[self viewWillAttach]; | |
view = [self newView]; | |
// check listeners dictionary to see if we need gesture recognizers | |
if ([self _hasListeners:@"pinch"]) { | |
UIPinchGestureRecognizer* r = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(recognizedPinch:)]; | |
[view addGestureRecognizer:r]; | |
[r release]; | |
} | |
if ([self _hasListeners:@"longpress"]) { | |
UILongPressGestureRecognizer* r = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(recognizedLongPress:)]; | |
[view addGestureRecognizer:r]; | |
[r release]; | |
} | |
view.proxy = self; | |
view.layer.transform = CATransform3DIdentity; | |
view.transform = CGAffineTransformIdentity; | |
[view initializeState]; | |
// fire property changes for all properties to our delegate | |
[self firePropertyChanges]; | |
[view configurationSet]; | |
pthread_rwlock_rdlock(&childrenLock); | |
NSArray * childrenArray = [[self children] retain]; | |
pthread_rwlock_unlock(&childrenLock); | |
for (id child in childrenArray) | |
{ | |
TiUIView *childView = [(TiViewProxy*)child view]; | |
[self insertSubview:childView forProxy:child]; | |
} | |
[childrenArray release]; | |
[self viewDidAttach]; | |
// make sure we do a layout of ourselves | |
if(CGRectIsEmpty(sandboxBounds) && (view != nil)){ | |
[self setSandboxBounds:view.bounds]; | |
} | |
[self relayout]; | |
viewInitialized = YES; | |
} | |
CGRect bounds = [view bounds]; | |
if (!CGPointEqualToPoint(bounds.origin, CGPointZero)) | |
{ | |
[view setBounds:CGRectMake(0, 0, bounds.size.width, bounds.size.height)]; | |
} | |
return view; | |
} | |
//CAUTION: TO BE USED ONLY WITH TABLEVIEW MAGIC | |
-(void)setView:(TiUIView *)newView | |
{ | |
if (view != newView) { | |
[view removeFromSuperview]; | |
[view release]; | |
view = [newView retain]; | |
} | |
if (self.modelDelegate != newView) { | |
if (self.modelDelegate!=nil && [self.modelDelegate respondsToSelector:@selector(detachProxy)]) | |
{ | |
[self.modelDelegate detachProxy]; | |
self.modelDelegate=nil; | |
} | |
self.modelDelegate = newView; | |
} | |
} | |
-(NSMutableDictionary*)langConversionTable | |
{ | |
return nil; | |
} | |
#pragma mark Methods subclasses should override for behavior changes | |
-(BOOL)optimizeSubviewInsertion | |
{ | |
//Return YES for any view that implements a wrapperView that is a TiUIView (Button and ScrollView currently) and a basic view | |
return ( [view isMemberOfClass:[TiUIView class]] ) ; | |
} | |
-(BOOL)suppressesRelayout | |
{ | |
return NO; | |
} | |
-(BOOL)supportsNavBarPositioning | |
{ | |
return YES; | |
} | |
// TODO: Re-evaluate this along with the other controller propagation mechanisms, post 1.3.0. | |
// Returns YES for anything that can have a UIController object in its parent view | |
-(BOOL)canHaveControllerParent | |
{ | |
return YES; | |
} | |
-(BOOL)shouldDetachViewOnUnload | |
{ | |
return YES; | |
} | |
-(UIView *)parentViewForChild:(TiViewProxy *)child | |
{ | |
return view; | |
} | |
#pragma mark Event trigger methods | |
-(void)windowWillOpen | |
{ | |
//TODO: This should be properly handled and moved, but for now, let's force it (Redundantly, I know.) | |
if (parent != nil) { | |
[self parentWillShow]; | |
} | |
pthread_rwlock_rdlock(&childrenLock); | |
// this method is called just before the top level window | |
// that this proxy is part of will open and is ready for | |
// the views to be attached | |
if (windowOpened==YES) | |
{ | |
pthread_rwlock_unlock(&childrenLock); | |
return; | |
} | |
windowOpened = YES; | |
windowOpening = YES; | |
// If the window was previously opened, it may need to have | |
// its existing children redrawn | |
// Maybe need to call layout children instead for non absolute layout | |
if (children != nil) { | |
for (TiViewProxy* child in children) { | |
[self layoutChild:child optimize:NO withMeasuredBounds:[[self size] rect]]; | |
[child windowWillOpen]; | |
} | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
if (pendingAdds!=nil) | |
{ | |
for (id child in pendingAdds) | |
{ | |
[self add:child]; | |
[child windowWillOpen]; | |
} | |
RELEASE_TO_NIL(pendingAdds); | |
} | |
} | |
-(void)windowDidOpen | |
{ | |
windowOpening = NO; | |
pthread_rwlock_rdlock(&childrenLock); | |
for (TiViewProxy *child in children) | |
{ | |
[child windowDidOpen]; | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)windowWillClose | |
{ | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(windowWillClose)]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)windowDidClose | |
{ | |
pthread_rwlock_rdlock(&childrenLock); | |
for (TiViewProxy *child in children) | |
{ | |
[child windowDidClose]; | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
[self detachView]; | |
windowOpened=NO; | |
} | |
-(void)willFirePropertyChanges | |
{ | |
// for subclasses | |
if ([view respondsToSelector:@selector(willFirePropertyChanges)]) | |
{ | |
[view performSelector:@selector(willFirePropertyChanges)]; | |
} | |
} | |
-(void)didFirePropertyChanges | |
{ | |
// for subclasses | |
if ([view respondsToSelector:@selector(didFirePropertyChanges)]) | |
{ | |
[view performSelector:@selector(didFirePropertyChanges)]; | |
} | |
} | |
-(void)viewWillAttach | |
{ | |
// for subclasses | |
} | |
-(void)viewDidAttach | |
{ | |
// for subclasses | |
} | |
-(void)viewWillDetach | |
{ | |
// for subclasses | |
} | |
-(void)viewDidDetach | |
{ | |
// for subclasses | |
} | |
-(void)parentWillAppear:(id)args | |
{ | |
if ([self viewAttached]) { | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentWillAppear:) withObject:args]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
} | |
-(void)parentDidAppear:(id)args | |
{ | |
if ([self viewAttached]) { | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentDidAppear:) withObject:args]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
} | |
-(void)parentWillDisappear:(id)args | |
{ | |
if ([self viewAttached]) { | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentWillDisappear:) withObject:args]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
} | |
-(void)parentDidDisappear:(id)args | |
{ | |
if ([self viewAttached]) { | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentDidDisappear:) withObject:args]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
} | |
#pragma mark Housecleaning state accessors | |
-(BOOL)viewHasSuperview:(UIView *)superview | |
{ | |
return [(UIView *)view superview] == superview; | |
} | |
-(BOOL)viewAttached | |
{ | |
return view!=nil && windowOpened; | |
} | |
//TODO: When swapping about proxies, views are uninitialized, aren't they? | |
-(BOOL)viewInitialized | |
{ | |
return viewInitialized && (view != nil); | |
} | |
-(BOOL)viewReady | |
{ | |
return view!=nil && | |
CGRectIsEmpty(view.bounds)==NO && | |
CGRectIsNull(view.bounds)==NO && | |
[view superview] != nil; | |
} | |
-(BOOL)windowHasOpened | |
{ | |
return windowOpened; | |
} | |
-(BOOL)windowIsOpening | |
{ | |
return windowOpening; | |
} | |
- (BOOL) isUsingBarButtonItem | |
{ | |
return isUsingBarButtonItem; | |
} | |
-(CGRect)appFrame //TODO: Why is this here? It doesn't have anything to do with a specific instance. | |
{ | |
CGRect result=[[UIScreen mainScreen] applicationFrame]; | |
switch ([[UIApplication sharedApplication] statusBarOrientation]) | |
{ | |
case UIInterfaceOrientationLandscapeLeft: | |
case UIInterfaceOrientationLandscapeRight: | |
{ | |
CGFloat leftMargin = result.origin.y; | |
CGFloat topMargin = result.origin.x; | |
CGFloat newHeight = result.size.width; | |
CGFloat newWidth = result.size.height; | |
result = CGRectMake(leftMargin, topMargin, newWidth, newHeight); | |
break; | |
} | |
default: { | |
break; | |
} | |
} | |
return result; | |
} | |
#pragma mark Building up and Tearing down | |
-(id)init | |
{ | |
if ((self = [super init])) | |
{ | |
destroyLock = [[NSRecursiveLock alloc] init]; | |
pthread_rwlock_init(&childrenLock, NULL); | |
} | |
return self; | |
} | |
-(void)_initWithProperties:(NSDictionary*)properties | |
{ | |
[self startLayout:nil]; | |
// Set horizontal layout wrap:true as default | |
layoutProperties.layoutFlags.horizontalWrap = YES; | |
[self initializeProperty:@"horizontalWrap" defaultValue:NUMBOOL(YES)]; | |
if (properties!=nil) | |
{ | |
NSString *objectId = [properties objectForKey:@"id"]; | |
NSString* className = [properties objectForKey:@"className"]; | |
NSMutableArray* classNames = [properties objectForKey:@"classNames"]; | |
NSString *type = [NSStringFromClass([self class]) stringByReplacingOccurrencesOfString:@"TiUI" withString:@""]; | |
type = [[type stringByReplacingOccurrencesOfString:@"Proxy" withString:@""] lowercaseString]; | |
TiStylesheet *stylesheet = [[[self pageContext] host] stylesheet]; | |
NSString *basename = [[self pageContext] basename]; | |
NSString *density = [TiUtils isRetinaDisplay] ? @"high" : @"medium"; | |
if (objectId!=nil || className != nil || classNames != nil || [stylesheet basename:basename density:density hasTag:type]) | |
{ | |
// get classes from proxy | |
NSString *className = [properties objectForKey:@"className"]; | |
NSMutableArray *classNames = [properties objectForKey:@"classNames"]; | |
if (classNames==nil) | |
{ | |
classNames = [NSMutableArray arrayWithCapacity:1]; | |
} | |
if (className!=nil) | |
{ | |
[classNames addObject:className]; | |
} | |
NSDictionary *merge = [stylesheet stylesheet:objectId density:density basename:basename classes:classNames tags:[NSArray arrayWithObject:type]]; | |
if (merge!=nil) | |
{ | |
// incoming keys take precendence over existing stylesheet keys | |
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:merge]; | |
[dict addEntriesFromDictionary:properties]; | |
properties = dict; | |
} | |
} | |
// do a translation of language driven keys to their converted counterparts | |
// for example titleid should look up the title in the Locale | |
NSMutableDictionary *table = [self langConversionTable]; | |
if (table!=nil) | |
{ | |
for (id key in table) | |
{ | |
// determine which key in the lang table we need to use | |
// from the lang property conversion key | |
id langKey = [properties objectForKey:key]; | |
if (langKey!=nil) | |
{ | |
// eg. titleid -> title | |
id convertKey = [table objectForKey:key]; | |
// check and make sure we don't already have that key | |
// since you can't override it if already present | |
if ([properties objectForKey:convertKey]==nil) | |
{ | |
id newValue = [TiLocale getString:langKey comment:nil]; | |
if (newValue!=nil) | |
{ | |
[(NSMutableDictionary*)properties setObject:newValue forKey:convertKey]; | |
} | |
} | |
} | |
} | |
} | |
} | |
[super _initWithProperties:properties]; | |
[self finishLayout:nil]; | |
} | |
-(void)dealloc | |
{ | |
RELEASE_TO_NIL(pendingAdds); | |
RELEASE_TO_NIL(destroyLock); | |
pthread_rwlock_destroy(&childrenLock); | |
//Dealing with children is in _destroy, which is called by super dealloc. | |
[super dealloc]; | |
} | |
-(BOOL)retainsJsObjectForKey:(NSString *)key | |
{ | |
return ![key isEqualToString:@"animation"]; | |
} | |
-(void)firePropertyChanges | |
{ | |
[self willFirePropertyChanges]; | |
id<NSFastEnumeration> values = [self allKeys]; | |
[view readProxyValuesWithKeys:values]; | |
[self didFirePropertyChanges]; | |
} | |
-(TiUIView*)newView | |
{ | |
NSString * proxyName = NSStringFromClass([self class]); | |
if ([proxyName hasSuffix:@"Proxy"]) | |
{ | |
Class viewClass = nil; | |
NSString * className = [proxyName substringToIndex:[proxyName length]-5]; | |
viewClass = NSClassFromString(className); | |
if (viewClass != nil) | |
{ | |
return [[viewClass alloc] init]; | |
} | |
} | |
else | |
{ | |
DeveloperLog(@"[WARN] No TiView for Proxy: %@, couldn't find class: %@",self,proxyName); | |
} | |
return [[TiUIView alloc] initWithFrame:[self appFrame]]; | |
} | |
-(void)detachView | |
{ | |
[destroyLock lock]; | |
if (view!=nil) | |
{ | |
[self viewWillDetach]; | |
// hold the view during detachment -- but we can't release it immediately. | |
// What if it (or a superview or subview) is in the middle of an animation? | |
// We probably need to be even MORE careful here. | |
[[view retain] autorelease]; | |
view.proxy = nil; | |
if (self.modelDelegate!=nil && [self.modelDelegate respondsToSelector:@selector(detachProxy)]) | |
{ | |
[self.modelDelegate detachProxy]; | |
} | |
self.modelDelegate = nil; | |
[view removeFromSuperview]; | |
RELEASE_TO_NIL(view); | |
[self viewDidDetach]; | |
} | |
pthread_rwlock_rdlock(&childrenLock); | |
[[self children] makeObjectsPerformSelector:@selector(detachView)]; | |
pthread_rwlock_unlock(&childrenLock); | |
[destroyLock unlock]; | |
} | |
-(void)_destroy | |
{ | |
[destroyLock lock]; | |
if ([self destroyed]) | |
{ | |
// not safe to do multiple times given rwlock | |
[destroyLock unlock]; | |
return; | |
} | |
// _destroy is called during a JS context shutdown, to inform the object to | |
// release all its memory and references. this will then cause dealloc | |
// on objects that it contains (assuming we don't have circular references) | |
// since some of these objects are registered in the context and thus still | |
// reachable, we need _destroy to help us start the unreferencing part | |
pthread_rwlock_wrlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(setParent:) withObject:nil]; | |
RELEASE_TO_NIL(children); | |
pthread_rwlock_unlock(&childrenLock); | |
[super _destroy]; | |
//Part of super's _destroy is to release the modelDelegate, which in our case is ALSO the view. | |
//As such, we need to have the super happen before we release the view, so that we can insure that the | |
//release that triggers the dealloc happens on the main thread. | |
if (barButtonItem != nil) | |
{ | |
if ([NSThread isMainThread]) | |
{ | |
RELEASE_TO_NIL(barButtonItem); | |
} | |
else | |
{ | |
TiThreadReleaseOnMainThread(barButtonItem, NO); | |
barButtonItem = nil; | |
} | |
} | |
if (view!=nil) | |
{ | |
if ([NSThread isMainThread]) | |
{ | |
[self detachView]; | |
} | |
else | |
{ | |
view.proxy = nil; | |
TiThreadReleaseOnMainThread(view, NO); | |
view = nil; | |
} | |
} | |
[destroyLock unlock]; | |
} | |
-(void)destroy | |
{ | |
//FIXME- me already have a _destroy, refactor this | |
[self _destroy]; | |
} | |
-(void)removeBarButtonView | |
{ | |
isUsingBarButtonItem = NO; | |
[self setBarButtonItem:nil]; | |
} | |
#pragma mark Callbacks | |
-(void)didReceiveMemoryWarning:(NSNotification*)notification | |
{ | |
// Only release a view if we're the only living reference for it | |
// WARNING: do not call [self view] here as that will create the | |
// view if it doesn't yet exist (thus defeating the purpose of | |
// this method) | |
//NOTE: for now, we're going to have to turn this off until post | |
//1.4 where we can figure out why the drawing is screwed up since | |
//the views aren't reattaching. | |
/* | |
if (view!=nil && [view retainCount]==1) | |
{ | |
[self detachView]; | |
}*/ | |
[super didReceiveMemoryWarning:notification]; | |
} | |
-(void)animationCompleted:(TiAnimation*)animation | |
{ | |
[self forgetProxy:animation]; | |
[[self view] animationCompleted]; | |
} | |
-(void)makeViewPerformSelector:(SEL)selector withObject:(id)object createIfNeeded:(BOOL)create waitUntilDone:(BOOL)wait | |
{ | |
BOOL isAttached = [self viewAttached]; | |
if(!isAttached && !create) | |
{ | |
return; | |
} | |
if([NSThread isMainThread]) | |
{ | |
[[self view] performSelector:selector withObject:object]; | |
return; | |
} | |
if(isAttached) | |
{ | |
TiThreadPerformOnMainThread(^{[[self view] performSelector:selector withObject:object];}, wait); | |
return; | |
} | |
TiThreadPerformOnMainThread(^{ | |
[[self view] performSelector:selector withObject:object]; | |
}, wait); | |
} | |
#pragma mark Listener Management | |
-(BOOL)_hasListeners:(NSString *)type | |
{ | |
if ([super _hasListeners:type]) | |
{ | |
return YES; | |
} | |
// check our parent since we optimize the fire with | |
// the check | |
if (parent!=nil) | |
{ | |
// walk up the chain | |
return [parent _hasListeners:type]; | |
} | |
return NO; | |
} | |
-(void)fireEvent:(NSString*)type withObject:(id)obj withSource:(id)source propagate:(BOOL)propagate | |
{ | |
// Note that some events (like movie 'complete') are fired after the view is removed/dealloc'd. | |
// Because of the handling below, we can safely set the view to 'nil' in this case. | |
TiUIView* proxyView = [self viewAttached] ? view : nil; | |
//TODO: We have to do view instead of [self view] because of a freaky race condition that can | |
//happen in the background (See bug 2809). This assumes that view == [self view], which may | |
//not always be the case in the future. Then again, we shouldn't be dealing with view in the BG... | |
// Have to handle the situation in which the proxy's view might be nil... like, for example, | |
// with table rows. Automagically assume any nil view we're firing an event for is A-OK. | |
if (proxyView == nil || [proxyView interactionEnabled]) { | |
[super fireEvent:type withObject:obj withSource:source propagate:YES]; | |
// views support event propagation. we need to check our | |
// parent and if he has the same named listener, we fire | |
// an event and set the source of the event to ourself | |
if (parent!=nil && propagate==YES) | |
{ | |
[parent fireEvent:type withObject:obj withSource:source]; | |
} | |
} | |
} | |
-(void)_listenerAdded:(NSString*)type count:(int)count | |
{ | |
if (self.modelDelegate!=nil && [(NSObject*)self.modelDelegate respondsToSelector:@selector(listenerAdded:count:)]) | |
{ | |
[self.modelDelegate listenerAdded:type count:count]; | |
} | |
else if(view!=nil) // don't create the view if not already realized | |
{ | |
[self.view listenerAdded:type count:count]; | |
} | |
} | |
-(void)_listenerRemoved:(NSString*)type count:(int)count | |
{ | |
if (self.modelDelegate!=nil && [(NSObject*)self.modelDelegate respondsToSelector:@selector(listenerRemoved:count:)]) | |
{ | |
[self.modelDelegate listenerRemoved:type count:count]; | |
} | |
else if(view!=nil) // don't create the view if not already realized | |
{ | |
[self.view listenerRemoved:type count:count]; | |
} | |
} | |
#pragma mark Layout events, internal and external | |
#define SET_AND_PERFORM(flagBit,action) \ | |
if(OSAtomicTestAndSetBarrier(flagBit, &dirtyflags)) \ | |
{ \ | |
action; \ | |
} | |
-(void)willEnqueue | |
{ | |
SET_AND_PERFORM(TiRefreshViewEnqueued,return); | |
[TiLayoutQueue addViewProxy:self]; | |
} | |
-(void)willEnqueueIfVisible | |
{ | |
if(parentVisible && !hidden) | |
{ | |
[self willEnqueue]; | |
} | |
} | |
-(void)willChangeSize | |
{ | |
SET_AND_PERFORM(TiRefreshViewSize,return); | |
if (!TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle)) | |
{ | |
[self willChangeLayout]; | |
} | |
if(TiDimensionIsUndefined(layoutProperties.centerX) || | |
TiDimensionIsUndefined(layoutProperties.centerY)) | |
{ | |
[self willChangePosition]; | |
} | |
[self willEnqueueIfVisible]; | |
[parent contentsWillChange]; | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentSizeWillChange)]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)willChangePosition | |
{ | |
SET_AND_PERFORM(TiRefreshViewPosition,return); | |
if(TiDimensionIsUndefined(layoutProperties.width) || | |
TiDimensionIsUndefined(layoutProperties.height)) | |
{//The only time size can be changed by the margins is if the margins define the size. | |
[self willChangeSize]; | |
} | |
[self willEnqueueIfVisible]; | |
[parent contentsWillChange]; | |
} | |
-(void)willChangeZIndex | |
{ | |
SET_AND_PERFORM(TiRefreshViewZIndex,); | |
//Nothing cascades from here. | |
[self willEnqueueIfVisible]; | |
} | |
-(void)willShow; | |
{ | |
if(dirtyflags) | |
{//If we have any need for changes, let's enroll ourselves. | |
[self willEnqueue]; | |
} | |
SET_AND_PERFORM(TiRefreshViewZIndex,); | |
[parent contentsWillChange]; | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentWillShow)]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)willHide; | |
{ | |
SET_AND_PERFORM(TiRefreshViewZIndex,); | |
[parent contentsWillChange]; | |
[self willEnqueue]; | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentWillHide)]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)willChangeLayout | |
{ | |
SET_AND_PERFORM(TiRefreshViewChildrenPosition,return); | |
[self willEnqueueIfVisible]; | |
pthread_rwlock_rdlock(&childrenLock); | |
[children makeObjectsPerformSelector:@selector(parentWillRelay)]; | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
-(void)contentsWillChange | |
{ | |
BOOL isAutoSize = NO; | |
BOOL heightIsAutoSize = NO; | |
if (TiDimensionIsAutoSize(layoutProperties.width)) | |
{ | |
isAutoSize = YES; | |
} | |
else if (TiDimensionIsAuto(layoutProperties.width) && TiDimensionIsAutoSize([self defaultAutoWidthBehavior:nil]) ) | |
{ | |
isAutoSize = YES; | |
} | |
else if (TiDimensionIsUndefined(layoutProperties.width) && TiDimensionIsAutoSize([self defaultAutoWidthBehavior:nil])) | |
{ | |
int pinCount = 0; | |
if (!TiDimensionIsUndefined(layoutProperties.left) ) { | |
pinCount ++; | |
} | |
if (!TiDimensionIsUndefined(layoutProperties.centerX) ) { | |
pinCount ++; | |
} | |
if (!TiDimensionIsUndefined(layoutProperties.right) ) { | |
pinCount ++; | |
} | |
if (pinCount < 2) { | |
isAutoSize = YES; | |
} | |
} | |
if (!isAutoSize) { | |
if (TiDimensionIsAutoSize(layoutProperties.height)) | |
{ | |
isAutoSize = YES; | |
} | |
else if (TiDimensionIsAuto(layoutProperties.height) && TiDimensionIsAutoSize([self defaultAutoHeightBehavior:nil]) ) | |
{ | |
isAutoSize = YES; | |
} | |
else if (TiDimensionIsUndefined(layoutProperties.height) && TiDimensionIsAutoSize([self defaultAutoHeightBehavior:nil])) | |
{ | |
int pinCount = 0; | |
if (!TiDimensionIsUndefined(layoutProperties.top) ) { | |
pinCount ++; | |
} | |
if (!TiDimensionIsUndefined(layoutProperties.centerY) ) { | |
pinCount ++; | |
} | |
if (!TiDimensionIsUndefined(layoutProperties.bottom) ) { | |
pinCount ++; | |
} | |
if (pinCount < 2) { | |
isAutoSize = YES; | |
} | |
} | |
} | |
if (isAutoSize) | |
{ | |
[self willChangeSize]; | |
} | |
else if (!TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle)) | |
{//Since changing size already does this, we only need to check | |
//Layout if the changeSize didn't | |
[self willChangeLayout]; | |
} | |
} | |
-(void)parentSizeWillChange | |
{ | |
// if percent or undefined size, change size | |
if(TiDimensionIsUndefined(layoutProperties.width) || | |
TiDimensionIsUndefined(layoutProperties.height) || | |
TiDimensionIsPercent(layoutProperties.width) || | |
TiDimensionIsPercent(layoutProperties.height)) | |
{ | |
[self willChangeSize]; | |
} | |
if(!TiDimensionIsDip(layoutProperties.centerX) || | |
!TiDimensionIsDip(layoutProperties.centerY)) | |
{ | |
[self willChangePosition]; | |
} | |
} | |
-(void)parentWillRelay | |
{ | |
// if percent or undefined size, change size | |
if(TiDimensionIsUndefined(layoutProperties.width) || | |
TiDimensionIsUndefined(layoutProperties.height) || | |
TiDimensionIsPercent(layoutProperties.width) || | |
TiDimensionIsPercent(layoutProperties.height)) | |
{ | |
[self willChangeSize]; | |
} | |
[self willChangePosition]; | |
} | |
-(void)parentWillShow | |
{ | |
VerboseLog(@"[INFO] Parent Will Show for %@",self); | |
if(parentVisible) | |
{//Nothing to do here, we're already visible here. | |
return; | |
} | |
parentVisible = YES; | |
if(!hidden) | |
{ //We should propagate this new status! Note this does not change the visible property. | |
[self willShow]; | |
} | |
} | |
-(void)parentWillHide | |
{ | |
VerboseLog(@"[INFO] Parent Will Hide for %@",self); | |
if(!parentVisible) | |
{//Nothing to do here, we're already visible here. | |
return; | |
} | |
parentVisible = NO; | |
if(!hidden) | |
{ //We should propagate this new status! Note this does not change the visible property. | |
[self willHide]; | |
} | |
} | |
#pragma mark Layout actions | |
// Need this so we can overload the sandbox bounds on split view detail/master | |
-(void)determineSandboxBounds | |
{ | |
UIView * ourSuperview = [[self view] superview]; | |
if(ourSuperview == nil) | |
{ | |
//TODO: Should we even be relaying out? I guess so. | |
sandboxBounds = CGRectZero; | |
} | |
else | |
{ | |
sandboxBounds = [ourSuperview bounds]; | |
} | |
} | |
-(void)refreshView:(TiUIView *)transferView | |
{ | |
WARN_IF_BACKGROUND_THREAD_OBJ; | |
OSAtomicTestAndClearBarrier(TiRefreshViewEnqueued, &dirtyflags); | |
if(!parentVisible) | |
{ | |
VerboseLog(@"[INFO] Parent Invisible"); | |
return; | |
} | |
if(hidden) | |
{ | |
VerboseLog(@"Removing from superview"); | |
if([self viewAttached]) | |
{ | |
[[self view] removeFromSuperview]; | |
} | |
return; | |
} | |
BOOL changedFrame = NO; | |
//BUG BARRIER: Code in this block is legacy code that should be factored out. | |
if (windowOpened && [self viewAttached]) | |
{ | |
CGRect oldFrame = [[self view] frame]; | |
BOOL relayout = ![self suppressesRelayout]; | |
if (parent != nil && (!TiLayoutRuleIsAbsolute([parent layoutProperties]->layoutStyle))) { | |
//Do not mess up the sandbox in vertical/horizontal layouts | |
relayout = NO; | |
} | |
if(relayout) | |
{ | |
[self determineSandboxBounds]; | |
} | |
[self relayout]; | |
[self layoutChildren:NO]; | |
if (!CGRectEqualToRect(oldFrame, [[self view] frame])) { | |
[parent childWillResize:self]; | |
} | |
} | |
//END BUG BARRIER | |
if(OSAtomicTestAndClearBarrier(TiRefreshViewSize, &dirtyflags)) | |
{ | |
[self refreshSize]; | |
if(TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle)) | |
{ | |
pthread_rwlock_rdlock(&childrenLock); | |
for (TiViewProxy * thisChild in children) | |
{ | |
[thisChild setSandboxBounds:sizeCache]; | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
} | |
changedFrame = YES; | |
} | |
else if(transferView != nil) | |
{ | |
[transferView setBounds:sizeCache]; | |
} | |
if(OSAtomicTestAndClearBarrier(TiRefreshViewPosition, &dirtyflags)) | |
{ | |
[self refreshPosition]; | |
changedFrame = YES; | |
} | |
else if(transferView != nil) | |
{ | |
[transferView setCenter:positionCache]; | |
} | |
//We should only recurse if we're a non-absolute layout. Otherwise, the views can take care of themselves. | |
if(OSAtomicTestAndClearBarrier(TiRefreshViewChildrenPosition, &dirtyflags) && (transferView == nil)) | |
//If transferView is non-nil, this will be managed by the table row. | |
{ | |
} | |
if(transferView != nil) | |
{ | |
//TODO: Better handoff of view | |
[self setView:transferView]; | |
} | |
//By now, we MUST have our view set to transferView. | |
if(changedFrame || (transferView != nil)) | |
{ | |
[view setAutoresizingMask:autoresizeCache]; | |
} | |
if(OSAtomicTestAndClearBarrier(TiRefreshViewZIndex, &dirtyflags) || (transferView != nil)) | |
{ | |
[parent insertSubview:view forProxy:self]; | |
} | |
} | |
-(void)refreshPosition | |
{ | |
OSAtomicTestAndClearBarrier(TiRefreshViewPosition, &dirtyflags); | |
} | |
-(void)refreshSize | |
{ | |
OSAtomicTestAndClearBarrier(TiRefreshViewSize, &dirtyflags); | |
} | |
-(void)insertSubview:(UIView *)childView forProxy:(TiViewProxy *)childProxy | |
{ | |
int result = 0; | |
int childZindex = [childProxy vzIndex]; | |
BOOL earlierSibling = YES; | |
UIView * ourView = [self parentViewForChild:childProxy]; | |
if (![self optimizeSubviewInsertion]) { | |
for (UIView* subview in [ourView subviews]) | |
{ | |
if (![subview isKindOfClass:[TiUIView class]]) { | |
result++; | |
} | |
} | |
} | |
pthread_rwlock_rdlock(&childrenLock); | |
for (TiViewProxy * thisChildProxy in children) | |
{ | |
if(thisChildProxy == childProxy) | |
{ | |
earlierSibling = NO; | |
continue; | |
} | |
if(![thisChildProxy viewHasSuperview:ourView]) | |
{ | |
continue; | |
} | |
int thisChildZindex = [thisChildProxy vzIndex]; | |
if((thisChildZindex < childZindex) || | |
(earlierSibling && (thisChildZindex == childZindex))) | |
{ | |
result ++; | |
} | |
} | |
pthread_rwlock_unlock(&childrenLock); | |
[ourView insertSubview:childView atIndex:result]; | |
} | |
#pragma mark Layout commands that need refactoring out | |
-(void)relayout | |
{ | |
if (!repositioning) | |
{ | |
ENSURE_UI_THREAD_0_ARGS | |
repositioning = YES; | |
UIView *parentView = [parent parentViewForChild:self]; | |
CGSize referenceSize = (parentView != nil) ? parentView.bounds.size : sandboxBounds.size; | |
if (parent != nil && (!TiLayoutRuleIsAbsolute([parent layoutProperties]->layoutStyle)) ) { | |
sizeCache.size = SizeConstraintViewWithSizeAddingResizing(&layoutProperties,self, sandboxBounds.size, &autoresizeCache); | |
} | |
else { | |
sizeCache.size = SizeConstraintViewWithSizeAddingResizing(&layoutProperties,self, referenceSize, &autoresizeCache); | |
} | |
positionCache = PositionConstraintGivenSizeBoundsAddingResizing(&layoutProperties, self, sizeCache.size, | |
[[view layer] anchorPoint], referenceSize, sandboxBounds.size, &autoresizeCache); | |
positionCache.x += sizeCache.origin.x + sandboxBounds.origin.x; | |
positionCache.y += sizeCache.origin.y + sandboxBounds.origin.y; | |
[view setAutoresizingMask:autoresizeCache]; | |
[view setCenter:positionCache]; | |
[view setBounds:sizeCache]; | |
[parent insertSubview:view forProxy:self]; | |
repositioning = NO; | |
if ([observer respondsToSelector:@selector(proxyDidRelayout:)]) { | |
[observer proxyDidRelayout:self]; | |
} | |
if ([self _hasListeners:@"postlayout"]) { | |
[self fireEvent:@"postlayout" withObject:nil]; | |
} | |
} | |
#ifdef VERBOSE | |
else | |
{ | |
DeveloperLog(@"[INFO] %@ Calling Relayout from within relayout.",self); | |
} | |
#endif | |
} | |
-(void)layoutChildrenIfNeeded | |
{ | |
IGNORE_IF_NOT_OPENED | |
// if not attached, ignore layout | |
if ([self viewAttached]) | |
{ | |
// if not visible, ignore layout | |
if (view.hidden) | |
{ | |
OSAtomicTestAndClearBarrier(TiRefreshViewEnqueued, &dirtyflags); | |
return; | |
} | |
[self refreshView:nil]; | |
BOOL wasSet=OSAtomicTestAndClearBarrier(NEEDS_LAYOUT_CHILDREN, &dirtyflags); | |
if (wasSet && [self viewAttached]) | |
{ | |
[self layoutChildren:NO]; | |
} | |
} | |
} | |
-(BOOL)willBeRelaying | |
{ | |
return dirtyflags != 0; | |
} | |
-(void)childWillResize:(TiViewProxy *)child | |
{ | |
[self contentsWillChange]; | |
IGNORE_IF_NOT_OPENED | |
pthread_rwlock_rdlock(&childrenLock); | |
BOOL containsChild = [children containsObject:child]; | |
pthread_rwlock_unlock(&childrenLock); | |
ENSURE_VALUE_CONSISTENCY(containsChild,YES); | |
if (!TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle)) | |
{ | |
BOOL alreadySet = OSAtomicTestAndSetBarrier(NEEDS_LAYOUT_CHILDREN, &dirtyflags); | |
if (!alreadySet) | |
{ | |
[self willEnqueue]; | |
} | |
} | |
} | |
-(void)reposition | |
{ | |
IGNORE_IF_NOT_OPENED | |
UIView* superview = [[self view] superview]; | |
if (![self viewAttached] || view.hidden || superview == nil) | |
{ | |
VerboseLog(@"[INFO] Reposition is exiting early in %@.",self); | |
return; | |
} | |
if ([NSThread isMainThread]) | |
{ //NOTE: This will cause problems with ScrollableView, or is a new wrapper needed? | |
[self willChangeSize]; | |
[self willChangePosition]; | |
[self refreshView:nil]; | |
} | |
else | |
{ | |
VerboseLog(@"[INFO] Reposition was called by a background thread in %@.",self); | |
TiThreadPerformOnMainThread(^{[self reposition];}, NO); | |
} | |
} | |
-(NSArray*)measureChildren:(NSArray*)childArray | |
{ | |
if ([childArray count] == 0) { | |
return nil; | |
} | |
BOOL horizontalNoWrap = TiLayoutRuleIsHorizontal(layoutProperties.layoutStyle) && !TiLayoutFlagsHasHorizontalWrap(&layoutProperties); | |
NSMutableArray * measuredBounds = [NSMutableArray arrayWithCapacity:[childArray count]]; | |
NSUInteger i, count = [childArray count]; | |
int maxHeight = 0; | |
//First measure the sandbox bounds | |
for (id child in childArray) | |
{ | |
TiRect * childRect = [[TiRect alloc] init]; | |
CGRect childBounds = CGRectZero; | |
UIView * ourView = [self parentViewForChild:child]; | |
if (ourView != nil) | |
{ | |
CGRect bounds = [ourView bounds]; | |
if (horizontalNoWrap) { | |
maxHeight = MAX(maxHeight, bounds.size.height); | |
} | |
if(!TiLayoutRuleIsAbsolute(layoutProperties.layoutStyle)) | |
{ | |
bounds = [self computeChildSandbox:child withBounds:bounds]; | |
} | |
childBounds.origin.x = bounds.origin.x; | |
childBounds.origin.y = bounds.origin.y; | |
childBounds.size.width = bounds.size.width; | |
childBounds.size.height = bounds.size.height; | |
} | |
[childRect setRect:childBounds]; | |
[measuredBounds addObject:childRect]; | |
[childRect release]; | |
} | |
//If it is a horizontal layout ensure that all the children in a row have the | |
//same height for the sandbox | |
if (horizontalNoWrap) | |
{ | |
for (i=0; i<count; i++) | |
{ | |
[(TiRect*)[measuredBounds objectAtIndex:i] setHeight:[NSNumber numberWithInt:maxHeight]]; | |
} | |
} | |
else if(TiLayoutRuleIsHorizontal(layoutProperties.layoutStyle) && (count > 1) ) | |
{ | |
int startIndex,endIndex, currentTop; | |
startIndex = endIndex = maxHeight = currentTop = -1; | |
for (i=0; i<count; i++) | |
{ | |
CGRect childSandbox = (CGRect)[(TiRect*)[measuredBounds objectAtIndex:i] rect]; | |
if (startIndex == -1) | |
{ | |
//FIRST ELEMENT | |
startIndex = i; | |
maxHeight = childSandbox.size.height; | |
currentTop = childSandbox.origin.y; | |
} | |
else | |
{ | |
if (childSandbox.origin.y != currentTop) | |
{ | |
//MOVED TO NEXT ROW | |
endIndex = i; | |
for (int j=startIndex; j<endIndex; j++) | |
{ | |
[(TiRect*)[measuredBounds objectAtIndex:j] setHeight:[NSNumber numberWithInt:maxHeight]]; | |
} | |
startIndex = i; | |
endIndex = -1; | |
maxHeight = childSandbox.size.height; | |
currentTop = childSandbox.origin.y; | |
} | |
else if (childSandbox.size.height > maxHeight) | |
{ | |
//SAME ROW HEIGHT CHANGED | |
maxHeight = childSandbox.size.height; | |
} | |
} | |
} | |
if (endIndex == -1) | |
{ | |
//LAST ROW | |
for (i=startIndex; i<count; i++) | |
{ | |
[(TiRect*)[measuredBounds objectAtIndex:i] setHeight:[NSNumber numberWithInt:maxHeight]]; | |
} | |
} | |
} | |
return measuredBounds; | |
} | |
-(CGRect)computeChildSandbox:(TiViewProxy*)child withBounds:(CGRect)bounds | |
{ | |
if(TiLayoutRuleIsVertical(layoutProperties.layoutStyle)) | |
{ | |
BOOL followsFillBehavior = TiDimensionIsAutoFill([child defaultAutoHeightBehavior:nil]); | |
bounds.origin.y = verticalLayoutBoundary; | |
CGFloat boundingValue = bounds.size.height-verticalLayoutBoundary; | |
if (boundingValue < 0) { | |
boundingValue = 0; | |
} | |
//Ensure that autoHeightForSize is called with the lowest limiting bound | |
CGFloat desiredWidth = MIN([child minimumParentWidthForSize:bounds.size],bounds.size.width); | |
//TOP + BOTTOM | |
CGFloat offsetV = TiDimensionCalculateValue([child layoutProperties]->top, bounds.size.height) | |
+ TiDimensionCalculateValue([child layoutProperties]->bottom, bounds.size.height); | |
//LEFT + RIGHT | |
CGFloat offsetH = TiDimensionCalculateValue([child layoutProperties]->left, bounds.size.width) | |
+ TiDimensionCalculateValue([child layoutProperties]->right, bounds.size.width); | |
TiDimension constraint = [child layoutProperties]->height; | |
if (TiDimensionIsDip(constraint) || TiDimensionIsPercent(constraint)) | |
{ | |
bounds.size.height = TiDimensionCalculateValue(constraint, bounds.size.height) + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (TiDimensionIsAutoFill(constraint)) | |
{ | |
//Fill up the remaining | |
bounds.size.height = boundingValue + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (TiDimensionIsAutoSize(constraint)) | |
{ | |
bounds.size.height = [child autoHeightForSize:CGSizeMake(desiredWidth - offsetH,boundingValue)] + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (TiDimensionIsAuto(constraint) ) | |
{ | |
if (followsFillBehavior) { | |
//FILL behavior | |
bounds.size.height = boundingValue + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else { | |
//SIZE behavior | |
bounds.size.height = [child autoHeightForSize:CGSizeMake(desiredWidth - offsetH,boundingValue)] + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
} | |
else if (TiDimensionIsUndefined(constraint)) | |
{ | |
if (!TiDimensionIsUndefined([child layoutProperties]->top) && !TiDimensionIsUndefined([child layoutProperties]->centerY) ) { | |
CGFloat height = 2 * ( TiDimensionCalculateValue([child layoutProperties]->centerY, boundingValue) - TiDimensionCalculateValue([child layoutProperties]->top, boundingValue) ); | |
bounds.size.height = height + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (!TiDimensionIsUndefined([child layoutProperties]->top) && !TiDimensionIsUndefined([child layoutProperties]->bottom) ) { | |
bounds.size.height = boundingValue + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (!TiDimensionIsUndefined([child layoutProperties]->centerY) && !TiDimensionIsUndefined([child layoutProperties]->bottom) ) { | |
CGFloat height = 2 * ( boundingValue - TiDimensionCalculateValue([child layoutProperties]->bottom, boundingValue) - TiDimensionCalculateValue([child layoutProperties]->centerY, boundingValue)); | |
bounds.size.height = height + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else if (followsFillBehavior) { | |
//FILL behavior | |
bounds.size.height = boundingValue + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else { | |
//SIZE behavior | |
bounds.size.height = [child autoHeightForSize:CGSizeMake(desiredWidth - offsetH,boundingValue)] + offsetV; | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
} | |
} | |
else if(TiLayoutRuleIsHorizontal(layoutProperties.layoutStyle)) | |
{ | |
BOOL horizontalWrap = TiLayoutFlagsHasHorizontalWrap(&layoutProperties); | |
BOOL followsFillBehavior = TiDimensionIsAutoFill([child defaultAutoWidthBehavior:nil]); | |
CGFloat boundingWidth = bounds.size.width-horizontalLayoutBoundary; | |
CGFloat boundingHeight = bounds.size.height-verticalLayoutBoundary; | |
//LEFT + RIGHT | |
CGFloat offsetH = TiDimensionCalculateValue([child layoutProperties]->left, bounds.size.width) | |
+ TiDimensionCalculateValue([child layoutProperties]->right, bounds.size.width); | |
//TOP + BOTTOM | |
CGFloat offsetV = TiDimensionCalculateValue([child layoutProperties]->top, bounds.size.height) | |
+ TiDimensionCalculateValue([child layoutProperties]->bottom, bounds.size.height); | |
TiDimension constraint = [child layoutProperties]->width; | |
CGFloat desiredWidth; | |
BOOL recalculateWidth = NO; | |
if (TiDimensionIsDip(constraint) || TiDimensionIsPercent(constraint)) | |
{ | |
desiredWidth = TiDimensionCalculateValue(constraint, bounds.size.width) + offsetH; | |
} | |
else if (TiDimensionIsUndefined(constraint)) | |
{ | |
if (!TiDimensionIsUndefined([child layoutProperties]->left) && !TiDimensionIsUndefined([child layoutProperties]->centerX) ) { | |
desiredWidth = 2 * ( TiDimensionCalculateValue([child layoutProperties]->centerX, boundingWidth) - TiDimensionCalculateValue([child layoutProperties]->left, boundingWidth) ); | |
desiredWidth += offsetH; | |
} | |
else if (!TiDimensionIsUndefined([child layoutProperties]->left) && !TiDimensionIsUndefined([child layoutProperties]->right) ) { | |
recalculateWidth = YES; | |
followsFillBehavior = YES; | |
desiredWidth = [child autoWidthForSize:CGSizeMake(boundingWidth - offsetH,boundingHeight - offsetV)] + offsetH; | |
} | |
else if (!TiDimensionIsUndefined([child layoutProperties]->centerX) && !TiDimensionIsUndefined([child layoutProperties]->right) ) { | |
desiredWidth = 2 * ( boundingWidth - TiDimensionCalculateValue([child layoutProperties]->right, boundingWidth) - TiDimensionCalculateValue([child layoutProperties]->centerX, boundingWidth)); | |
desiredWidth += offsetH; | |
} | |
else { | |
recalculateWidth = YES; | |
desiredWidth = [child autoWidthForSize:CGSizeMake(boundingWidth - offsetH,boundingHeight - offsetV)] + offsetH; | |
} | |
} | |
else { | |
//This block takes care of auto,SIZE and FILL. If it is size ensure followsFillBehavior is set to false | |
recalculateWidth = YES; | |
desiredWidth = [child autoWidthForSize:CGSizeMake(boundingWidth - offsetH,boundingHeight - offsetV)] + offsetH; | |
if (TiDimensionIsAutoSize(constraint)) { | |
followsFillBehavior = NO; | |
} else if(TiDimensionIsAutoFill(constraint)) { | |
followsFillBehavior = YES; | |
} | |
} | |
CGFloat desiredHeight; | |
BOOL childIsFixedHeight = TiDimensionIsPercent([child layoutProperties]->height) || TiDimensionIsDip([child layoutProperties]->height); | |
if (childIsFixedHeight) | |
{ | |
//For percent width is irrelevant | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(0,bounds.size.height)]; | |
bounds.size.height = desiredHeight; | |
} | |
if (horizontalWrap && (desiredWidth > boundingWidth)) { | |
if (horizontalLayoutBoundary == 0.0) { | |
//This is start of row | |
bounds.origin.x = horizontalLayoutBoundary; | |
bounds.origin.y = verticalLayoutBoundary; | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(desiredWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
verticalLayoutBoundary += bounds.size.height; | |
horizontalLayoutRowHeight = 0.0; | |
} | |
else { | |
//This is not the start of row. Move to next row | |
horizontalLayoutBoundary = 0.0; | |
verticalLayoutBoundary += horizontalLayoutRowHeight; | |
horizontalLayoutRowHeight = 0; | |
bounds.origin.x = horizontalLayoutBoundary; | |
bounds.origin.y = verticalLayoutBoundary; | |
boundingWidth = bounds.size.width; | |
boundingHeight = bounds.size.height - verticalLayoutBoundary; | |
if (!recalculateWidth) { | |
if (desiredWidth < boundingWidth) { | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(desiredWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
horizontalLayoutBoundary += desiredWidth; | |
bounds.size.width = desiredWidth; | |
horizontalLayoutRowHeight = bounds.size.height; | |
} | |
else { | |
//Will take up whole row | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(boundingWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
} | |
else if (followsFillBehavior) { | |
//Will take up whole row | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(boundingWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
else { | |
desiredWidth = [child autoWidthForSize:CGSizeMake(boundingWidth - offsetH,boundingHeight - offsetV)] + offsetH; | |
if (desiredWidth < boundingWidth) { | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(desiredWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
bounds.size.width = desiredWidth; | |
horizontalLayoutBoundary = bounds.size.width; | |
horizontalLayoutRowHeight = bounds.size.height; | |
} | |
else { | |
//Will take up whole row | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(boundingWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
verticalLayoutBoundary += bounds.size.height; | |
} | |
} | |
} | |
} | |
else { | |
//If it fits update the horizontal layout row height | |
if (!childIsFixedHeight) | |
{ | |
desiredHeight = [child minimumParentHeightForSize:CGSizeMake(desiredWidth,boundingHeight)]; | |
bounds.size.height = desiredHeight; | |
} | |
bounds.origin.x = horizontalLayoutBoundary; | |
bounds.origin.y = verticalLayoutBoundary; | |
if (bounds.size.height > horizontalLayoutRowHeight) { | |
horizontalLayoutRowHeight = bounds.size.height; | |
} | |
if (!recalculateWidth) { | |
//DIP,PERCENT,UNDEFINED WITH ATLEAST 2 PINS one of them being centerX | |
bounds.size.width = desiredWidth; | |
horizontalLayoutBoundary += bounds.size.width; | |
} | |
else if(followsFillBehavior) | |
{ | |
//FILL that fits in left over space. Move to next row | |
bounds.size.width = boundingWidth; | |
if (horizontalWrap) { | |
horizontalLayoutBoundary = 0.0; | |
verticalLayoutBoundary += horizontalLayoutRowHeight; | |
horizontalLayoutRowHeight = 0.0; | |
} else { | |
horizontalLayoutBoundary += bounds.size.width; | |
} | |
} | |
else | |
{ | |
//SIZE behavior | |
bounds.size.width = desiredWidth; | |
horizontalLayoutBoundary += bounds.size.width; | |
} | |
} | |
} | |
return bounds; | |
} | |
-(void)layoutChild:(TiViewProxy*)child optimize:(BOOL)optimize withMeasuredBounds:(CGRect)bounds | |
{ | |
IGNORE_IF_NOT_OPENED | |
UIView * ourView = [self parentViewForChild:child]; | |
if (ourView==nil) | |
{ | |
return; | |
} | |
if (optimize==NO) | |
{ | |
TiUIView *childView = [child view]; | |
if ([childView superview]!=ourView) | |
{ | |
//TODO: Optimize! | |
int insertPosition = 0; | |
int childZIndex = [child vzIndex]; | |
pthread_rwlock_rdlock(&childrenLock); | |
int childProxyIndex = [children indexOfObject:child]; | |
BOOL optimizeInsertion = [self optimizeSubviewInsertion]; | |
for (TiUIView * thisView in [ourView subviews]) | |
{ | |
if ( (!optimizeInsertion) && (![thisView isKindOfClass:[TiUIView class]]) ) | |
{ | |
insertPosition ++; | |
continue; | |
} | |
int thisZIndex=[(TiViewProxy *)[thisView proxy] vzIndex]; | |
if (childZIndex < thisZIndex) //We've found our stop! | |
{ | |
break; | |
} | |
if (childZIndex == thisZIndex) | |
{ | |
TiProxy * thisProxy = [thisView proxy]; | |
if (childProxyIndex <= [children indexOfObject:thisProxy]) | |
{ | |
break; | |
} | |
} | |
insertPosition ++; | |
} | |
[ourView insertSubview:childView atIndex:insertPosition]; | |
pthread_rwlock_unlock(&childrenLock); // must release before calling resize | |
if ( !CGSizeEqualToSize(child.sandboxBounds.size, bounds.size) ) { | |
//Child will not resize if sandbox size does not change | |
[self childWillResize:child]; | |
} | |
} | |
} | |
[child setSandboxBounds:bounds]; | |
if ([[child view] animating]) | |
{ | |
// changing the layout while animating is bad, ignore for now | |
DebugLog(@"[WARN] New layout set while view %@ animating: Will relayout after animation.", child); | |
} | |
else | |
{ | |
[child relayout]; | |
} | |
// tell our children to also layout | |
[child layoutChildren:optimize]; | |
} | |
-(void)layoutChildren:(BOOL)optimize | |
{ | |
IGNORE_IF_NOT_OPENED | |
verticalLayoutBoundary = 0.0; | |
horizontalLayoutBoundary = 0.0; | |
horizontalLayoutRowHeight = 0.0; | |
if (optimize==NO) | |
{ | |
OSAtomicTestAndSetBarrier(NEEDS_LAYOUT_CHILDREN, &dirtyflags); | |
} | |
//TODO: This is really expensive, but what can you do? Laying out the child needs the lock again. | |
pthread_rwlock_rdlock(&childrenLock); | |
NSArray * childrenArray = [[self children] retain]; | |
pthread_rwlock_unlock(&childrenLock); | |
NSUInteger childCount = [childrenArray count]; | |
if (childCount > 0) { | |
NSArray * measuredBounds = [[self measureChildren:childrenArray] retain]; | |
NSUInteger childIndex; | |
for (childIndex = 0; childIndex < childCount; childIndex++) { | |
id child = [childrenArray objectAtIndex:childIndex]; | |
CGRect childSandBox = (CGRect)[(TiRect*)[measuredBounds objectAtIndex:childIndex] rect]; | |
[self layoutChild:child optimize:optimize withMeasuredBounds:childSandBox]; | |
} | |
[measuredBounds release]; | |
} | |
[childrenArray release]; | |
if (optimize==NO) | |
{ | |
OSAtomicTestAndClearBarrier(NEEDS_LAYOUT_CHILDREN, &dirtyflags); | |
} | |
} | |
-(TiDimension)defaultAutoWidthBehavior:(id)unused | |
{ | |
return TiDimensionAutoFill; | |
} | |
-(TiDimension)defaultAutoHeightBehavior:(id)unused | |
{ | |
return TiDimensionAutoFill; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
SDK 2.1