jens@1: // jens@1: // BoardUIView.m jens@1: // GeekGameBoard jens@1: // jens@1: // Created by Jens Alfke on 3/7/08. jens@1: // Copyright 2008 __MyCompanyName__. All rights reserved. jens@1: // jens@1: jens@1: #import "BoardUIView.h" jens@1: #import "Bit.h" jens@1: #import "BitHolder.h" jens@1: #import "Game.h" jens@1: #import "QuartzUtils.h" jens@1: #import "GGBUtils.h" jens@1: jens@1: jens@1: @implementation BoardUIView jens@1: jens@1: jens@1: - (id)initWithFrame:(CGRect)frame { jens@1: if ( (self = [super initWithFrame:frame]) ) { jens@1: // Initialization code here. jens@1: } jens@1: return self; jens@1: } jens@1: jens@1: jens@1: - (void)dealloc jens@1: { jens@1: [_game release]; jens@1: [super dealloc]; jens@1: } jens@1: jens@1: jens@1: @synthesize game=_game, gameboard=_gameboard; jens@1: jens@1: jens@1: - (void) startGameNamed: (NSString*)gameClassName jens@1: { jens@1: if( _gameboard ) { jens@1: [_gameboard removeFromSuperlayer]; jens@1: _gameboard = nil; jens@1: } jens@1: _gameboard = [[GGBLayer alloc] init]; jens@1: _gameboard.frame = [self gameBoardFrame]; jens@1: _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable; jens@1: [self.layer addSublayer: _gameboard]; jens@1: [_gameboard release]; jens@1: jens@1: Class gameClass = NSClassFromString(gameClassName); jens@1: NSAssert1(gameClass,@"Unknown game '%@'",gameClassName); jens@1: setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]); jens@1: } jens@1: jens@1: jens@1: - (CGRect) gameBoardFrame jens@1: { jens@1: return self.layer.bounds; jens@1: } jens@1: jens@1: jens@1: #pragma mark - jens@1: #pragma mark HIT-TESTING: jens@1: jens@1: jens@1: // Hit-testing callbacks (to identify which layers caller is interested in): jens@1: typedef BOOL (*LayerMatchCallback)(CALayer*); jens@1: jens@1: static BOOL layerIsBit( CALayer* layer ) {return [layer isKindOfClass: [Bit class]];} jens@1: static BOOL layerIsBitHolder( CALayer* layer ) {return [layer conformsToProtocol: @protocol(BitHolder)];} jens@1: jens@1: jens@1: /** Locates the layer at a given point in window coords. jens@1: If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned. jens@1: If outOffset is provided, the point's position relative to the layer is stored into it. */ jens@1: - (CALayer*) hitTestPoint: (CGPoint)where jens@1: forLayerMatching: (LayerMatchCallback)match jens@1: offset: (CGPoint*)outOffset jens@1: { jens@1: where = [_gameboard convertPoint: where fromLayer: self.layer]; jens@1: CALayer *layer = [_gameboard hitTest: where]; jens@1: while( layer ) { jens@1: if( match(layer) ) { jens@1: CGPoint bitPos = [self.layer convertPoint: layer.position jens@1: fromLayer: layer.superlayer]; jens@1: if( outOffset ) jens@1: *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y); jens@1: return layer; jens@1: } else jens@1: layer = layer.superlayer; jens@1: } jens@1: return nil; jens@1: } jens@1: jens@1: jens@1: #pragma mark - jens@1: #pragma mark MOUSE CLICKS & DRAGS: jens@1: jens@1: jens@1: - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: NSAssert(touches.count==1,@"No multitouch support yet"); jens@1: UITouch *touch = touches.anyObject; jens@1: jens@1: _dragStartPos = touch.locationInView; jens@1: _dragBit = (Bit*) [self hitTestPoint: _dragStartPos jens@1: forLayerMatching: layerIsBit jens@1: offset: &_dragOffset]; jens@1: if( _dragBit ) { jens@1: _dragMoved = NO; jens@1: _dropTarget = nil; jens@1: _oldHolder = _dragBit.holder; jens@1: // Ask holder's and game's permission before dragging: jens@1: if( _oldHolder ) jens@1: _dragBit = [_oldHolder canDragBit: _dragBit]; jens@1: if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) { jens@1: [_oldHolder cancelDragBit: _dragBit]; jens@1: _dragBit = nil; jens@1: } jens@1: if( ! _dragBit ) { jens@1: _oldHolder = nil; jens@1: Beep(); jens@1: return; jens@1: } jens@1: // Start dragging: jens@1: _oldSuperlayer = _dragBit.superlayer; jens@1: _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit]; jens@1: _oldPos = _dragBit.position; jens@1: ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count); jens@1: _dragBit.pickedUp = YES; jens@1: } else jens@1: Beep(); jens@1: } jens@1: jens@1: jens@1: - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: NSAssert(touches.count==1,@"No multitouch support yet"); jens@1: UITouch *touch = touches.anyObject; jens@1: jens@1: if( _dragBit ) { jens@1: // Get the mouse position, and see if we've moved 3 pixels since the mouseDown: jens@1: CGPoint pos = touch.locationInView; jens@1: if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 ) jens@1: _dragMoved = YES; jens@1: jens@1: // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness): jens@1: pos.x += _dragOffset.x; jens@1: pos.y += _dragOffset.y; jens@1: jens@1: CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer]; jens@1: jens@1: [CATransaction flush]; jens@1: [CATransaction begin]; jens@1: [CATransaction setValue:(id)kCFBooleanTrue jens@1: forKey:kCATransactionDisableActions]; jens@1: _dragBit.position = newPos; jens@1: [CATransaction commit]; jens@1: jens@1: // Find what it's over: jens@1: id target = (id) [self hitTestPoint: pos jens@1: forLayerMatching: layerIsBitHolder jens@1: offset: NULL]; jens@1: if( target == _oldHolder ) jens@1: target = nil; jens@1: if( target != _dropTarget ) { jens@1: [_dropTarget willNotDropBit: _dragBit]; jens@1: _dropTarget.highlighted = NO; jens@1: _dropTarget = nil; jens@1: } jens@1: if( target ) { jens@1: CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position jens@1: fromLayer: _dragBit.superlayer]; jens@1: if( [target canDropBit: _dragBit atPoint: targetPos] jens@1: && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) { jens@1: _dropTarget = target; jens@1: _dropTarget.highlighted = YES; jens@1: } jens@1: } jens@1: } jens@1: } jens@1: jens@1: jens@1: - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event jens@1: { jens@1: if( _dragBit ) { jens@1: if( _dragMoved ) { jens@1: // Update the drag tracking to the final mouse position: jens@1: [self touchesMoved: touches withEvent: event]; jens@1: _dropTarget.highlighted = NO; jens@1: _dragBit.pickedUp = NO; jens@1: jens@1: // Is the move legal? jens@1: if( _dropTarget && [_dropTarget dropBit: _dragBit jens@1: atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position jens@1: fromLayer: _dragBit.superlayer]] ) { jens@1: // Yes, notify the interested parties: jens@1: [_oldHolder draggedBit: _dragBit to: _dropTarget]; jens@1: [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget]; jens@1: } else { jens@1: // Nope, cancel: jens@1: [_dropTarget willNotDropBit: _dragBit]; jens@1: ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex); jens@1: _dragBit.position = _oldPos; jens@1: [_oldHolder cancelDragBit: _dragBit]; jens@1: } jens@1: } else { jens@1: // Just a click, without a drag: jens@1: _dropTarget.highlighted = NO; jens@1: _dragBit.pickedUp = NO; jens@1: ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex); jens@1: [_oldHolder cancelDragBit: _dragBit]; jens@1: if( ! [_game clickedBit: _dragBit] ) jens@1: Beep(); jens@1: } jens@1: _dropTarget = nil; jens@1: _dragBit = nil; jens@1: } jens@1: } jens@1: jens@1: jens@1: @end