Updated DemoBoardView and the xcode project to work with the latest sources.
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 - (IBAction) enterFullScreen: (id)sender
115 [self _removeGameBoard];
116 if( self.isInFullScreenMode ) {
117 [self exitFullScreenModeWithOptions: nil];
119 [self enterFullScreenMode: self.window.screen
122 [self createGameBoard];
126 - (void)viewWillStartLiveResize
128 [super viewWillStartLiveResize];
129 _oldSize = self.frame.size;
132 - (void)setFrameSize:(NSSize)newSize
134 [super setFrameSize: newSize];
135 if( _oldSize.width > 0.0f ) {
136 CGAffineTransform xform = _table.affineTransform;
137 xform.a = xform.d = MIN(newSize.width,newSize.height)/MIN(_oldSize.width,_oldSize.height);
138 _table.affineTransform = xform;
140 [self createGameBoard];
143 - (void)viewDidEndLiveResize
145 [super viewDidEndLiveResize];
146 _oldSize.width = _oldSize.height = 0.0f;
147 [self createGameBoard];
152 #pragma mark KEY EVENTS:
155 - (void) keyDown: (NSEvent*)ev
157 if( self.isInFullScreenMode ) {
158 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
159 [self enterFullScreen: self];
165 #pragma mark HIT-TESTING:
168 /** Converts a point from window coords, to this view's root layer's coords. */
169 - (CGPoint) _convertPointFromWindowToLayer: (NSPoint)locationInWindow
171 NSPoint where = [self convertPoint: locationInWindow fromView: nil]; // convert to view coords
172 return NSPointToCGPoint( [self convertPointToBase: where] ); // then to layer coords
176 // Hit-testing callbacks (to identify which layers caller is interested in):
177 typedef BOOL (*LayerMatchCallback)(CALayer*);
179 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
180 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
181 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
184 /** Locates the layer at a given point in window coords.
185 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
186 If outOffset is provided, the point's position relative to the layer is stored into it. */
187 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
188 forLayerMatching: (LayerMatchCallback)match
189 offset: (CGPoint*)outOffset
191 CGPoint where = [self _convertPointFromWindowToLayer: locationInWindow ];
192 CALayer *layer = [_table hitTest: where];
195 CGPoint bitPos = [self.layer convertPoint: layer.position
196 fromLayer: layer.superlayer];
198 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
201 layer = layer.superlayer;
208 #pragma mark MOUSE CLICKS & DRAGS:
211 - (void) mouseDown: (NSEvent*)ev
213 if( ! _game.okToMove ) {
219 _dragStartPos = ev.locationInWindow;
220 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
221 forLayerMatching: layerIsBit
222 offset: &_dragOffset];
225 // If no bit was clicked, see if it's a BitHolder the game will let the user add a Bit to:
226 id<BitHolder> holder = (id<BitHolder>) [self hitTestPoint: _dragStartPos
227 forLayerMatching: layerIsBitHolder
230 _dragBit = [_game bitToPlaceInHolder: holder];
232 _dragOffset.x = _dragOffset.y = 0;
233 if( _dragBit.superlayer==nil )
234 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
248 _oldHolder = _dragBit.holder;
249 // Ask holder's and game's permission before dragging:
251 _dragBit = [_oldHolder canDragBit: _dragBit];
252 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
253 [_oldHolder cancelDragBit: _dragBit];
264 _oldSuperlayer = _dragBit.superlayer;
265 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
266 _oldPos = _dragBit.position;
267 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
268 _dragBit.pickedUp = YES;
269 [[NSCursor closedHandCursor] push];
273 _dragBit.position = [self _convertPointFromWindowToLayer: _dragStartPos];
275 [self _findDropTarget: _dragStartPos];
280 - (void) mouseDragged: (NSEvent*)ev
283 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
284 NSPoint pos = ev.locationInWindow;
285 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
288 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
289 CGPoint where = [self _convertPointFromWindowToLayer: pos];
290 where.x += _dragOffset.x;
291 where.y += _dragOffset.y;
293 CGPoint newPos = [_dragBit.superlayer convertPoint: where fromLayer: self.layer];
295 [CATransaction flush];
296 [CATransaction begin];
297 [CATransaction setValue:(id)kCFBooleanTrue
298 forKey:kCATransactionDisableActions];
299 _dragBit.position = newPos;
300 [CATransaction commit];
302 // Find what it's over:
303 [self _findDropTarget: pos];
308 - (void) _findDropTarget: (NSPoint)locationInWindow
310 locationInWindow.x += _dragOffset.x;
311 locationInWindow.y += _dragOffset.y;
312 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: locationInWindow
313 forLayerMatching: layerIsBitHolder
315 if( target == _oldHolder )
317 if( target != _dropTarget ) {
318 [_dropTarget willNotDropBit: _dragBit];
319 _dropTarget.highlighted = NO;
323 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
324 fromLayer: _dragBit.superlayer];
325 if( [target canDropBit: _dragBit atPoint: targetPos]
326 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
327 _dropTarget = target;
328 _dropTarget.highlighted = YES;
334 - (void) mouseUp: (NSEvent*)ev
338 // Update the drag tracking to the final mouse position:
339 [self mouseDragged: ev];
340 _dropTarget.highlighted = NO;
341 _dragBit.pickedUp = NO;
343 // Is the move legal?
344 if( _dropTarget && [_dropTarget dropBit: _dragBit
345 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
346 fromLayer: _dragBit.superlayer]] ) {
347 // Yes, notify the interested parties:
348 [_oldHolder draggedBit: _dragBit to: _dropTarget];
349 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
352 [_dropTarget willNotDropBit: _dragBit];
353 if( _oldSuperlayer ) {
354 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
355 _dragBit.position = _oldPos;
356 [_oldHolder cancelDragBit: _dragBit];
358 [_dragBit removeFromSuperlayer];
362 // Just a click, without a drag:
363 _dropTarget.highlighted = NO;
364 _dragBit.pickedUp = NO;
365 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
366 [_oldHolder cancelDragBit: _dragBit];
367 if( ! [_game clickedBit: _dragBit] )
379 #pragma mark INCOMING DRAGS:
382 // subroutine to call the target
383 static int tell( id target, SEL selector, id arg, int defaultValue )
385 if( target && [target respondsToSelector: selector] )
386 return (ssize_t) [target performSelector: selector withObject: arg];
392 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
394 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
395 forLayerMatching: layerIsDropTarget
397 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
401 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
403 CALayer *target = [self hitTestPoint: [sender draggingLocation]
404 forLayerMatching: layerIsDropTarget
406 if( target == _viewDropTarget ) {
407 if( _viewDropTarget )
408 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
410 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
411 _viewDropTarget = target;
412 if( _viewDropTarget )
413 _viewDropOp = [_viewDropTarget draggingEntered: sender];
415 _viewDropOp = NSDragOperationNone;
420 - (BOOL)wantsPeriodicDraggingUpdates
422 return (_viewDropTarget!=nil);
425 - (void)draggingExited:(id <NSDraggingInfo>)sender
427 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
428 _viewDropTarget = nil;
431 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
433 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
436 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
438 return [_viewDropTarget performDragOperation: sender];
441 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
443 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
446 - (void)draggingEnded:(id <NSDraggingInfo>)sender
448 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);