Fixed: An exception in the Go game if you mouse down on the board but then drag to the captured-pieces area and release the mouse.
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.
5 Redistribution and use in source and binary forms, with or without modification, are permitted
6 provided that the following conditions are met:
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.
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.
27 #import "QuartzUtils.h"
31 @interface BoardView ()
32 - (void) _findDropTarget: (NSPoint)pos;
36 @implementation BoardView
39 @synthesize game=_game, gameboard=_gameboard;
49 - (void) startGameNamed: (NSString*)gameClassName
52 [_gameboard removeFromSuperlayer];
55 _gameboard = [[CALayer alloc] init];
56 _gameboard.frame = [self gameBoardFrame];
57 _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
58 [self.layer addSublayer: _gameboard];
61 Class gameClass = NSClassFromString(gameClassName);
62 setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
66 - (CGRect) gameBoardFrame
68 return self.layer.bounds;
72 - (void)resetCursorRects
74 [super resetCursorRects];
75 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
79 - (IBAction) enterFullScreen: (id)sender
81 if( self.isInFullScreenMode ) {
82 [self exitFullScreenModeWithOptions: nil];
84 [self enterFullScreenMode: self.window.screen
91 #pragma mark KEY EVENTS:
94 - (void) keyDown: (NSEvent*)ev
96 if( self.isInFullScreenMode ) {
97 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
98 [self enterFullScreen: self];
104 #pragma mark HIT-TESTING:
107 /** Converts a point from window coords, to this view's root layer's coords. */
108 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
110 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
111 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
115 // Hit-testing callbacks (to identify which layers caller is interested in):
116 typedef BOOL (*LayerMatchCallback)(CALayer*);
118 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
119 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
120 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
123 /** Locates the layer at a given point in window coords.
124 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
125 If outOffset is provided, the point's position relative to the layer is stored into it. */
126 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
127 forLayerMatching: (LayerMatchCallback)match
128 offset: (CGPoint*)outOffset
130 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
131 CALayer *layer = [_gameboard hitTest: where];
134 CGPoint bitPos = [self.layer convertPoint: layer.position
135 fromLayer: layer.superlayer];
137 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
140 layer = layer.superlayer;
147 #pragma mark MOUSE CLICKS & DRAGS:
150 - (void) mouseDown: (NSEvent*)ev
153 _dragStartPos = ev.locationInWindow;
154 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
155 forLayerMatching: layerIsBit
156 offset: &_dragOffset];
159 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
160 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
161 forLayerMatching: layerIsBitHolder
164 _dragBit = [_game bitToPlaceInHolder: holder];
166 _dragOffset.x = _dragOffset.y = 0;
167 if( _dragBit.superlayer==nil )
168 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
182 _oldHolder = _dragBit.holder;
183 // Ask holder's and game's permission before dragging:
185 _dragBit = [_oldHolder canDragBit: _dragBit];
186 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
187 [_oldHolder cancelDragBit: _dragBit];
198 _oldSuperlayer = _dragBit.superlayer;
199 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
200 _oldPos = _dragBit.position;
201 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
202 _dragBit.pickedUp = YES;
203 [[NSCursor closedHandCursor] push];
207 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
209 [self _findDropTarget: _dragStartPos];
214 - (void) mouseDragged: (NSEvent*)ev
217 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
218 NSPoint pos = ev.locationInWindow;
219 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
222 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
223 CGPoint where = [self _convertPointFromWindowToLayer: pos];
224 where.x += _dragOffset.x;
225 where.y += _dragOffset.y;
227 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
229 [CATransaction flush];
230 [CATransaction begin];
231 [CATransaction setValue:(id)kCFBooleanTrue
232 forKey:kCATransactionDisableActions];
233 _dragBit.position = newPos;
234 [CATransaction commit];
236 // Find what it's over:
237 [self _findDropTarget: pos];
242 - (void) _findDropTarget: (NSPoint)locationInWindow
244 locationInWindow.x += _dragOffset.x;
245 locationInWindow.y += _dragOffset.y;
246 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
247 forLayerMatching: layerIsBitHolder
249 if( target == _oldHolder )
251 if( target != _dropTarget ) {
252 [_dropTarget willNotDropBit: _dragBit];
253 _dropTarget.highlighted = NO;
257 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
258 fromLayer: _dragBit.superlayer];
259 if( [target canDropBit: _dragBit atPoint: targetPos]
260 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
261 _dropTarget = target;
262 _dropTarget.highlighted = YES;
268 - (void) mouseUp: (NSEvent*)ev
272 // Update the drag tracking to the final mouse position:
273 [self mouseDragged: ev];
274 _dropTarget.highlighted = NO;
275 _dragBit.pickedUp = NO;
277 // Is the move legal?
278 if( _dropTarget && [_dropTarget dropBit: _dragBit
279 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
280 fromLayer: _dragBit.superlayer]] ) {
281 // Yes, notify the interested parties:
282 [_oldHolder draggedBit: _dragBit to: _dropTarget];
283 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
286 [_dropTarget willNotDropBit: _dragBit];
287 if( _oldSuperlayer ) {
288 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
289 _dragBit.position = _oldPos;
290 [_oldHolder cancelDragBit: _dragBit];
292 [_dragBit removeFromSuperlayer];
296 // Just a click, without a drag:
297 _dropTarget.highlighted = NO;
298 _dragBit.pickedUp = NO;
299 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
300 [_oldHolder cancelDragBit: _dragBit];
301 if( ! [_game clickedBit: _dragBit] )
312 #pragma mark INCOMING DRAGS:
315 // subroutine to call the target
316 static int tell( id target, SEL selector, id arg, int defaultValue )
318 if( target && [target respondsToSelector: selector] )
319 return (ssize_t) [target performSelector: selector withObject: arg];
325 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
327 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
328 forLayerMatching: layerIsDropTarget
330 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
334 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
336 CALayer *target = [self hitTestPoint: [sender draggingLocation]
337 forLayerMatching: layerIsDropTarget
339 if( target == _viewDropTarget ) {
340 if( _viewDropTarget )
341 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
343 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
344 _viewDropTarget = target;
345 if( _viewDropTarget )
346 _viewDropOp = [_viewDropTarget draggingEntered: sender];
348 _viewDropOp = NSDragOperationNone;
353 - (BOOL)wantsPeriodicDraggingUpdates
355 return (_viewDropTarget!=nil);
358 - (void)draggingExited:(id <NSDraggingInfo>)sender
360 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
361 _viewDropTarget = nil;
364 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
366 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
369 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
371 return [_viewDropTarget performDragOperation: sender];
374 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
376 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
379 - (void)draggingEnded:(id <NSDraggingInfo>)sender
381 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);