1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/Source/BoardView.m Mon Mar 10 17:30:57 2008 -0700
1.3 @@ -0,0 +1,329 @@
1.4 +/* This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
1.5 + http://developer.apple.com/samplecode/GeekGameBoard/
1.6 + Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
1.7 +
1.8 + Redistribution and use in source and binary forms, with or without modification, are permitted
1.9 + provided that the following conditions are met:
1.10 +
1.11 + * Redistributions of source code must retain the above copyright notice, this list of conditions
1.12 + and the following disclaimer.
1.13 + * Redistributions in binary form must reproduce the above copyright notice, this list of
1.14 + conditions and the following disclaimer in the documentation and/or other materials provided
1.15 + with the distribution.
1.16 +
1.17 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
1.18 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
1.19 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
1.20 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1.21 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
1.22 + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1.23 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
1.24 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.25 +*/
1.26 +#import "BoardView.h"
1.27 +#import "Bit.h"
1.28 +#import "BitHolder.h"
1.29 +#import "Game.h"
1.30 +#import "QuartzUtils.h"
1.31 +#import "GGBUtils.h"
1.32 +
1.33 +
1.34 +@implementation BoardView
1.35 +
1.36 +
1.37 +@synthesize game=_game, gameboard=_gameboard;
1.38 +
1.39 +
1.40 +- (void) dealloc
1.41 +{
1.42 + [_game release];
1.43 + [super dealloc];
1.44 +}
1.45 +
1.46 +
1.47 +- (void) startGameNamed: (NSString*)gameClassName
1.48 +{
1.49 + if( _gameboard ) {
1.50 + [_gameboard removeFromSuperlayer];
1.51 + _gameboard = nil;
1.52 + }
1.53 + _gameboard = [[CALayer alloc] init];
1.54 + _gameboard.frame = [self gameBoardFrame];
1.55 + _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
1.56 + [self.layer addSublayer: _gameboard];
1.57 + [_gameboard release];
1.58 +
1.59 + Class gameClass = NSClassFromString(gameClassName);
1.60 + setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
1.61 +}
1.62 +
1.63 +
1.64 +- (CGRect) gameBoardFrame
1.65 +{
1.66 + return self.layer.bounds;
1.67 +}
1.68 +
1.69 +
1.70 +- (void)resetCursorRects
1.71 +{
1.72 + [super resetCursorRects];
1.73 + [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
1.74 +}
1.75 +
1.76 +
1.77 +- (IBAction) enterFullScreen: (id)sender
1.78 +{
1.79 + if( self.isInFullScreenMode ) {
1.80 + [self exitFullScreenModeWithOptions: nil];
1.81 + } else {
1.82 + [self enterFullScreenMode: self.window.screen
1.83 + withOptions: nil];
1.84 + }
1.85 +}
1.86 +
1.87 +
1.88 +#pragma mark -
1.89 +#pragma mark KEY EVENTS:
1.90 +
1.91 +
1.92 +- (void) keyDown: (NSEvent*)ev
1.93 +{
1.94 + if( self.isInFullScreenMode ) {
1.95 + if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
1.96 + [self enterFullScreen: self];
1.97 + }
1.98 +}
1.99 +
1.100 +
1.101 +#pragma mark -
1.102 +#pragma mark HIT-TESTING:
1.103 +
1.104 +
1.105 +// Hit-testing callbacks (to identify which layers caller is interested in):
1.106 +typedef BOOL (*LayerMatchCallback)(CALayer*);
1.107 +
1.108 +static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
1.109 +static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
1.110 +static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
1.111 +
1.112 +
1.113 +/** Locates the layer at a given point in window coords.
1.114 + If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
1.115 + If outOffset is provided, the point's position relative to the layer is stored into it. */
1.116 +- (CALayer*) hitTestPoint: (NSPoint)locationInWindow
1.117 + forLayerMatching: (LayerMatchCallback)match
1.118 + offset: (CGPoint*)outOffset
1.119 +{
1.120 + CGPoint where = NSPointToCGPoint([self convertPoint: locationInWindow fromView: nil]);
1.121 + where = [_gameboard convertPoint: where fromLayer: self.layer];
1.122 + CALayer *layer = [_gameboard hitTest: where];
1.123 + while( layer ) {
1.124 + if( match(layer) ) {
1.125 + CGPoint bitPos = [self.layer convertPoint: layer.position
1.126 + fromLayer: layer.superlayer];
1.127 + if( outOffset )
1.128 + *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
1.129 + return layer;
1.130 + } else
1.131 + layer = layer.superlayer;
1.132 + }
1.133 + return nil;
1.134 +}
1.135 +
1.136 +
1.137 +#pragma mark -
1.138 +#pragma mark MOUSE CLICKS & DRAGS:
1.139 +
1.140 +
1.141 +- (void) mouseDown: (NSEvent*)ev
1.142 +{
1.143 + _dragStartPos = ev.locationInWindow;
1.144 + _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
1.145 + forLayerMatching: layerIsBit
1.146 + offset: &_dragOffset];
1.147 + if( _dragBit ) {
1.148 + _dragMoved = NO;
1.149 + _dropTarget = nil;
1.150 + _oldHolder = _dragBit.holder;
1.151 + // Ask holder's and game's permission before dragging:
1.152 + if( _oldHolder )
1.153 + _dragBit = [_oldHolder canDragBit: _dragBit];
1.154 + if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
1.155 + [_oldHolder cancelDragBit: _dragBit];
1.156 + _dragBit = nil;
1.157 + }
1.158 + if( ! _dragBit ) {
1.159 + _oldHolder = nil;
1.160 + NSBeep();
1.161 + return;
1.162 + }
1.163 + // Start dragging:
1.164 + _oldSuperlayer = _dragBit.superlayer;
1.165 + _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
1.166 + _oldPos = _dragBit.position;
1.167 + ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
1.168 + _dragBit.pickedUp = YES;
1.169 + [[NSCursor closedHandCursor] push];
1.170 + } else
1.171 + NSBeep();
1.172 +}
1.173 +
1.174 +- (void) mouseDragged: (NSEvent*)ev
1.175 +{
1.176 + if( _dragBit ) {
1.177 + // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
1.178 + NSPoint pos = ev.locationInWindow;
1.179 + if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
1.180 + _dragMoved = YES;
1.181 +
1.182 + // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
1.183 + NSPoint where = [self convertPoint: pos fromView: nil];
1.184 + where.x += _dragOffset.x;
1.185 + where.y += _dragOffset.y;
1.186 +
1.187 + CGPoint newPos = [_dragBit.superlayer convertPoint: NSPointToCGPoint(where)
1.188 + fromLayer: self.layer];
1.189 +
1.190 + [CATransaction flush];
1.191 + [CATransaction begin];
1.192 + [CATransaction setValue:(id)kCFBooleanTrue
1.193 + forKey:kCATransactionDisableActions];
1.194 + _dragBit.position = newPos;
1.195 + [CATransaction commit];
1.196 +
1.197 + // Find what it's over:
1.198 + id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: where
1.199 + forLayerMatching: layerIsBitHolder
1.200 + offset: NULL];
1.201 + if( target == _oldHolder )
1.202 + target = nil;
1.203 + if( target != _dropTarget ) {
1.204 + [_dropTarget willNotDropBit: _dragBit];
1.205 + _dropTarget.highlighted = NO;
1.206 + _dropTarget = nil;
1.207 + }
1.208 + if( target ) {
1.209 + CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
1.210 + fromLayer: _dragBit.superlayer];
1.211 + if( [target canDropBit: _dragBit atPoint: targetPos]
1.212 + && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
1.213 + _dropTarget = target;
1.214 + _dropTarget.highlighted = YES;
1.215 + }
1.216 + }
1.217 + }
1.218 +}
1.219 +
1.220 +- (void) mouseUp: (NSEvent*)ev
1.221 +{
1.222 + if( _dragBit ) {
1.223 + if( _dragMoved ) {
1.224 + // Update the drag tracking to the final mouse position:
1.225 + [self mouseDragged: ev];
1.226 + _dropTarget.highlighted = NO;
1.227 + _dragBit.pickedUp = NO;
1.228 +
1.229 + // Is the move legal?
1.230 + if( _dropTarget && [_dropTarget dropBit: _dragBit
1.231 + atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
1.232 + fromLayer: _dragBit.superlayer]] ) {
1.233 + // Yes, notify the interested parties:
1.234 + [_oldHolder draggedBit: _dragBit to: _dropTarget];
1.235 + [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
1.236 + } else {
1.237 + // Nope, cancel:
1.238 + [_dropTarget willNotDropBit: _dragBit];
1.239 + ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
1.240 + _dragBit.position = _oldPos;
1.241 + [_oldHolder cancelDragBit: _dragBit];
1.242 + }
1.243 + } else {
1.244 + // Just a click, without a drag:
1.245 + _dropTarget.highlighted = NO;
1.246 + _dragBit.pickedUp = NO;
1.247 + ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
1.248 + [_oldHolder cancelDragBit: _dragBit];
1.249 + if( ! [_game clickedBit: _dragBit] )
1.250 + NSBeep();
1.251 + }
1.252 + _dropTarget = nil;
1.253 + _dragBit = nil;
1.254 + [NSCursor pop];
1.255 + }
1.256 +}
1.257 +
1.258 +
1.259 +#pragma mark -
1.260 +#pragma mark INCOMING DRAGS:
1.261 +
1.262 +
1.263 +// subroutine to call the target
1.264 +static int tell( id target, SEL selector, id arg, int defaultValue )
1.265 +{
1.266 + if( target && [target respondsToSelector: selector] )
1.267 + return (ssize_t) [target performSelector: selector withObject: arg];
1.268 + else
1.269 + return defaultValue;
1.270 +}
1.271 +
1.272 +
1.273 +- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
1.274 +{
1.275 + _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
1.276 + forLayerMatching: layerIsDropTarget
1.277 + offset: NULL];
1.278 + _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
1.279 + return _viewDropOp;
1.280 +}
1.281 +
1.282 +- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
1.283 +{
1.284 + CALayer *target = [self hitTestPoint: [sender draggingLocation]
1.285 + forLayerMatching: layerIsDropTarget
1.286 + offset: NULL];
1.287 + if( target == _viewDropTarget ) {
1.288 + if( _viewDropTarget )
1.289 + _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
1.290 + } else {
1.291 + tell(_viewDropTarget,@selector(draggingExited:),sender,0);
1.292 + _viewDropTarget = target;
1.293 + if( _viewDropTarget )
1.294 + _viewDropOp = [_viewDropTarget draggingEntered: sender];
1.295 + else
1.296 + _viewDropOp = NSDragOperationNone;
1.297 + }
1.298 + return _viewDropOp;
1.299 +}
1.300 +
1.301 +- (BOOL)wantsPeriodicDraggingUpdates
1.302 +{
1.303 + return (_viewDropTarget!=nil);
1.304 +}
1.305 +
1.306 +- (void)draggingExited:(id <NSDraggingInfo>)sender
1.307 +{
1.308 + tell(_viewDropTarget,@selector(draggingExited:),sender,0);
1.309 + _viewDropTarget = nil;
1.310 +}
1.311 +
1.312 +- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
1.313 +{
1.314 + return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
1.315 +}
1.316 +
1.317 +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
1.318 +{
1.319 + return [_viewDropTarget performDragOperation: sender];
1.320 +}
1.321 +
1.322 +- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
1.323 +{
1.324 + tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
1.325 +}
1.326 +
1.327 +- (void)draggingEnded:(id <NSDraggingInfo>)sender
1.328 +{
1.329 + tell(_viewDropTarget,@selector(draggingEnded:),sender,0);
1.330 +}
1.331 +
1.332 +@end