* Fixed scaling of king pieces when the board's state is set.
* Added IBOutlets for tilting the BoardView.
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.
26 #import "Game+Protected.h"
29 #import "QuartzUtils.h"
33 #define kMaxPerspective 0.965 // 55 degrees
36 @interface BoardView ()
37 - (void) _findDropTarget: (NSPoint)pos;
41 @implementation BoardView
44 @synthesize table=_table, gameBoardInset=_gameBoardInset;
55 #pragma mark PERSPECTIVE:
58 - (void) _applyPerspective
61 if( fabs(_perspective) >= M_PI/180 ) {
62 CGSize size = self.layer.bounds.size;
63 t = CATransform3DMakeTranslation(-size.width/2, -size.height/4, 0);
64 t = CATransform3DConcat(t, CATransform3DMakeRotation(-_perspective, 1,0,0));
66 CATransform3D pers = CATransform3DIdentity;
68 t = CATransform3DConcat(t, pers);
69 t = CATransform3DConcat(t, CATransform3DMakeTranslation(size.width/2,
70 size.height*(0.25 + 0.05*sin(2*_perspective)),
72 self.layer.borderWidth = 3;
74 t = CATransform3DIdentity;
75 self.layer.borderWidth = 0;
77 self.layer.transform = t;
80 - (CGFloat) perspective {return _perspective;}
82 - (void) setPerspective: (CGFloat)p
84 p = MAX(0.0, MIN(kMaxPerspective, p));
85 if( p != _perspective ) {
87 [self _applyPerspective];
88 _game.tablePerspectiveAngle = p;
92 - (IBAction) tiltUp: (id)sender {self.perspective -= M_PI/40;}
93 - (IBAction) tiltDown: (id)sender {self.perspective += M_PI/40;}
97 #pragma mark GAME BOARD:
100 - (void) _removeGameBoard
103 RemoveImmediately(_table);
108 - (void) createGameBoard
110 [self _removeGameBoard];
111 _table = [[CALayer alloc] init];
112 _table.frame = [self gameBoardFrame];
113 _table.autoresizingMask = kCALayerMinXMargin | kCALayerMaxXMargin | kCALayerMinYMargin | kCALayerMaxYMargin;
115 // Tell the game to set up the board:
116 _game.table = _table;
118 [self.layer addSublayer: _table];
128 - (void) setGame: (Game*)game
133 [self createGameBoard];
137 - (void) startGameNamed: (NSString*)gameClassName
139 Class gameClass = NSClassFromString(gameClassName);
140 Game *game = [[gameClass alloc] init];
149 #pragma mark VIEW MANIPULATION:
152 - (CGRect) gameBoardFrame
154 return CGRectInset(self.layer.bounds, _gameBoardInset.width,_gameBoardInset.height);
158 - (void)resetCursorRects
160 [super resetCursorRects];
162 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
166 - (NSView*) fullScreenView
168 return _fullScreenView ?: self;
171 - (IBAction) enterFullScreen: (id)sender
173 //[self _removeGameBoard];
174 if( self.fullScreenView.isInFullScreenMode ) {
175 [self.fullScreenView exitFullScreenModeWithOptions: nil];
177 [self.fullScreenView enterFullScreenMode: self.window.screen
180 //[self createGameBoard];
184 - (void)viewWillStartLiveResize
186 [super viewWillStartLiveResize];
187 _oldSize = self.frame.size;
190 - (void)setFrameSize:(NSSize)newSize
192 [super setFrameSize: newSize];
193 if( _oldSize.width > 0.0f ) {
194 CGAffineTransform xform = _table.affineTransform;
195 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
196 BeginDisableAnimations();
197 [self _applyPerspective];
198 _table.affineTransform = xform;
199 EndDisableAnimations();
201 [self createGameBoard];
204 - (void)viewDidEndLiveResize
206 [super viewDidEndLiveResize];
207 _oldSize.width = _oldSize.height = 0.0f;
208 [self createGameBoard];
213 #pragma mark KEY EVENTS:
216 - (BOOL) performKeyEquivalent: (NSEvent*)ev
218 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) { // Esc key
219 if( self.fullScreenView.isInFullScreenMode ) {
220 [self performSelector: @selector(enterFullScreen:) withObject: nil afterDelay: 0.0];
221 // without the delayed-perform, NSWindow crashes right after this method returns!
230 #pragma mark HIT-TESTING:
233 /** Converts a point from window coords, to this view's root layer's coords. */
234 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
236 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
237 where = [self convertPointToBase: where]; // then to layer base coords
238 return [self.layer convertPoint: NSPointToCGPoint(where) // then to transformed layer coords
239 fromLayer: self.layer.superlayer];
243 // Hit-testing callbacks (to identify which layers caller is interested in):
244 typedef BOOL (*LayerMatchCallback)(CALayer*);
246 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
247 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
248 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
251 /** Locates the layer at a given point in window coords.
252 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
253 If outOffset is provided, the point's position relative to the layer is stored into it. */
254 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
255 forLayerMatching: (LayerMatchCallback)match
256 offset: (CGPoint*)outOffset
258 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
259 CALayer *layer = [_table hitTest: where];
262 CGPoint bitPos = [self.layer convertPoint: layer.position
263 fromLayer: layer.superlayer];
265 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
268 layer = layer.superlayer;
275 #pragma mark MOUSE CLICKS & DRAGS:
278 - (void) mouseDown: (NSEvent*)ev
280 if( ! _game.okToMove ) {
286 _dragStartPos = ev.locationInWindow;
287 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
288 forLayerMatching: layerIsBit
289 offset: &_dragOffset];
292 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
293 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
294 forLayerMatching: layerIsBitHolder
297 _dragBit = [_game bitToPlaceInHolder: holder];
299 _dragOffset.x = _dragOffset.y = 0;
300 if( _dragBit.superlayer==nil )
301 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
315 _oldHolder = _dragBit.holder;
316 // Ask holder's and game's permission before dragging:
318 _dragBit = [_oldHolder canDragBit: _dragBit];
319 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
320 [_oldHolder cancelDragBit: _dragBit];
331 _oldSuperlayer = _dragBit.superlayer;
332 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
333 _oldPos = _dragBit.position;
334 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
335 _dragBit.pickedUp = YES;
336 [[NSCursor closedHandCursor] push];
340 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
342 [self _findDropTarget: _dragStartPos];
347 - (void) mouseDragged: (NSEvent*)ev
350 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
351 NSPoint pos = ev.locationInWindow;
352 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
355 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
356 CGPoint where = [self _convertPointFromWindowToLayer: pos];
357 where.x += _dragOffset.x;
358 where.y += _dragOffset.y;
360 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
362 [CATransaction flush];
363 [CATransaction begin];
364 [CATransaction setValue:(id)kCFBooleanTrue
365 forKey:kCATransactionDisableActions];
366 _dragBit.position = newPos;
367 [CATransaction commit];
369 // Find what it's over:
370 [self _findDropTarget: pos];
375 - (void) _findDropTarget: (NSPoint)locationInWindow
377 locationInWindow.x += _dragOffset.x;
378 locationInWindow.y += _dragOffset.y;
379 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
380 forLayerMatching: layerIsBitHolder
382 if( target == _oldHolder )
384 if( target != _dropTarget ) {
385 [_dropTarget willNotDropBit: _dragBit];
386 _dropTarget.highlighted = NO;
390 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
391 fromLayer: _dragBit.superlayer];
392 if( [target canDropBit: _dragBit atPoint: targetPos]
393 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
394 _dropTarget = target;
395 _dropTarget.highlighted = YES;
401 - (void) mouseUp: (NSEvent*)ev
405 // Update the drag tracking to the final mouse position:
406 [self mouseDragged: ev];
407 _dropTarget.highlighted = NO;
408 _dragBit.pickedUp = NO;
410 // Is the move legal?
411 if( _dropTarget && [_dropTarget dropBit: _dragBit
412 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
413 fromLayer: _dragBit.superlayer]] ) {
414 // Yes, notify the interested parties:
415 [_oldHolder draggedBit: _dragBit to: _dropTarget];
416 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
419 [_dropTarget willNotDropBit: _dragBit];
420 if( _oldSuperlayer ) {
421 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
422 _dragBit.position = _oldPos;
423 [_oldHolder cancelDragBit: _dragBit];
425 [_dragBit removeFromSuperlayer];
429 // Just a click, without a drag:
430 _dropTarget.highlighted = NO;
431 _dragBit.pickedUp = NO;
432 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
433 [_oldHolder cancelDragBit: _dragBit];
434 if( ! [_game clickedBit: _dragBit] )
445 - (void)scrollWheel:(NSEvent *)e
447 self.perspective += e.deltaY * M_PI/180;
448 //Log(@"Perspective = %2.0f degrees (%5.3f radians)", self.perspective*180/M_PI, self.perspective);
453 #pragma mark INCOMING DRAGS:
456 // subroutine to call the target
457 static int tell( id target, SEL selector, id arg, int defaultValue )
459 if( target && [target respondsToSelector: selector] )
460 return (ssize_t) [target performSelector: selector withObject: arg];
466 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
468 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
469 forLayerMatching: layerIsDropTarget
471 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
475 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
477 CALayer *target = [self hitTestPoint: [sender draggingLocation]
478 forLayerMatching: layerIsDropTarget
480 if( target == _viewDropTarget ) {
481 if( _viewDropTarget )
482 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
484 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
485 _viewDropTarget = target;
486 if( _viewDropTarget )
487 _viewDropOp = [_viewDropTarget draggingEntered: sender];
489 _viewDropOp = NSDragOperationNone;
494 - (BOOL)wantsPeriodicDraggingUpdates
496 return (_viewDropTarget!=nil);
499 - (void)draggingExited:(id <NSDraggingInfo>)sender
501 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
502 _viewDropTarget = nil;
505 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
507 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
510 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
512 return [_viewDropTarget performDragOperation: sender];
515 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
517 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
520 - (void)draggingEnded:(id <NSDraggingInfo>)sender
522 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);