Created
September 15, 2008 00:45
-
-
Save erichocean/10784 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// SCView.h | |
// StateCode | |
// | |
// Created by Erich Ocean on 8/13/08. | |
// Copyright 2008 Erich Atlas Ocean. All rights reserved. | |
// | |
@interface SCView : NSResponder | |
{ | |
NSNumber *guid; | |
NSMutableDictionary *outlets; | |
NSMutableArray *bindings; | |
SCView *parentNode; | |
SCView *previousSibling; | |
SCView *nextSibling; | |
SCView *firstChild; | |
SCView *lastChild; | |
NSMutableArray *childNodes; | |
CALayer *rootLayer; | |
CALayer *containerLayer; | |
BOOL wantsMouseMoved; | |
BOOL isAwake; | |
BOOL isVisible; | |
BOOL isVisibleInWindow; | |
BOOL displayIsVisible; | |
BOOL isEnabled; | |
BOOL isSelected; | |
// private | |
BOOL _parentNodeVisibility; | |
NSMutableArray *_outlets; | |
NSMutableDictionary *_bindings; | |
STATECHART_IVARS | |
} | |
STATECHART_PROPERTIES | |
@property (readonly) NSNumber *guid; | |
@property BOOL wantsMouseMoved; | |
@property BOOL isAwake; | |
@property BOOL isVisible; | |
@property BOOL isVisibleInWindow; | |
@property BOOL displayIsVisible; | |
@property BOOL isEnabled; | |
@property BOOL isSelected; | |
@property SCView *parentNode; | |
@property SCView *previousSibling; | |
@property SCView *nextSibling; | |
@property SCView *firstChild; | |
@property SCView *lastChild; | |
@property( copy, readwrite ) NSMutableArray *childNodes; // don't use setter! | |
@property CGFloat opacity; | |
@property CGRect frame; | |
@property (readonly) CGSize preferredFrameSize; | |
@property CGRect bounds; | |
@property CGSize size; | |
@property CGPoint origin; | |
@property (copy) NSDictionary *style; | |
@property (copy) NSDictionary *actions; | |
@property (copy) NSArray *constraints; | |
@property id layoutManager; | |
@property CALayer *rootLayer; | |
@property CALayer *containerLayer; | |
@property( readonly ) CALayer *containerLayerOrRootLayer; | |
- (SCView *) nextResponder; | |
+ (CALayer *) exampleLayer; | |
+ (SCView *) view; | |
+ (SCView *) view: (NSDictionary *) config; | |
- initWithLayer: (CALayer *) layer; | |
- (void) setView: (SCView *) view forOutlet: (NSString *) outlet; | |
- (SCView *) insert: (SCView *) view before: (SCView *) beforeView; | |
- (void) append: (SCView *) view; | |
- (void) removeFromParent; | |
- (void) removeChild: (SCView *) child; | |
- (void) replaceChild: (SCView *) oldView with: (SCView *) view; | |
- (void) clear; | |
// used by SCWindow only... | |
- (SCView *) _insert: (SCView *) view before: (SCView *) beforeView updateLayerTree: (BOOL) updateLayerTree; | |
- (void) awake; | |
// triggered by view.isVisible = YES|NO; | |
- (void) show; | |
- (void) hide; | |
- (void) setRootLayerNameIfNeeded; | |
@end | |
@interface SCView (Delegate) | |
- (void) willAddToParent: (SCView *) parentView before: (SCView *) beforeView; | |
- (void) willAddChild: (SCView *) childView before: (SCView *) beforeView; | |
- (void) didAddToParent: (SCView *) parentView before: (SCView *) beforeView; | |
- (void) didAddChild: (SCView *) childView before: (SCView *) beforeView; | |
- (void) willRemoveFromParent: (SCView *) parentView; | |
- (void) willRemoveChild: (SCView *) view; | |
- (void) didRemoveFromParent: (SCView *) parentView; | |
- (void) didRemoveChild: (SCView *) view; | |
@end | |
#import "SCPathModule.h" |
This file contains hidden or 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
// | |
// SCView.m | |
// StateCode | |
// | |
// Created by Erich Ocean on 8/13/08. | |
// Copyright 2008 Erich Atlas Ocean. All rights reserved. | |
// | |
#import "SCView.h" | |
#import "SCWindow.h" | |
#import "SCBinding.h" | |
#import "SCObservable.h" | |
@interface SCView (Private) | |
+ (NSNumber *) _nextGuid; | |
- (SCView *) _insert: (SCView *) view before: (SCView *) beforeView updateLayerTree: (BOOL) updateLayerTree; | |
- (void) _rebuildChildNodes; | |
- (void) _updateIsVisibleInWindow; | |
- (void) _updateIsVisibleInWindow: (BOOL) parentNodeVisibility; | |
- (void) _flushInternalCaches; | |
- (void) _invalidateClippingFrame; | |
- (void) _show; | |
- (void) _hide; | |
@end | |
static NSMutableDictionary *_views; | |
@implementation SCView | |
STATECHART_SYNTHESIZE | |
@synthesize guid; | |
@synthesize wantsMouseMoved; | |
@synthesize isAwake; | |
@synthesize isVisible; | |
@synthesize isVisibleInWindow; | |
@synthesize displayIsVisible; | |
@synthesize isEnabled; | |
@synthesize isSelected; | |
@synthesize parentNode; | |
@synthesize previousSibling; | |
@synthesize nextSibling; | |
@synthesize firstChild; | |
@synthesize lastChild; | |
@synthesize childNodes; | |
@synthesize rootLayer; | |
@synthesize containerLayer; | |
+ (void) initialize | |
{ | |
static BOOL initialized = NO; | |
if ( !initialized ) { | |
_views = [[NSMutableDictionary alloc] init]; | |
initialized = YES; | |
} | |
} | |
+ (NSNumber *) _nextGuid | |
{ | |
static unsigned int _guid = 0; | |
return [NSNumber numberWithUnsignedInt: _guid++]; // FIXME: not thread safe | |
} | |
- (SCView *) nextResponder | |
{ | |
return self.parentNode; | |
} | |
- (void) setView: (SCView *) view forOutlet: (NSString *) outlet | |
{ | |
[_outlets addObject: outlet]; | |
[outlets setObject: view forKey: outlet]; | |
} | |
- (void) awake | |
{ | |
if ( !self.isAwake ) { | |
// The default implementation... | |
[self setRootLayerNameIfNeeded]; | |
// ...appends outlets to the view tree... | |
for ( NSString *key in _outlets ) { | |
SCView *view = [outlets objectForKey: key]; | |
[self append: view]; | |
} | |
// ...and then calls awake() recursively... | |
for ( NSString *key in _outlets ) { | |
SCView *view = [outlets objectForKey: key]; | |
[view awake]; | |
} | |
// ... and then configures any bindings. | |
for ( NSString *key in _bindings ) { | |
SCBinding *binding = [self bindTo: [key substringWithRange: NSMakeRange( 0, [key length] - 7)] from: [_bindings objectForKey: key]]; | |
[bindings addObject: binding]; | |
} | |
_outlets = nil; // don't need to keep this around anymore | |
_bindings = nil; // don't need to keep this around anymore | |
self.isAwake = YES; | |
} | |
} | |
+ (CALayer *) exampleLayer | |
{ | |
return [CALayer layer]; | |
} | |
+ (SCView *) view | |
{ | |
return [[self alloc] init]; | |
} | |
+ (SCView *) view: (NSDictionary *) config | |
{ | |
SCView *view = nil; | |
CALayer *layer = [config objectForKey: @"rootLayer"]; | |
if ( layer ) view = [[self alloc] initWithLayer: layer]; | |
else view = [self view]; | |
// configure outlets | |
NSArray *outlets = [config valueForKey: @"outlets"]; | |
if ( outlets ) { | |
for ( NSString *outlet in outlets ) { | |
[view setView: [config valueForKey: outlet] forOutlet: outlet]; | |
} | |
} | |
else outlets = [NSArray array]; | |
// configure options | |
for ( NSString *key in config ) { | |
if ( [key isEqualToString: @"outlets"] ) continue; | |
if ( [key hasSuffix: @"Binding"] ) { | |
// bindings are configure in awake | |
[view->_bindings setObject: [config objectForKey: key] forKey: key]; | |
continue; | |
} | |
if ( [outlets indexOfObject: key] == NSNotFound ) { | |
// must be an option... | |
NSRange range = [key rangeOfString: @"."]; // check for key paths... | |
if ( range.location == NSNotFound ) { | |
[view setValue: [config valueForKey: key] forKey: key]; | |
} | |
else [view setValue: [config valueForKey: key] forKeyPath: key]; | |
} | |
} | |
return view; | |
} | |
- (CGFloat) opacity | |
{ | |
return self.rootLayer.opacity; | |
} | |
- (void) setOpacity: (CGFloat) value | |
{ | |
if ( value != self.rootLayer.opacity ) { | |
[self willChangeValueForKey: @"opacity"]; | |
self.rootLayer.opacity = value; | |
[self didChangeValueForKey: @"opacity"]; | |
} | |
} | |
- (CGRect) frame | |
{ | |
return self.rootLayer.frame; | |
} | |
- (void) setFrame: (CGRect) theFrame | |
{ | |
self.rootLayer.frame = theFrame; | |
} | |
- (CGRect) bounds | |
{ | |
return self.rootLayer.bounds; | |
} | |
- (void) setBounds: (CGRect) theBounds | |
{ | |
self.rootLayer.bounds = theBounds; | |
} | |
- (CGSize) size | |
{ | |
return self.rootLayer.bounds.size; | |
} | |
- (void) setSize: (CGSize) theSize | |
{ | |
[self.rootLayer setValue: [NSValue valueWithSize: NSSizeFromCGSize( theSize )] forKeyPath: @"bounds.size"]; | |
} | |
- (CGPoint) origin | |
{ | |
return self.rootLayer.bounds.origin; | |
} | |
- (void) setOrigin: (CGPoint) theOrigin | |
{ | |
[self.rootLayer setValue: [NSValue valueWithPoint: NSPointFromCGPoint( theOrigin)] forKeyPath: @"bounds.origin"]; | |
} | |
- (NSDictionary *) style | |
{ | |
return self.rootLayer.style; | |
} | |
- (void) setStyle: (NSDictionary *) aStyle | |
{ | |
self.rootLayer.style = aStyle; | |
} | |
- (NSDictionary *) actions | |
{ | |
return self.rootLayer.actions; | |
} | |
- (void) setActions: (NSDictionary *) theActions | |
{ | |
self.rootLayer.actions = theActions; | |
} | |
- (id) layoutManager | |
{ | |
return self.rootLayer.layoutManager; | |
} | |
- (void) setLayoutManager: (id) layoutManager | |
{ | |
self.rootLayer.layoutManager = layoutManager; | |
} | |
- (NSArray *) constraints | |
{ | |
return self.rootLayer.constraints; | |
} | |
- (void) setConstraints: (NSArray *) constraints | |
{ | |
self.rootLayer.constraints = constraints; | |
} | |
- (void) setRootLayerNameIfNeeded | |
{ | |
if ( !rootLayer.name ) rootLayer.name = [NSString stringWithFormat: @"layer%@", [self.guid stringValue]]; | |
} | |
- (CGSize) preferredFrameSize { return CGSizeMake( 500.0, 500.0 ); } // a reasonable default | |
- init | |
{ | |
return [self initWithLayer: [[self class] exampleLayer]]; | |
} | |
- initWithLayer: (CALayer *) layer | |
{ | |
if ( self = [super init] ) { | |
STATECHART_INIT | |
outlets = [NSMutableDictionary dictionary]; | |
_outlets = [NSMutableArray array]; // used to retain outlet load order until awake is called | |
bindings = [NSMutableArray array]; | |
_bindings = [NSMutableDictionary dictionary]; // used to store bindings until they object awakes | |
self.parentNode = nil; | |
self.childNodes = [NSMutableArray array]; | |
self.rootLayer = layer; | |
self.wantsMouseMoved = NO; | |
self.isAwake = NO; | |
self.isVisible = YES; | |
isEnabled = NO; | |
isSelected = NO; | |
guid = [SCView _nextGuid]; | |
[_views setObject: self forKey: self.guid]; // without this, our view *could* disappear due to garbage collection | |
} | |
return self; | |
} | |
- (void) setVisible: (BOOL) yn | |
{ | |
if ( yn != isVisible ) { | |
[self willChangeValueForKey: @"isVisible"]; | |
isVisible = yn; | |
if ( isVisible ) [self _show]; | |
else [self _hide]; | |
// update parent state | |
[self _updateIsVisibleInWindow]; | |
[self didChangeValueForKey: @"isVisible"]; | |
} | |
} | |
- (void) show | |
{ | |
self.rootLayer.hidden = NO; | |
self.containerLayer.hidden = NO; | |
self.displayIsVisible = YES; | |
} | |
- (void) hide | |
{ | |
self.rootLayer.hidden = YES; | |
self.containerLayer.hidden = YES; | |
self.displayIsVisible = NO; | |
} | |
- (void) _show | |
{ | |
// add animation support later | |
[self show]; | |
} | |
- (void) _hide | |
{ | |
// add animation support later | |
[self hide]; | |
} | |
- (CALayer *) containerLayerOrRootLayer | |
{ | |
CALayer *layer = nil; | |
if ( self.containerLayer ) layer = self.containerLayer; | |
else if ( self.rootLayer ) layer = self.rootLayer; | |
return layer; | |
} | |
- (void) setRootLayer: (CALayer *) layer | |
{ | |
rootLayer = layer; | |
layer.delegate = self; | |
} | |
- (void) append: (SCView *) view { [self insert: view before: nil]; } | |
- (SCView *) insert: (SCView *) view before: (SCView *) beforeView | |
{ | |
return [self _insert: view before: beforeView updateLayerTree: YES]; | |
} | |
- (void) removeFromParent { if ( self.parentNode ) [self.parentNode removeChild: self]; } | |
- (void) removeChild: (SCView *) view | |
{ | |
if ( !view ) return ; | |
if ( view.parentNode != self ) | |
@throw [NSException exceptionWithName: @"SCView#removeChild:" reason: @"view must belong to parent" userInfo: nil]; | |
[view willRemoveFromParent: self]; | |
[self willRemoveChild: view]; | |
// unpatch. | |
if ( view.previousSibling ) { | |
view.previousSibling.nextSibling = view.nextSibling; | |
} | |
else self.firstChild = view.nextSibling; | |
if ( view.nextSibling ) { | |
view.nextSibling.previousSibling = view.previousSibling; | |
} | |
else self.lastChild = view.previousSibling; | |
// Update Layer Tree -- ANIMATE | |
CALayer *layer = self.containerLayerOrRootLayer; | |
if ( layer && ( view.rootLayer.superlayer == layer) ) { | |
[view.rootLayer removeFromSuperlayer]; | |
} | |
// regenerate the childNodes array. | |
[self _rebuildChildNodes]; | |
view.nextSibling = nil; | |
view.previousSibling = nil; | |
view.parentNode = nil; | |
// update parent state. | |
[view _updateIsVisibleInWindow]; | |
[view _flushInternalCaches]; | |
[view _invalidateClippingFrame]; | |
[self didRemoveChild: view]; | |
[view didRemoveFromParent: self]; | |
} | |
- (void) replaceChild: (SCView *) oldView with: (SCView *) view | |
{ | |
[self insert: view before: oldView]; | |
[self removeChild: oldView]; | |
} | |
- (void) clear | |
{ | |
while( self.firstChild ) [self removeChild: self.firstChild]; | |
} | |
- (SCView *) _insert: (SCView *) view before: (SCView *) beforeView updateLayerTree: (BOOL) updateLayerTree | |
{ | |
// verify that beforeView is a child. | |
if ( beforeView ) { | |
if (beforeView.parentNode != self) | |
@throw [NSException exceptionWithName: @"SCView#_insert:before:updateLayerTree:" reason: @"beforeView must belong to the receiver" userInfo: nil]; | |
if (beforeView == view) | |
@throw [NSException exceptionWithName: @"SCView#_insert:before:updateLayerTree:" reason: @"views cannot be the same" userInfo: nil]; | |
} | |
if ( view.parentNode ) [view removeFromParent]; | |
[self willAddChild: view before: beforeView]; | |
[view willAddToParent: self before: beforeView]; | |
// patch in the view. | |
if ( beforeView ) { | |
view.previousSibling = beforeView.previousSibling; | |
view.nextSibling = beforeView; | |
beforeView.previousSibling = view; | |
} | |
else { | |
view.previousSibling = self.lastChild; | |
view.nextSibling = nil; | |
self.lastChild = view; | |
} | |
if ( view.previousSibling ) view.previousSibling.nextSibling = view; | |
if ( view.previousSibling == nil ) self.firstChild = view; | |
view.parentNode = self; | |
// Update Layer Tree -- ANIMATE | |
if ( updateLayerTree ) { | |
CALayer *beforeLayer = ( beforeView ) ? beforeView.rootLayer : nil; | |
CALayer *superlayer = self.containerLayerOrRootLayer; | |
if ( superlayer ) { | |
if ( beforeLayer ) { | |
[superlayer insertSublayer: view.rootLayer below: beforeLayer]; | |
} | |
else [superlayer addSublayer: view.rootLayer]; | |
} | |
// regenerate the childNodes array. | |
[self _rebuildChildNodes]; | |
} | |
// update cached states. | |
[view _updateIsVisibleInWindow]; | |
[view _flushInternalCaches]; | |
[view _invalidateClippingFrame]; | |
// call notices. | |
[view didAddToParent: self before: beforeView]; | |
[self didAddChild: view before: beforeView]; | |
return self; | |
} | |
- (void) willAddToParent: (SCView *) parentView before: (SCView *) beforeView {} | |
- (void) willAddChild: (SCView *) childView before: (SCView *) beforeView {} | |
- (void) didAddToParent: (SCView *) parentView before: (SCView *) beforeView {} | |
- (void) didAddChild: (SCView *) childView before: (SCView *) beforeView {} | |
- (void) willRemoveFromParent: (SCView *) parentView {} | |
- (void) willRemoveChild: (SCView *) view {} | |
- (void) didRemoveFromParent: (SCView *) parentView {} | |
- (void) didRemoveChild: (SCView *) view {} | |
- (void) _rebuildChildNodes | |
{ | |
NSMutableArray *newChildNodes = [NSMutableArray array]; | |
if ( self.firstChild ) { | |
[newChildNodes addObject: self.firstChild]; | |
SCView *childToAdd = self.firstChild.nextSibling; | |
while ( childToAdd ) | |
{ | |
[newChildNodes addObject: childToAdd]; | |
childToAdd = childToAdd.nextSibling; | |
} | |
} | |
self.childNodes = newChildNodes; | |
} | |
- (void) _updateIsVisibleInWindow | |
{ | |
SCView *_parentNode = self.parentNode; | |
_parentNodeVisibility = ( _parentNode ) ? _parentNode.isVisibleInWindow : NO ; | |
[self _updateIsVisibleInWindow: _parentNodeVisibility]; | |
} | |
- (void) _updateIsVisibleInWindow: (BOOL) parentNodeVisibility | |
{ | |
BOOL visible = parentNodeVisibility && self.isVisible; | |
// if state changes, update and notify children. | |
if ( visible != self.isVisibleInWindow ) { | |
self.isVisibleInWindow = visible; | |
// this.recacheFrames() ; | |
SCView *child = self.firstChild; | |
while ( child ) { | |
[child _updateIsVisibleInWindow: visible]; | |
child = child.nextSibling; | |
} | |
} | |
} | |
- (void) _flushInternalCaches {} | |
- (void) _invalidateClippingFrame {} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment