Added new convenience methods for Game implementations.
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.
29 #import "QuartzUtils.h"
33 @interface BoardView ()
34 - (void) _findDropTarget: (NSPoint)pos;
38 @implementation BoardView
41 @synthesize gameboard=_gameboard;
51 - (void) _removeGameBoard
54 RemoveImmediately(_gameboard);
59 - (void) createGameBoard
61 [self _removeGameBoard];
62 _gameboard = [[CALayer alloc] init];
63 _gameboard.frame = [self gameBoardFrame];
64 _gameboard.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
66 // Tell the game to set up the board:
67 _game.board = _gameboard;
69 [self.layer addSublayer: _gameboard];
76 [_gameboard setValue: [NSNumber numberWithDouble: M_PI]
77 forKeyPath: @"transform.rotation"];
86 - (void) setGame: (Game*)game
90 [self createGameBoard];
94 - (void) startGameNamed: (NSString*)gameClassName
96 Class gameClass = NSClassFromString(gameClassName);
97 Game *game = [[gameClass alloc] init];
108 && _game.currentPlayer.local
109 && _game.currentTurn.status < kTurnComplete;
113 - (CGRect) gameBoardFrame
115 return self.layer.bounds;
119 - (void)resetCursorRects
121 [super resetCursorRects];
122 if( self.canMakeMove )
123 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
127 - (IBAction) enterFullScreen: (id)sender
129 [self _removeGameBoard];
130 if( self.isInFullScreenMode ) {
131 [self exitFullScreenModeWithOptions: nil];
133 [self enterFullScreenMode: self.window.screen
136 [self createGameBoard];
140 - (void)viewWillStartLiveResize
142 [super viewWillStartLiveResize];
143 _oldSize = self.frame.size;
146 - (void)setFrameSize:(NSSize)newSize
148 [super setFrameSize: newSize];
149 if( _oldSize.width > 0.0f ) {
150 CGAffineTransform xform = _gameboard.affineTransform;
151 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
152 _gameboard.affineTransform = xform;
154 [self createGameBoard];
157 - (void)viewDidEndLiveResize
159 [super viewDidEndLiveResize];
160 _oldSize.width = _oldSize.height = 0.0f;
161 [self createGameBoard];
166 #pragma mark KEY EVENTS:
169 - (void) keyDown: (NSEvent*)ev
171 if( self.isInFullScreenMode ) {
172 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
173 [self enterFullScreen: self];
179 #pragma mark HIT-TESTING:
182 /** Converts a point from window coords, to this view's root layer's coords. */
183 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
185 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
186 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
190 // Hit-testing callbacks (to identify which layers caller is interested in):
191 typedef BOOL (*LayerMatchCallback)(CALayer*);
193 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
194 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
195 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
198 /** Locates the layer at a given point in window coords.
199 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
200 If outOffset is provided, the point's position relative to the layer is stored into it. */
201 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
202 forLayerMatching: (LayerMatchCallback)match
203 offset: (CGPoint*)outOffset
205 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
206 CALayer *layer = [_gameboard hitTest: where];
209 CGPoint bitPos = [self.layer convertPoint: layer.position
210 fromLayer: layer.superlayer];
212 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
215 layer = layer.superlayer;
222 #pragma mark MOUSE CLICKS & DRAGS:
225 - (void) mouseDown: (NSEvent*)ev
227 if( ! self.canMakeMove ) {
233 _dragStartPos = ev.locationInWindow;
234 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
235 forLayerMatching: layerIsBit
236 offset: &_dragOffset];
239 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
240 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
241 forLayerMatching: layerIsBitHolder
244 _dragBit = [_game bitToPlaceInHolder: holder];
246 _dragOffset.x = _dragOffset.y = 0;
247 if( _dragBit.superlayer==nil )
248 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
262 _oldHolder = _dragBit.holder;
263 // Ask holder's and game's permission before dragging:
265 _dragBit = [_oldHolder canDragBit: _dragBit];
266 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
267 [_oldHolder cancelDragBit: _dragBit];
278 _oldSuperlayer = _dragBit.superlayer;
279 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
280 _oldPos = _dragBit.position;
281 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
282 _dragBit.pickedUp = YES;
283 [[NSCursor closedHandCursor] push];
287 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
289 [self _findDropTarget: _dragStartPos];
294 - (void) mouseDragged: (NSEvent*)ev
297 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
298 NSPoint pos = ev.locationInWindow;
299 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
302 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
303 CGPoint where = [self _convertPointFromWindowToLayer: pos];
304 where.x += _dragOffset.x;
305 where.y += _dragOffset.y;
307 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
309 [CATransaction flush];
310 [CATransaction begin];
311 [CATransaction setValue:(id)kCFBooleanTrue
312 forKey:kCATransactionDisableActions];
313 _dragBit.position = newPos;
314 [CATransaction commit];
316 // Find what it's over:
317 [self _findDropTarget: pos];
322 - (void) _findDropTarget: (NSPoint)locationInWindow
324 locationInWindow.x += _dragOffset.x;
325 locationInWindow.y += _dragOffset.y;
326 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
327 forLayerMatching: layerIsBitHolder
329 if( target == _oldHolder )
331 if( target != _dropTarget ) {
332 [_dropTarget willNotDropBit: _dragBit];
333 _dropTarget.highlighted = NO;
337 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
338 fromLayer: _dragBit.superlayer];
339 if( [target canDropBit: _dragBit atPoint: targetPos]
340 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
341 _dropTarget = target;
342 _dropTarget.highlighted = YES;
348 - (void) mouseUp: (NSEvent*)ev
352 // Update the drag tracking to the final mouse position:
353 [self mouseDragged: ev];
354 _dropTarget.highlighted = NO;
355 _dragBit.pickedUp = NO;
357 // Is the move legal?
358 if( _dropTarget && [_dropTarget dropBit: _dragBit
359 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
360 fromLayer: _dragBit.superlayer]] ) {
361 // Yes, notify the interested parties:
362 [_oldHolder draggedBit: _dragBit to: _dropTarget];
363 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
366 [_dropTarget willNotDropBit: _dragBit];
367 if( _oldSuperlayer ) {
368 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
369 _dragBit.position = _oldPos;
370 [_oldHolder cancelDragBit: _dragBit];
372 [_dragBit removeFromSuperlayer];
376 // Just a click, without a drag:
377 _dropTarget.highlighted = NO;
378 _dragBit.pickedUp = NO;
379 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
380 [_oldHolder cancelDragBit: _dragBit];
381 if( ! [_game clickedBit: _dragBit] )
393 #pragma mark INCOMING DRAGS:
396 // subroutine to call the target
397 static int tell( id target, SEL selector, id arg, int defaultValue )
399 if( target && [target respondsToSelector: selector] )
400 return (ssize_t) [target performSelector: selector withObject: arg];
406 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
408 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
409 forLayerMatching: layerIsDropTarget
411 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
415 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
417 CALayer *target = [self hitTestPoint: [sender draggingLocation]
418 forLayerMatching: layerIsDropTarget
420 if( target == _viewDropTarget ) {
421 if( _viewDropTarget )
422 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
424 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
425 _viewDropTarget = target;
426 if( _viewDropTarget )
427 _viewDropOp = [_viewDropTarget draggingEntered: sender];
429 _viewDropOp = NSDragOperationNone;
434 - (BOOL)wantsPeriodicDraggingUpdates
436 return (_viewDropTarget!=nil);
439 - (void)draggingExited:(id <NSDraggingInfo>)sender
441 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
442 _viewDropTarget = nil;
445 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
447 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
450 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
452 return [_viewDropTarget performDragOperation: sender];
455 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
457 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
460 - (void)draggingEnded:(id <NSDraggingInfo>)sender
462 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);