Minor fixes.
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
91 [self createGameBoard];
95 - (void) startGameNamed: (NSString*)gameClassName
97 Class gameClass = NSClassFromString(gameClassName);
98 Game *game = [[gameClass alloc] init];
109 && _game.currentPlayer.local
110 && _game.currentTurn.status < kTurnComplete;
114 - (CGRect) gameBoardFrame
116 return self.layer.bounds;
120 - (void)resetCursorRects
122 [super resetCursorRects];
123 if( self.canMakeMove )
124 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
128 - (IBAction) enterFullScreen: (id)sender
130 [self _removeGameBoard];
131 if( self.isInFullScreenMode ) {
132 [self exitFullScreenModeWithOptions: nil];
134 [self enterFullScreenMode: self.window.screen
137 [self createGameBoard];
141 - (void)viewWillStartLiveResize
143 [super viewWillStartLiveResize];
144 _oldSize = self.frame.size;
147 - (void)setFrameSize:(NSSize)newSize
149 [super setFrameSize: newSize];
150 if( _oldSize.width > 0.0f ) {
151 CGAffineTransform xform = _gameboard.affineTransform;
152 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
153 _gameboard.affineTransform = xform;
155 [self createGameBoard];
158 - (void)viewDidEndLiveResize
160 [super viewDidEndLiveResize];
161 _oldSize.width = _oldSize.height = 0.0f;
162 [self createGameBoard];
167 #pragma mark KEY EVENTS:
170 - (void) keyDown: (NSEvent*)ev
172 if( self.isInFullScreenMode ) {
173 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
174 [self enterFullScreen: self];
180 #pragma mark HIT-TESTING:
183 /** Converts a point from window coords, to this view's root layer's coords. */
184 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
186 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
187 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
191 // Hit-testing callbacks (to identify which layers caller is interested in):
192 typedef BOOL (*LayerMatchCallback)(CALayer*);
194 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
195 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
196 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
199 /** Locates the layer at a given point in window coords.
200 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
201 If outOffset is provided, the point's position relative to the layer is stored into it. */
202 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
203 forLayerMatching: (LayerMatchCallback)match
204 offset: (CGPoint*)outOffset
206 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
207 CALayer *layer = [_gameboard hitTest: where];
210 CGPoint bitPos = [self.layer convertPoint: layer.position
211 fromLayer: layer.superlayer];
213 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
216 layer = layer.superlayer;
223 #pragma mark MOUSE CLICKS & DRAGS:
226 - (void) mouseDown: (NSEvent*)ev
228 if( ! self.canMakeMove ) {
234 _dragStartPos = ev.locationInWindow;
235 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
236 forLayerMatching: layerIsBit
237 offset: &_dragOffset];
240 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
241 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
242 forLayerMatching: layerIsBitHolder
245 _dragBit = [_game bitToPlaceInHolder: holder];
247 _dragOffset.x = _dragOffset.y = 0;
248 if( _dragBit.superlayer==nil )
249 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
263 _oldHolder = _dragBit.holder;
264 // Ask holder's and game's permission before dragging:
266 _dragBit = [_oldHolder canDragBit: _dragBit];
267 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
268 [_oldHolder cancelDragBit: _dragBit];
279 _oldSuperlayer = _dragBit.superlayer;
280 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
281 _oldPos = _dragBit.position;
282 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
283 _dragBit.pickedUp = YES;
284 [[NSCursor closedHandCursor] push];
288 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
290 [self _findDropTarget: _dragStartPos];
295 - (void) mouseDragged: (NSEvent*)ev
298 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
299 NSPoint pos = ev.locationInWindow;
300 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
303 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
304 CGPoint where = [self _convertPointFromWindowToLayer: pos];
305 where.x += _dragOffset.x;
306 where.y += _dragOffset.y;
308 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
310 [CATransaction flush];
311 [CATransaction begin];
312 [CATransaction setValue:(id)kCFBooleanTrue
313 forKey:kCATransactionDisableActions];
314 _dragBit.position = newPos;
315 [CATransaction commit];
317 // Find what it's over:
318 [self _findDropTarget: pos];
323 - (void) _findDropTarget: (NSPoint)locationInWindow
325 locationInWindow.x += _dragOffset.x;
326 locationInWindow.y += _dragOffset.y;
327 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
328 forLayerMatching: layerIsBitHolder
330 if( target == _oldHolder )
332 if( target != _dropTarget ) {
333 [_dropTarget willNotDropBit: _dragBit];
334 _dropTarget.highlighted = NO;
338 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
339 fromLayer: _dragBit.superlayer];
340 if( [target canDropBit: _dragBit atPoint: targetPos]
341 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
342 _dropTarget = target;
343 _dropTarget.highlighted = YES;
349 - (void) mouseUp: (NSEvent*)ev
353 // Update the drag tracking to the final mouse position:
354 [self mouseDragged: ev];
355 _dropTarget.highlighted = NO;
356 _dragBit.pickedUp = NO;
358 // Is the move legal?
359 if( _dropTarget && [_dropTarget dropBit: _dragBit
360 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
361 fromLayer: _dragBit.superlayer]] ) {
362 // Yes, notify the interested parties:
363 [_oldHolder draggedBit: _dragBit to: _dropTarget];
364 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
367 [_dropTarget willNotDropBit: _dragBit];
368 if( _oldSuperlayer ) {
369 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
370 _dragBit.position = _oldPos;
371 [_oldHolder cancelDragBit: _dragBit];
373 [_dragBit removeFromSuperlayer];
377 // Just a click, without a drag:
378 _dropTarget.highlighted = NO;
379 _dragBit.pickedUp = NO;
380 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
381 [_oldHolder cancelDragBit: _dragBit];
382 if( ! [_game clickedBit: _dragBit] )
394 #pragma mark INCOMING DRAGS:
397 // subroutine to call the target
398 static int tell( id target, SEL selector, id arg, int defaultValue )
400 if( target && [target respondsToSelector: selector] )
401 return (ssize_t) [target performSelector: selector withObject: arg];
407 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
409 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
410 forLayerMatching: layerIsDropTarget
412 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
416 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
418 CALayer *target = [self hitTestPoint: [sender draggingLocation]
419 forLayerMatching: layerIsDropTarget
421 if( target == _viewDropTarget ) {
422 if( _viewDropTarget )
423 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
425 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
426 _viewDropTarget = target;
427 if( _viewDropTarget )
428 _viewDropOp = [_viewDropTarget draggingEntered: sender];
430 _viewDropOp = NSDragOperationNone;
435 - (BOOL)wantsPeriodicDraggingUpdates
437 return (_viewDropTarget!=nil);
440 - (void)draggingExited:(id <NSDraggingInfo>)sender
442 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
443 _viewDropTarget = nil;
446 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
448 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
451 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
453 return [_viewDropTarget performDragOperation: sender];
456 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
458 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
461 - (void)draggingEnded:(id <NSDraggingInfo>)sender
463 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);