Skip to content

Instantly share code, notes, and snippets.

@erichocean
Created September 16, 2008 21:28
Show Gist options
  • Save erichocean/11138 to your computer and use it in GitHub Desktop.
Save erichocean/11138 to your computer and use it in GitHub Desktop.
//
// SCWindow.h
// StateCode
//
// Created by Erich Ocean on 8/13/08.
// Copyright 2008 Erich Atlas Ocean. All rights reserved.
//
@class SCLayerView, SCView, SCPage;
@interface SCWindow : NSWindow
{
NSString *nowShowing;
NSMutableDictionary *pages;
NSMutableArray *bindings;
BOOL mouseIsDragging;
BOOL isAwake;
id delegate;
// private
CALayer *_mainLayer;
SCView *_mouseDownView;
NSMutableArray *_lastHovered;
NSTrackingArea *_contentViewTrackingArea;
NSMutableDictionary *_bindings;
SCPage *_currentPage;
}
@property id delegate;
@property BOOL isAwake;
@property (copy) NSString *nowShowing;
@property (readonly) BOOL mouseIsDragging;
@property (readonly) CALayer *rootLayer;
@property (readonly) SCView *rootView;
+ window;
+ window: (NSDictionary *) config;
- (void) awake;
- (void) insert: (SCView *) view before: (SCView *) beforeView;
- (void) append: (SCView *) view;
- (SCView *) firstViewForEvent: (NSEvent *) evt;
@end
@interface SCWindow (Delegate)
- (BOOL) wantsMouseMovedEventsInWindow: (SCWindow *) win;
- (void) mouseDown: (NSEvent *) evt inWindow: (SCWindow *) win;
- (void) mouseMoved: (NSEvent *) evt inWindow: (SCWindow *) win;
- (void) mouseUp: (NSEvent *) evt inWindow: (SCWindow *) win;
@end
//
// SCWindow.m
// StateCode
//
// Created by Erich Ocean on 8/13/08.
// Copyright 2008 Erich Atlas Ocean. All rights reserved.
//
#import "SCWindow.h"
#import "SCObservable.h"
#import "SCView.h"
#import "SCPage.h"
#import "SCBinding.h"
@interface SCWindow (Private)
- (NSTrackingArea *) _contentViewTrackingArea;
- (void) replaceMainLayerWithLayer: (CALayer *) aLayer;
- (void) _notifyDelegateMouseDown: (NSEvent *) evt;
- (void) _notifyDelegateMouseMoved: (NSEvent *) evt;
- (void) _notifyDelegateMouseUp: (NSEvent *) evt;
@end
@implementation SCWindow
@synthesize delegate;
@synthesize isAwake;
@synthesize nowShowing;
@synthesize mouseIsDragging;
+ window { return [[self alloc] init]; }
+ window: (NSDictionary *) config
{
SCWindow *window = [[self alloc]
initWithContentRect: NSMakeRect( 0.0, 0.0, 540.0, 960.0 )
styleMask: NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask
backing: NSBackingStoreBuffered
defer: NO
];
// configure options
for ( NSString *key in [config allKeys] ) {
if ( [key hasSuffix: @"Page"] ) {
[window->pages setObject: [config objectForKey: key] forKey: [key substringWithRange: NSMakeRange( 0, [key length] - 4 )]];
continue;
}
if ( [key hasSuffix: @"Binding"] ) {
// bindings are configure in awake
[window->_bindings setObject: [config objectForKey: key] forKey: key];
continue;
}
// must be an option...
NSRange range = [key rangeOfString: @"."]; // check for key paths...
if ( range.location == NSNotFound ) {
[window setValue: [config valueForKey: key] forKey: key];
}
else [window setValue: [config valueForKey: key] forKeyPath: key];
}
[window awake];
[window makeKeyAndOrderFront: nil];
return window;
}
- initWithContentRect: (NSRect) contentRect styleMask: (NSUInteger) windowStyle backing: (NSBackingStoreType) bufferingType defer: (BOOL) deferCreation
{
if ( self = [super initWithContentRect: contentRect styleMask: windowStyle backing: bufferingType defer: deferCreation] ) {
isAwake = NO;
pages = [NSMutableDictionary dictionary];
bindings = [NSMutableArray array];
_bindings = [NSMutableDictionary dictionary]; // used to store bindings until the object awakes
}
return self;
}
- (CALayer *) rootLayer
{
return [[self contentView] layer];
}
- (SCView *) rootView
{
return [self rootLayer].delegate;
}
- (void) awake
{
if ( !self.isAwake ) {
NSView *nsview = [self contentView];
CALayer *layer = [CALayer layer];
layer.backgroundColor = BLACKCOLOR;
[layer setValue: [NSNumber numberWithFloat: SCALE] forKeyPath: @"transform.scale.x"];
[layer setValue: [NSNumber numberWithFloat: SCALE] forKeyPath: @"transform.scale.y"];
// order matters here ...
[nsview setLayer: layer];
[nsview setWantsLayer: YES]; // must be called in or after awakeFromNib
// ... add a view to manage the rootLayer of the window ...
[[SCView alloc] initWithLayer: layer];
[self setAcceptsMouseMovedEvents: YES];
[nsview addTrackingArea: [self _contentViewTrackingArea]]; // FIXME: how does this update on resize?
// ... 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];
}
// ... and then load the default page
SCPage *page = ( nowShowing ) ? [pages objectForKey: nowShowing] : nil;
if ( page ) {
_currentPage = page;
[page awake];
[self.rootView append: page];
}
if ( !page && nowShowing ) nowShowing = nil; // don't pretend we're showing a page that doesn't exist
_bindings = nil; // don't need to keep this around anymore
self.isAwake = YES;
}
}
- (void) setNowShowing: (NSString *) outletName
{
if ( ![nowShowing isEqualToString: outletName] ) {
[self willChangeValueForKey: @"nowShowing"];
nowShowing = [outletName copy];
SCPage *page = ( nowShowing ) ? [pages objectForKey: nowShowing] : nil;
if ( page ) {
[page awake];
if ( _currentPage ) [self.rootView replaceChild: _currentPage with: page];
else [self.rootView append: page];
_currentPage = page;
}
if ( !page && nowShowing ) nowShowing = nil; // don't pretend we're showing a page that doesn't exist
[self didChangeValueForKey: @"nowShowing"];
}
}
- (NSTrackingArea *) _contentViewTrackingArea
{
if ( !_contentViewTrackingArea ) {
_contentViewTrackingArea = [[NSTrackingArea alloc] initWithRect: [[self contentView] frame]
options: NSTrackingMouseMoved | NSTrackingActiveInActiveApp
owner: self
userInfo: nil ];
}
return _contentViewTrackingArea;
}
- (void) insert: (SCView *) view before: (SCView *) beforeView
{
[[self rootView] insert: view before: beforeView];
}
- (void) append: (SCView *) view
{
[self insert: view before: nil];
}
// This finds the first view responding to the event.
- (SCView *) firstViewForEvent: (NSEvent *) evt
{
NSPoint thePoint = [evt locationInWindow];
CALayer *rootLayer = self.rootLayer;
CALayer *hitLayer = [rootLayer hitTest: CGPointMake( thePoint.x, thePoint.y )];
if ( hitLayer ) {
SCView *view = hitLayer.delegate;
while ( !view ) {
hitLayer = hitLayer.superlayer;
view = hitLayer.delegate;
}
return view;
}
else return nil;
}
- (void) mouseDown: (NSEvent *) evt
{
mouseIsDragging = YES;
[self _notifyDelegateMouseDown: evt];
// feed the event to the appropriate layer's view
_mouseDownView = [self firstViewForEvent: evt];
if ( _mouseDownView ) [_mouseDownView mouseDown: evt];
}
- (void) mouseMoved: (NSEvent *) evt
{
NSMutableArray *lh = ( _lastHovered ) ? _lastHovered : [NSMutableArray array];
NSMutableArray *nh = [NSMutableArray array];
SCView *view = [self firstViewForEvent: evt];
SCView *rootView = [self rootView];
[self _notifyDelegateMouseMoved: evt];
// work up the view chain. Notify of mouse entered and mouseMoved if implemented.
while ( view && (view != rootView) )
{
if ( [lh containsObject: view] ) {
if ( view.wantsMouseMoved ) [view mouseMoved: evt];
[nh addObject: view];
}
else {
[view mouseEntered: evt];
[nh addObject: view];
}
view = [view nextResponder];
}
// now find those views last hovered over that were no longer found
// in this chain and notify of mouseExited.
for ( SCView *exitedView in lh )
{
if ( ![nh containsObject: exitedView] ) [exitedView mouseExited: evt];
}
_lastHovered = nh;
// also, if a mouseDownView exists, call the mouseDragged action, if it exists.
if ( _mouseDownView ) [_mouseDownView mouseDragged: evt];
}
- (void) mouseDragged: (NSEvent *) evt
{
[self mouseMoved: evt];
}
- (void) mouseEntered: (NSEvent *) evt
{
[self mouseMoved: evt];
}
- (void) mouseExited: (NSEvent *) evt
{
[self mouseMoved: evt];
}
- (void) mouseUp: (NSEvent *) evt
{
[self _notifyDelegateMouseUp: evt];
if ( _mouseDownView ) [_mouseDownView mouseUp: evt];
_mouseDownView = nil;
mouseIsDragging = NO;
}
- (void) setWindowTitle: (NSString *) title
{
LOGO( title );
[self setTitle: title];
}
#pragma mark Core Animation Helpers
- (void) replaceMainLayerWithLayer: (CALayer *) aLayer
{
[self.rootLayer insertSublayer: aLayer below: _mainLayer];
if ( _mainLayer ) [_mainLayer removeFromSuperlayer];
_mainLayer = aLayer;
}
#pragma mark delegate helpers
- (void) _notifyDelegateMouseDown: (NSEvent *) evt
{
if ( !delegate ) return;
if ( [delegate respondsToSelector: @selector(mouseDown:inWindow:)] ) {
[delegate mouseDown: evt inWindow: self];
}
}
- (void) _notifyDelegateMouseMoved: (NSEvent *) evt
{
if ( !delegate ) return;
if ( [delegate respondsToSelector: @selector(wantsMouseMovedEventsInWindow:)] ) {
if ( [delegate wantsMouseMovedEventsInWindow: self] && [delegate respondsToSelector: @selector(mouseMoved:inWindow:)]) {
[delegate mouseMoved: evt inWindow: self];
}
}
}
- (void) _notifyDelegateMouseUp: (NSEvent *) evt
{
if ( !delegate ) return;
if ( [delegate respondsToSelector: @selector(mouseDown:inWindow:)] ) {
[delegate mouseUp: evt inWindow: self];
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment