Skip to content

Instantly share code, notes, and snippets.

@erichocean
Created September 15, 2008 00:45
Show Gist options
  • Save erichocean/10784 to your computer and use it in GitHub Desktop.
Save erichocean/10784 to your computer and use it in GitHub Desktop.
//
// 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"
//
// 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