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.
27 #import "QuartzUtils.h"
31 @implementation BoardView
34 @synthesize game=_game, gameboard=_gameboard;
44 - (void) startGameNamed: (NSString*)gameClassName
47 [_gameboard removeFromSuperlayer];
50 _gameboard = [[CALayer alloc] init];
51 _gameboard.frame = [self gameBoardFrame];
52 _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
53 [self.layer addSublayer: _gameboard];
56 Class gameClass = NSClassFromString(gameClassName);
57 setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
61 - (CGRect) gameBoardFrame
63 return self.layer.bounds;
67 - (void)resetCursorRects
69 [super resetCursorRects];
70 [self addCursorRect: self.bounds cursor: [NSCursor openHandCursor]];
74 - (IBAction) enterFullScreen: (id)sender
76 if( self.isInFullScreenMode ) {
77 [self exitFullScreenModeWithOptions: nil];
79 [self enterFullScreenMode: self.window.screen
86 #pragma mark KEY EVENTS:
89 - (void) keyDown: (NSEvent*)ev
91 if( self.isInFullScreenMode ) {
92 if( [ev.charactersIgnoringModifiers hasPrefix: @"\033"] ) // Esc key
93 [self enterFullScreen: self];
99 #pragma mark HIT-TESTING:
102 // Hit-testing callbacks (to identify which layers caller is interested in):
103 typedef BOOL (*LayerMatchCallback)(CALayer*);
105 static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];}
106 static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];}
107 static BOOL layerIsDropTarget( CALayer* layer ) {return [layer respondsToSelector: @selector(draggingEntered:)];}
110 /** Locates the layer at a given point in window coords.
111 If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
112 If outOffset is provided, the point's position relative to the layer is stored into it. */
113 - (CALayer*) hitTestPoint: (NSPoint)locationInWindow
114 forLayerMatching: (LayerMatchCallback)match
115 offset: (CGPoint*)outOffset
117 CGPoint where = NSPointToCGPoint([self convertPoint: locationInWindow fromView: nil]);
118 where = [_gameboard convertPoint: where fromLayer: self.layer];
119 CALayer *layer = [_gameboard hitTest: where];
122 CGPoint bitPos = [self.layer convertPoint: layer.position
123 fromLayer: layer.superlayer];
125 *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
128 layer = layer.superlayer;
135 #pragma mark MOUSE CLICKS & DRAGS:
138 - (void) mouseDown: (NSEvent*)ev
140 _dragStartPos = ev.locationInWindow;
141 _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
142 forLayerMatching: layerIsBit
143 offset: &_dragOffset];
147 _oldHolder = _dragBit.holder;
148 // Ask holder's and game's permission before dragging:
150 _dragBit = [_oldHolder canDragBit: _dragBit];
151 if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
152 [_oldHolder cancelDragBit: _dragBit];
161 _oldSuperlayer = _dragBit.superlayer;
162 _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
163 _oldPos = _dragBit.position;
164 ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
165 _dragBit.pickedUp = YES;
166 [[NSCursor closedHandCursor] push];
171 - (void) mouseDragged: (NSEvent*)ev
174 // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
175 NSPoint pos = ev.locationInWindow;
176 if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
179 // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
180 NSPoint where = [self convertPoint: pos fromView: nil];
181 where.x += _dragOffset.x;
182 where.y += _dragOffset.y;
184 CGPoint newPos = [_dragBit.superlayer convertPoint: NSPointToCGPoint(where)
185 fromLayer: self.layer];
187 [CATransaction flush];
188 [CATransaction begin];
189 [CATransaction setValue:(id)kCFBooleanTrue
190 forKey:kCATransactionDisableActions];
191 _dragBit.position = newPos;
192 [CATransaction commit];
194 // Find what it's over:
195 id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: where
196 forLayerMatching: layerIsBitHolder
198 if( target == _oldHolder )
200 if( target != _dropTarget ) {
201 [_dropTarget willNotDropBit: _dragBit];
202 _dropTarget.highlighted = NO;
206 CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
207 fromLayer: _dragBit.superlayer];
208 if( [target canDropBit: _dragBit atPoint: targetPos]
209 && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
210 _dropTarget = target;
211 _dropTarget.highlighted = YES;
217 - (void) mouseUp: (NSEvent*)ev
221 // Update the drag tracking to the final mouse position:
222 [self mouseDragged: ev];
223 _dropTarget.highlighted = NO;
224 _dragBit.pickedUp = NO;
226 // Is the move legal?
227 if( _dropTarget && [_dropTarget dropBit: _dragBit
228 atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position
229 fromLayer: _dragBit.superlayer]] ) {
230 // Yes, notify the interested parties:
231 [_oldHolder draggedBit: _dragBit to: _dropTarget];
232 [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
235 [_dropTarget willNotDropBit: _dragBit];
236 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
237 _dragBit.position = _oldPos;
238 [_oldHolder cancelDragBit: _dragBit];
241 // Just a click, without a drag:
242 _dropTarget.highlighted = NO;
243 _dragBit.pickedUp = NO;
244 ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
245 [_oldHolder cancelDragBit: _dragBit];
246 if( ! [_game clickedBit: _dragBit] )
257 #pragma mark INCOMING DRAGS:
260 // subroutine to call the target
261 static int tell( id target, SEL selector, id arg, int defaultValue )
263 if( target && [target respondsToSelector: selector] )
264 return (ssize_t) [target performSelector: selector withObject: arg];
270 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
272 _viewDropTarget = [self hitTestPoint: [sender draggingLocation]
273 forLayerMatching: layerIsDropTarget
275 _viewDropOp = _viewDropTarget ?[_viewDropTarget draggingEntered: sender] :NSDragOperationNone;
279 - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
281 CALayer *target = [self hitTestPoint: [sender draggingLocation]
282 forLayerMatching: layerIsDropTarget
284 if( target == _viewDropTarget ) {
285 if( _viewDropTarget )
286 _viewDropOp = tell(_viewDropTarget,@selector(draggingUpdated:),sender,_viewDropOp);
288 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
289 _viewDropTarget = target;
290 if( _viewDropTarget )
291 _viewDropOp = [_viewDropTarget draggingEntered: sender];
293 _viewDropOp = NSDragOperationNone;
298 - (BOOL)wantsPeriodicDraggingUpdates
300 return (_viewDropTarget!=nil);
303 - (void)draggingExited:(id <NSDraggingInfo>)sender
305 tell(_viewDropTarget,@selector(draggingExited:),sender,0);
306 _viewDropTarget = nil;
309 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
311 return tell(_viewDropTarget,@selector(prepareForDragOperation:),sender,YES);
314 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
316 return [_viewDropTarget performDragOperation: sender];
319 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
321 tell(_viewDropTarget,@selector(concludeDragOperation:),sender,0);
324 - (void)draggingEnded:(id <NSDraggingInfo>)sender
326 tell(_viewDropTarget,@selector(draggingEnded:),sender,0);