Don't load Checkers sound files till a game is displayed on a board; this speeds up launch.
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 table=_table;
51 - (void) _removeGameBoard
54 RemoveImmediately(_table);
59 - (void) createGameBoard
61 [self _removeGameBoard];
62 _table = [[CALayer alloc] init];
63 _table.frame = [self gameBoardFrame];
64 _table.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
66 // Tell the game to set up the board:
69 [self.layer addSublayer: _table];
79 - (void) setGame: (Game*)game
84 [self createGameBoard];
88 - (void) startGameNamed: (NSString*)gameClassName
90 Class gameClass = NSClassFromString(gameClassName);
91 Game *game = [[gameClass alloc] init];
99 - (CGRect) gameBoardFrame
101 return self.layer.bounds;
105 - (void)resetCursorRects
107 [super resetCursorRects];
109 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
113 - (NSView*) fullScreenView
115 return _fullScreenView ?: self;
119 - (IBAction) enterFullScreen: (id)sender
121 //[self _removeGameBoard];
122 if( self.fullScreenView.isInFullScreenMode ) {
123 [self.fullScreenView exitFullScreenModeWithOptions: nil];
125 [self.fullScreenView enterFullScreenMode: self.window.screen
128 //[self createGameBoard];
132 - (void)viewWillStartLiveResize
134 [super viewWillStartLiveResize];
135 _oldSize = self.frame.size;
138 - (void)setFrameSize:(NSSize)newSize
140 [super setFrameSize: newSize];
141 if( _oldSize.width > 0.0f ) {
142 CGAffineTransform xform = _table.affineTransform;
143 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
144 BeginDisableAnimations();
145 _table.affineTransform = xform;
146 EndDisableAnimations();
148 [self createGameBoard];
151 - (void)viewDidEndLiveResize
153 [super viewDidEndLiveResize];
154 _oldSize.width = _oldSize.height = 0.0f;
155 [self createGameBoard];
160 #pragma mark KEY EVENTS:
163 - (BOOL) performKeyEquivalent: (NSEvent*)ev
165 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) { // Esc key
166 if( self.fullScreenView.isInFullScreenMode ) {
167 [self performSelector: @selector(enterFullScreen:) withObject: nil afterDelay: 0.0];
168 // without the delayed-perform, NSWindow crashes right after this method returns!
177 #pragma mark HIT-TESTING:
180 /** Converts a point from window coords, to this view's root layer's coords. */
181 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
183 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
184 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
188 // Hit-testing callbacks (to identify which layers caller is interested in):
189 typedef BOOL (*LayerMatchCallback)(CALayer*);
191 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
192 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
193 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
196 /** Locates the layer at a given point in window coords.
197 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
198 If outOffset is provided, the point's position relative to the layer is stored into it. */
199 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
200 forLayerMatching: (LayerMatchCallback)match
201 offset: (CGPoint*)outOffset
203 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
204 CALayer *layer = [_table hitTest: where];
207 CGPoint bitPos = [self.layer convertPoint: layer.position
208 fromLayer: layer.superlayer];
210 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
213 layer = layer.superlayer;
220 #pragma mark MOUSE CLICKS & DRAGS:
223 - (void) mouseDown: (NSEvent*)ev
225 if( ! _game.okToMove ) {
231 _dragStartPos = ev.locationInWindow;
232 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
233 forLayerMatching: layerIsBit
234 offset: &_dragOffset];
237 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
238 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
239 forLayerMatching: layerIsBitHolder
242 _dragBit = [_game bitToPlaceInHolder: holder];
244 _dragOffset.x = _dragOffset.y = 0;
245 if( _dragBit.superlayer==nil )
246 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
260 _oldHolder = _dragBit.holder;
261 // Ask holder's and game's permission before dragging:
263 _dragBit = [_oldHolder canDragBit: _dragBit];
264 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
265 [_oldHolder cancelDragBit: _dragBit];
276 _oldSuperlayer = _dragBit.superlayer;
277 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
278 _oldPos = _dragBit.position;
279 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
280 _dragBit.pickedUp = YES;
281 [[NSCursor closedHandCursor] push];
285 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
287 [self _findDropTarget: _dragStartPos];
292 - (void) mouseDragged: (NSEvent*)ev
295 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
296 NSPoint pos = ev.locationInWindow;
297 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
300 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
301 CGPoint where = [self _convertPointFromWindowToLayer: pos];
302 where.x += _dragOffset.x;
303 where.y += _dragOffset.y;
305 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
307 [CATransaction flush];
308 [CATransaction begin];
309 [CATransaction setValue:(id)kCFBooleanTrue
310 forKey:kCATransactionDisableActions];
311 _dragBit.position = newPos;
312 [CATransaction commit];
314 // Find what it's over:
315 [self _findDropTarget: pos];
320 - (void) _findDropTarget: (NSPoint)locationInWindow
322 locationInWindow.x += _dragOffset.x;
323 locationInWindow.y += _dragOffset.y;
324 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
325 forLayerMatching: layerIsBitHolder
327 if( target == _oldHolder )
329 if( target != _dropTarget ) {
330 [_dropTarget willNotDropBit: _dragBit];
331 _dropTarget.highlighted = NO;
335 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
336 fromLayer: _dragBit.superlayer];
337 if( [target canDropBit: _dragBit atPoint: targetPos]
338 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
339 _dropTarget = target;
340 _dropTarget.highlighted = YES;
346 - (void) mouseUp: (NSEvent*)ev
350 // Update the drag tracking to the final mouse position:
351 [self mouseDragged: ev];
352 _dropTarget.highlighted = NO;
353 _dragBit.pickedUp = NO;
355 // Is the move legal?
356 if( _dropTarget && [_dropTarget dropBit: _dragBit
357 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
358 fromLayer: _dragBit.superlayer]] ) {
359 // Yes, notify the interested parties:
360 [_oldHolder draggedBit: _dragBit to: _dropTarget];
361 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
364 [_dropTarget willNotDropBit: _dragBit];
365 if( _oldSuperlayer ) {
366 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
367 _dragBit.position = _oldPos;
368 [_oldHolder cancelDragBit: _dragBit];
370 [_dragBit removeFromSuperlayer];
374 // Just a click, without a drag:
375 _dropTarget.highlighted = NO;
376 _dragBit.pickedUp = NO;
377 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
378 [_oldHolder cancelDragBit: _dragBit];
379 if( ! [_game clickedBit: _dragBit] )
391 #pragma mark INCOMING DRAGS:
394 // subroutine to call the target
395 static int tell( id target, SEL selector, id arg, int defaultValue )
397 if( target && [target respondsToSelector: selector] )
398 return (ssize_t) [target performSelector: selector withObject: arg];
404 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
406 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
407 forLayerMatching: layerIsDropTarget
409 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
413 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
415 CALayer *target = [self hitTestPoint: [sender draggingLocation]
416 forLayerMatching: layerIsDropTarget
418 if( target == _viewDropTarget ) {
419 if( _viewDropTarget )
420 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
422 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
423 _viewDropTarget = target;
424 if( _viewDropTarget )
425 _viewDropOp = [_viewDropTarget draggingEntered: sender];
427 _viewDropOp = NSDragOperationNone;
432 - (BOOL)wantsPeriodicDraggingUpdates
434 return (_viewDropTarget!=nil);
437 - (void)draggingExited:(id <NSDraggingInfo>)sender
439 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
440 _viewDropTarget = nil;
443 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
445 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
448 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
450 return [_viewDropTarget performDragOperation: sender];
453 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
455 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
458 - (void)draggingEnded:(id <NSDraggingInfo>)sender
460 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);