Source/BoardView.m
changeset 2 7b0441db81e5
child 3 40d225cf9c43
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Source/BoardView.m	Mon Mar 10 17:32:04 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