Source/BoardView.m
author Jens Alfke <jens@mooseyard.com>
Mon Mar 10 17:32:04 2008 -0700 (2008-03-10)
changeset 2 7b0441db81e5
child 3 40d225cf9c43
permissions -rw-r--r--
Oops, needed to fix an #include
     1 /*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
     2     http://developer.apple.com/samplecode/GeekGameBoard/
     3     Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     8     * Redistributions of source code must retain the above copyright notice, this list of conditions
     9       and the following disclaimer.
    10     * Redistributions in binary form must reproduce the above copyright notice, this list of
    11       conditions and the following disclaimer in the documentation and/or other materials provided
    12       with the distribution.
    13 
    14     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    15     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    16     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    17     BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    18     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    19     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    20     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    21     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    22 */
    23 #import "BoardView.h"
    24 #import "Bit.h"
    25 #import "BitHolder.h"
    26 #import "Game.h"
    27 #import "QuartzUtils.h"
    28 #import "GGBUtils.h"
    29 
    30 
    31 @implementation BoardView
    32 
    33 
    34 @synthesize game=_game, gameboard=_gameboard;
    35 
    36 
    37 - (void) dealloc
    38 {
    39     [_game release];
    40     [super dealloc];
    41 }
    42 
    43 
    44 - (void) startGameNamed: (NSString*)gameClassName
    45 {
    46     if( _gameboard ) {
    47         [_gameboard removeFromSuperlayer];
    48         _gameboard = nil;
    49     }
    50     _gameboard = [[CALayer alloc] init];
    51     _gameboard.frame = [self gameBoardFrame];
    52     _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
    53     [self.layer addSublayer: _gameboard];
    54     [_gameboard release];
    55     
    56     Class gameClass = NSClassFromString(gameClassName);
    57     setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
    58 }
    59 
    60 
    61 - (CGRect) gameBoardFrame
    62 {
    63     return self.layer.bounds;
    64 }
    65 
    66 
    67 - (void)resetCursorRects
    68 {
    69     [super resetCursorRects];
    70     [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
    71 }
    72 
    73 
    74 - (IBAction) enterFullScreen: (id)sender
    75 {
    76     if( self.isInFullScreenMode ) {
    77         [self exitFullScreenModeWithOptions: nil];
    78     } else {
    79         [self enterFullScreenMode: self.window.screen 
    80                       withOptions: nil];
    81     }
    82 }
    83 
    84 
    85 #pragma mark -
    86 #pragma mark KEY EVENTS:
    87 
    88 
    89 - (void) keyDown: (NSEvent*)ev
    90 {
    91     if( self.isInFullScreenMode ) {
    92         if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] )       // Esc key
    93             [self enterFullScreen: self];
    94     }
    95 }
    96 
    97 
    98 #pragma mark -
    99 #pragma mark HIT-TESTING:
   100 
   101 
   102 // Hit-testing callbacks (to identify which layers caller is interested in):
   103 typedef BOOL (*LayerMatchCallback)(CALayer*);
   104 
   105 static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
   106 static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
   107 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
   108 
   109 
   110 /** Locates the layer at a given point in window coords.
   111     If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
   112     If outOffset is provided, the point's position relative to the layer is stored into it. */
   113 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
   114          forLayerMatching: (LayerMatchCallback)match
   115                    offset: (CGPoint*)outOffset
   116 {
   117     CGPoint where = NSPointToCGPoint([self convertPoint: locationInWindow fromView: nil]);
   118     where = [_gameboard convertPoint: where fromLayer: self.layer];
   119     CALayer *layer = [_gameboard hitTest: where];
   120     while( layer ) {
   121         if( match(layer) ) {
   122             CGPoint bitPos = [self.layer convertPoint: layer.position 
   123                               fromLayer: layer.superlayer];
   124             if( outOffset )
   125                 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
   126             return layer;
   127         } else
   128             layer = layer.superlayer;
   129     }
   130     return nil;
   131 }
   132 
   133 
   134 #pragma mark -
   135 #pragma mark MOUSE CLICKS & DRAGS:
   136 
   137 
   138 - (void) mouseDown: (NSEvent*)ev
   139 {
   140     _dragStartPos = ev.locationInWindow;
   141     _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
   142                         forLayerMatching: layerIsBit 
   143                                   offset: &_dragOffset];
   144     if( _dragBit ) {
   145         _dragMoved = NO;
   146         _dropTarget = nil;
   147         _oldHolder = _dragBit.holder;
   148         // Ask holder's and game's permission before dragging:
   149         if( _oldHolder )
   150             _dragBit = [_oldHolder canDragBit: _dragBit];
   151         if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
   152             [_oldHolder cancelDragBit: _dragBit];
   153             _dragBit = nil;
   154         }
   155         if( ! _dragBit ) {
   156             _oldHolder = nil;
   157             NSBeep();
   158             return;
   159         }
   160         // Start dragging:
   161         _oldSuperlayer = _dragBit.superlayer;
   162         _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
   163         _oldPos = _dragBit.position;
   164         ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
   165         _dragBit.pickedUp = YES;
   166         [[NSCursor closedHandCursor] push];
   167     } else
   168         NSBeep();
   169 }
   170 
   171 - (void) mouseDragged: (NSEvent*)ev
   172 {
   173     if( _dragBit ) {
   174         // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
   175         NSPoint pos = ev.locationInWindow;
   176         if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
   177             _dragMoved = YES;
   178         
   179         // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
   180         NSPoint where = [self convertPoint: pos fromView: nil];
   181         where.x += _dragOffset.x;
   182         where.y += _dragOffset.y;
   183         
   184         CGPoint newPos = [_dragBit.superlayer convertPoint: NSPointToCGPoint(where) 
   185                                                  fromLayer: self.layer];
   186 
   187         [CATransaction flush];
   188         [CATransaction begin];
   189         [CATransaction setValue:(id)kCFBooleanTrue
   190                          forKey:kCATransactionDisableActions];
   191         _dragBit.position = newPos;
   192         [CATransaction commit];
   193 
   194         // Find what it's over:
   195         id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: where
   196                                                  forLayerMatching: layerIsBitHolder
   197                                                            offset: NULL];
   198         if( target == _oldHolder )
   199             target = nil;
   200         if( target != _dropTarget ) {
   201             [_dropTarget willNotDropBit: _dragBit];
   202             _dropTarget.highlighted = NO;
   203             _dropTarget = nil;
   204         }
   205         if( target ) {
   206             CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
   207                                                      fromLayer: _dragBit.superlayer];
   208             if( [target canDropBit: _dragBit atPoint: targetPos]
   209                && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
   210                 _dropTarget = target;
   211                 _dropTarget.highlighted = YES;
   212             }
   213         }
   214     }
   215 }
   216 
   217 - (void) mouseUp: (NSEvent*)ev
   218 {
   219     if( _dragBit ) {
   220         if( _dragMoved ) {
   221             // Update the drag tracking to the final mouse position:
   222             [self mouseDragged: ev];
   223             _dropTarget.highlighted = NO;
   224             _dragBit.pickedUp = NO;
   225 
   226             // Is the move legal?
   227             if( _dropTarget && [_dropTarget dropBit: _dragBit
   228                                             atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
   229                                                                             fromLayer: _dragBit.superlayer]] ) {
   230                 // Yes, notify the interested parties:
   231                 [_oldHolder draggedBit: _dragBit to: _dropTarget];
   232                 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
   233             } else {
   234                 // Nope, cancel:
   235                 [_dropTarget willNotDropBit: _dragBit];
   236                 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   237                 _dragBit.position = _oldPos;
   238                 [_oldHolder cancelDragBit: _dragBit];
   239             }
   240         } else {
   241             // Just a click, without a drag:
   242             _dropTarget.highlighted = NO;
   243             _dragBit.pickedUp = NO;
   244             ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   245             [_oldHolder cancelDragBit: _dragBit];
   246             if( ! [_game clickedBit: _dragBit] )
   247                 NSBeep();
   248         }
   249         _dropTarget = nil;
   250         _dragBit = nil;
   251         [NSCursor pop];
   252     }
   253 }
   254 
   255 
   256 #pragma mark -
   257 #pragma mark INCOMING DRAGS:
   258 
   259 
   260 // subroutine to call the target
   261 static int tell( id target, SEL selector, id arg, int defaultValue )
   262 {
   263     if( target && [target respondsToSelector: selector] )
   264         return (ssize_t) [target performSelector: selector withObject: arg];
   265     else
   266         return defaultValue;
   267 }
   268 
   269 
   270 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
   271 {
   272     _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
   273                         forLayerMatching: layerIsDropTarget
   274                                   offset: NULL];
   275     _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
   276     return _viewDropOp;
   277 }
   278 
   279 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
   280 {
   281     CALayer *target = [self hitTestPoint: [sender draggingLocation]
   282                         forLayerMatching: layerIsDropTarget 
   283                                   offset: NULL];
   284     if( target == _viewDropTarget ) {
   285         if( _viewDropTarget )
   286             _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
   287     } else {
   288         tell(_viewDropTarget,@selector(draggingExited:),sender,0);
   289         _viewDropTarget = target;
   290         if( _viewDropTarget )
   291             _viewDropOp = [_viewDropTarget draggingEntered: sender];
   292         else
   293             _viewDropOp = NSDragOperationNone;
   294     }
   295     return _viewDropOp;
   296 }
   297 
   298 - (BOOL)wantsPeriodicDraggingUpdates
   299 {
   300     return (_viewDropTarget!=nil);
   301 }
   302 
   303 - (void)draggingExited:(id <NSDraggingInfo>)sender
   304 {
   305     tell(_viewDropTarget,@selector(draggingExited:),sender,0);
   306     _viewDropTarget = nil;
   307 }
   308 
   309 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
   310 {
   311     return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
   312 }
   313 
   314 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
   315 {
   316     return [_viewDropTarget performDragOperation: sender];
   317 }
   318 
   319 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
   320 {
   321     tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
   322 }
   323 
   324 - (void)draggingEnded:(id <NSDraggingInfo>)sender
   325 {
   326     tell(_viewDropTarget,@selector(draggingEnded:),sender,0);
   327 }
   328 
   329 @end