Source/BoardUIView.m
changeset 2 7b0441db81e5
child 3 40d225cf9c43
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Source/BoardUIView.m	Mon Mar 10 17:32:04 2008 -0700
     1.3 @@ -0,0 +1,222 @@
     1.4 +//
     1.5 +//  BoardUIView.m
     1.6 +//  GeekGameBoard
     1.7 +//
     1.8 +//  Created by Jens Alfke on 3/7/08.
     1.9 +//  Copyright 2008 __MyCompanyName__. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import "BoardUIView.h"
    1.13 +#import "Bit.h"
    1.14 +#import "BitHolder.h"
    1.15 +#import "Game.h"
    1.16 +#import "QuartzUtils.h"
    1.17 +#import "GGBUtils.h"
    1.18 +
    1.19 +
    1.20 +@implementation BoardUIView
    1.21 +
    1.22 +
    1.23 +- (id)initWithFrame:(CGRect)frame {
    1.24 +    if ( (self = [super initWithFrame:frame]) ) {
    1.25 +        // Initialization code here.
    1.26 +    }
    1.27 +    return self;
    1.28 +}
    1.29 +
    1.30 +
    1.31 +- (void)dealloc
    1.32 +{
    1.33 +    [_game release];
    1.34 +    [super dealloc];
    1.35 +}
    1.36 +
    1.37 +
    1.38 +@synthesize game=_game, gameboard=_gameboard;
    1.39 +
    1.40 +
    1.41 +- (void) startGameNamed: (NSString*)gameClassName
    1.42 +{
    1.43 +    if( _gameboard ) {
    1.44 +        [_gameboard removeFromSuperlayer];
    1.45 +        _gameboard = nil;
    1.46 +    }
    1.47 +    _gameboard = [[GGBLayer alloc] init];
    1.48 +    _gameboard.frame = [self gameBoardFrame];
    1.49 +    _gameboard.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
    1.50 +    [self.layer addSublayer: _gameboard];
    1.51 +    [_gameboard release];
    1.52 +    
    1.53 +    Class gameClass = NSClassFromString(gameClassName);
    1.54 +    NSAssert1(gameClass,@"Unknown game '%@'",gameClassName);
    1.55 +    setObj(&_game, [[gameClass alloc] initWithBoard: _gameboard]);
    1.56 +}
    1.57 +
    1.58 +
    1.59 +- (CGRect) gameBoardFrame
    1.60 +{
    1.61 +    return self.layer.bounds;
    1.62 +}
    1.63 +
    1.64 +
    1.65 +#pragma mark -
    1.66 +#pragma mark HIT-TESTING:
    1.67 +
    1.68 +
    1.69 +// Hit-testing callbacks (to identify which layers caller is interested in):
    1.70 +typedef BOOL (*LayerMatchCallback)(CALayer*);
    1.71 +
    1.72 +static BOOL layerIsBit( CALayer* layer )        {return [layer isKindOfClass: [Bit class]];}
    1.73 +static BOOL layerIsBitHolder( CALayer* layer )  {return [layer conformsToProtocol: @protocol(BitHolder)];}
    1.74 +
    1.75 +
    1.76 +/** Locates the layer at a given point in window coords.
    1.77 +    If the leaf layer doesn't pass the layer-match callback, the nearest ancestor that does is returned.
    1.78 +    If outOffset is provided, the point's position relative to the layer is stored into it. */
    1.79 +- (CALayer*) hitTestPoint: (CGPoint)where
    1.80 +         forLayerMatching: (LayerMatchCallback)match
    1.81 +                   offset: (CGPoint*)outOffset
    1.82 +{
    1.83 +    where = [_gameboard convertPoint: where fromLayer: self.layer];
    1.84 +    CALayer *layer = [_gameboard hitTest: where];
    1.85 +    while( layer ) {
    1.86 +        if( match(layer) ) {
    1.87 +            CGPoint bitPos = [self.layer convertPoint: layer.position 
    1.88 +                              fromLayer: layer.superlayer];
    1.89 +            if( outOffset )
    1.90 +                *outOffset = CGPointMake( bitPos.x-where.x, bitPos.y-where.y);
    1.91 +            return layer;
    1.92 +        } else
    1.93 +            layer = layer.superlayer;
    1.94 +    }
    1.95 +    return nil;
    1.96 +}
    1.97 +
    1.98 +
    1.99 +#pragma mark -
   1.100 +#pragma mark MOUSE CLICKS & DRAGS:
   1.101 +
   1.102 +
   1.103 +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
   1.104 +{
   1.105 +    NSAssert(touches.count==1,@"No multitouch support yet");
   1.106 +    UITouch *touch = touches.anyObject;
   1.107 +    
   1.108 +    _dragStartPos = touch.locationInView;
   1.109 +    _dragBit = (Bit*) [self hitTestPoint: _dragStartPos
   1.110 +                        forLayerMatching: layerIsBit 
   1.111 +                                  offset: &_dragOffset];
   1.112 +    if( _dragBit ) {
   1.113 +        _dragMoved = NO;
   1.114 +        _dropTarget = nil;
   1.115 +        _oldHolder = _dragBit.holder;
   1.116 +        // Ask holder's and game's permission before dragging:
   1.117 +        if( _oldHolder )
   1.118 +            _dragBit = [_oldHolder canDragBit: _dragBit];
   1.119 +        if( _dragBit && ! [_game canBit: _dragBit moveFrom: _oldHolder] ) {
   1.120 +            [_oldHolder cancelDragBit: _dragBit];
   1.121 +            _dragBit = nil;
   1.122 +        }
   1.123 +        if( ! _dragBit ) {
   1.124 +            _oldHolder = nil;
   1.125 +            Beep();
   1.126 +            return;
   1.127 +        }
   1.128 +        // Start dragging:
   1.129 +        _oldSuperlayer = _dragBit.superlayer;
   1.130 +        _oldLayerIndex = [_oldSuperlayer.sublayers indexOfObjectIdenticalTo: _dragBit];
   1.131 +        _oldPos = _dragBit.position;
   1.132 +        ChangeSuperlayer(_dragBit, self.layer, self.layer.sublayers.count);
   1.133 +        _dragBit.pickedUp = YES;
   1.134 +    } else
   1.135 +        Beep();
   1.136 +}
   1.137 +
   1.138 +
   1.139 +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
   1.140 +{
   1.141 +    NSAssert(touches.count==1,@"No multitouch support yet");
   1.142 +    UITouch *touch = touches.anyObject;
   1.143 +    
   1.144 +    if( _dragBit ) {
   1.145 +        // Get the mouse position, and see if we've moved 3 pixels since the mouseDown:
   1.146 +        CGPoint pos = touch.locationInView;
   1.147 +        if( fabs(pos.x-_dragStartPos.x)>=3 || fabs(pos.y-_dragStartPos.y)>=3 )
   1.148 +            _dragMoved = YES;
   1.149 +        
   1.150 +        // Move the _dragBit (without animation -- it's unnecessary and slows down responsiveness):
   1.151 +        pos.x += _dragOffset.x;
   1.152 +        pos.y += _dragOffset.y;
   1.153 +        
   1.154 +        CGPoint newPos = [_dragBit.superlayer convertPoint: pos fromLayer: self.layer];
   1.155 +
   1.156 +        [CATransaction flush];
   1.157 +        [CATransaction begin];
   1.158 +        [CATransaction setValue:(id)kCFBooleanTrue
   1.159 +                         forKey:kCATransactionDisableActions];
   1.160 +        _dragBit.position = newPos;
   1.161 +        [CATransaction commit];
   1.162 +
   1.163 +        // Find what it's over:
   1.164 +        id<BitHolder> target = (id<BitHolder>) [self hitTestPoint: pos
   1.165 +                                                 forLayerMatching: layerIsBitHolder
   1.166 +                                                           offset: NULL];
   1.167 +        if( target == _oldHolder )
   1.168 +            target = nil;
   1.169 +        if( target != _dropTarget ) {
   1.170 +            [_dropTarget willNotDropBit: _dragBit];
   1.171 +            _dropTarget.highlighted = NO;
   1.172 +            _dropTarget = nil;
   1.173 +        }
   1.174 +        if( target ) {
   1.175 +            CGPoint targetPos = [(CALayer*)target convertPoint: _dragBit.position
   1.176 +                                                     fromLayer: _dragBit.superlayer];
   1.177 +            if( [target canDropBit: _dragBit atPoint: targetPos]
   1.178 +               && [_game canBit: _dragBit moveFrom: _oldHolder to: target] ) {
   1.179 +                _dropTarget = target;
   1.180 +                _dropTarget.highlighted = YES;
   1.181 +            }
   1.182 +        }
   1.183 +    }
   1.184 +}
   1.185 +
   1.186 +
   1.187 +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
   1.188 +{
   1.189 +    if( _dragBit ) {
   1.190 +        if( _dragMoved ) {
   1.191 +            // Update the drag tracking to the final mouse position:
   1.192 +            [self touchesMoved: touches withEvent: event];
   1.193 +            _dropTarget.highlighted = NO;
   1.194 +            _dragBit.pickedUp = NO;
   1.195 +
   1.196 +            // Is the move legal?
   1.197 +            if( _dropTarget && [_dropTarget dropBit: _dragBit
   1.198 +                                            atPoint: [(CALayer*)_dropTarget convertPoint: _dragBit.position 
   1.199 +                                                                            fromLayer: _dragBit.superlayer]] ) {
   1.200 +                // Yes, notify the interested parties:
   1.201 +                [_oldHolder draggedBit: _dragBit to: _dropTarget];
   1.202 +                [_game bit: _dragBit movedFrom: _oldHolder to: _dropTarget];
   1.203 +            } else {
   1.204 +                // Nope, cancel:
   1.205 +                [_dropTarget willNotDropBit: _dragBit];
   1.206 +                ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   1.207 +                _dragBit.position = _oldPos;
   1.208 +                [_oldHolder cancelDragBit: _dragBit];
   1.209 +            }
   1.210 +        } else {
   1.211 +            // Just a click, without a drag:
   1.212 +            _dropTarget.highlighted = NO;
   1.213 +            _dragBit.pickedUp = NO;
   1.214 +            ChangeSuperlayer(_dragBit, _oldSuperlayer, _oldLayerIndex);
   1.215 +            [_oldHolder cancelDragBit: _dragBit];
   1.216 +            if( ! [_game clickedBit: _dragBit] )
   1.217 +                Beep();
   1.218 +        }
   1.219 +        _dropTarget = nil;
   1.220 +        _dragBit = nil;
   1.221 +    }
   1.222 +}
   1.223 +
   1.224 +
   1.225 +@end