# HG changeset patch # User Jens Alfke # Date 1215305203 25200 # Node ID 436cbdf5681030533a554d94af14cfcdebf60cf3 # Parent 6c78cc6bd7a65a90965ed7c005b5207aa3cac779 * Improved drag-and-drop (supports CandyBar) * Fixed DiscPiece * Inheritable layer styles etc. diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Bit.h --- a/Source/Bit.h Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Bit.h Sat Jul 05 17:46:43 2008 -0700 @@ -43,6 +43,8 @@ { @private int _restingZ; // Original z position, saved while pickedUp + float _restingShadowOpacity, _restingShadowRadius; + CGSize _restingShadowOffset; BOOL _pickedUp; Player *_owner; // Player that owns this Bit } diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Bit.m --- a/Source/Bit.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Bit.m Sat Jul 05 17:46:43 2008 -0700 @@ -100,18 +100,24 @@ - (void) setPickedUp: (BOOL)up { if( up != _pickedUp ) { - CGFloat shadow, offset, radius, opacity, z, scale; + CGFloat shadow, radius, opacity, z, scale; + CGSize offset; if( up ) { shadow = 0.8; - offset = 2; + offset = CGSizeMake(2,2); radius = 8; opacity = kPickedUpOpacity; scale = kPickedUpScale; z = kPickedUpZ; _restingZ = self.zPosition; + _restingShadowOpacity = self.shadowOpacity; + _restingShadowOffset = self.shadowOffset; + _restingShadowRadius = self.shadowRadius; } else { - shadow = offset = radius = 0.0; - opacity = 1.0; + shadow = _restingShadowOpacity; + offset = _restingShadowOffset; + radius = _restingShadowRadius; + opacity = 1; scale = 1.0/kPickedUpScale; z = _restingZ; } @@ -119,7 +125,7 @@ //self.zPosition = z; #if !TARGET_OS_IPHONE self.shadowOpacity = shadow; - self.shadowOffset = CGSizeMake(offset,-offset); + self.shadowOffset = offset; self.shadowRadius = radius; #endif self.opacity = opacity; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Card.m --- a/Source/Card.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Card.m Sat Jul 05 17:46:43 2008 -0700 @@ -183,7 +183,7 @@ - (BOOL)performDragOperation:(id )sender { - CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); + CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender); if( image ) { GGBLayer *face = _faceUp ?_front :_back; face.contents = (id) image; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/CheckersGame.m --- a/Source/CheckersGame.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/CheckersGame.m Sat Jul 05 17:46:43 2008 -0700 @@ -30,6 +30,25 @@ @implementation CheckersGame +static NSMutableDictionary *kPieceStyle1, *kPieceStyle2; + ++ (void) initialize +{ + if( self == [CheckersGame class] ) { + kPieceStyle1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + (id)GetCGImageNamed(@"Green.png"), @"contents", + kCAGravityResizeAspect, @"contentsGravity", + kCAFilterLinear, @"minificationFilter", + nil]; + kPieceStyle2 = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + (id)GetCGImageNamed(@"Red.png"), @"contents", + kCAGravityResizeAspect, @"contentsGravity", + kCAFilterLinear, @"minificationFilter", + nil]; + } +} + + - (id) init { self = [super init]; @@ -52,8 +71,9 @@ - (Piece*) pieceForPlayer: (int)playerNum { - Piece *p = [[Piece alloc] initWithImageNamed: (playerNum==0 ?@"Green.png" :@"Red.png") - scale: floor(_grid.spacing.width * 1.0)]; + Piece *p = [[Piece alloc] init]; + p.bounds = CGRectMake(0,0,floor(_grid.spacing.width),floor(_grid.spacing.height)); + p.style = (playerNum ?kPieceStyle2 :kPieceStyle1); p.owner = [self.players objectAtIndex: playerNum]; p.name = playerNum ?@"2" :@"1"; return [p autorelease]; @@ -68,7 +88,7 @@ - (void) setUpBoard { - RectGrid *grid = [[[RectGrid alloc] initWithRows: 8 columns: 8 frame: _board.bounds] autorelease]; + RectGrid *grid = [[RectGrid alloc] initWithRows: 8 columns: 8 frame: _board.bounds]; _grid = grid; [_board addSublayer: _grid]; CGPoint pos = _grid.position; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/DemoBoardView.m --- a/Source/DemoBoardView.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/DemoBoardView.m Sat Jul 05 17:46:43 2008 -0700 @@ -79,6 +79,7 @@ { srandomdev(); + // BoardView supports receiving dragged images, but you have to register for them: [self registerForDraggedTypes: [NSImage imagePasteboardTypes]]; [self registerForDraggedTypes: [NSArray arrayWithObject: NSFilenamesPboardType]]; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/DiscPiece.m --- a/Source/DiscPiece.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/DiscPiece.m Sat Jul 05 17:46:43 2008 -0700 @@ -27,27 +27,23 @@ @implementation DiscPiece -- (void) setImage: (CGImageRef)image scale: (CGFloat)scale +- (void) _setImage: (CGImageRef)image { - if( scale < 4.0 ) { - int size = MAX(CGImageGetWidth(image), CGImageGetHeight(image)); - if( scale > 0 ) - scale = ceil( size * scale); - else - scale = size; - scale *= 1.2; - } - self.bounds = CGRectMake(0,0,scale,scale); - + CGFloat diameter = MAX(CGImageGetWidth(image),CGImageGetHeight(image)); + CGFloat outerDiameter = round(diameter * 1.1); + self.bounds = CGRectMake(0,0,outerDiameter,outerDiameter); + if( ! _imageLayer ) { _imageLayer = [[CALayer alloc] init]; _imageLayer.contentsGravity = @"resizeAspect"; + _imageLayer.masksToBounds = YES; [self addSublayer: _imageLayer]; [_imageLayer release]; } - _imageLayer.frame = CGRectInset(self.bounds, scale*.1, scale*.1); + _imageLayer.frame = CGRectInset(self.bounds, outerDiameter-diameter, outerDiameter-diameter); + _imageLayer.cornerRadius = diameter/2; _imageLayer.contents = (id) image; - self.cornerRadius = scale/2; + self.cornerRadius = outerDiameter/2; self.borderWidth = 3; self.borderColor = kTranslucentLightGrayColor; self.imageName = nil; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Dispenser.m --- a/Source/Dispenser.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Dispenser.m Sat Jul 05 17:46:43 2008 -0700 @@ -181,7 +181,7 @@ { if( ! [_prototype isKindOfClass: [Piece class]] ) return NO; - CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); + CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender); if( image ) { [(Piece*)_prototype setImage: image]; self.prototype = _prototype; // recreates _bit diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/GGBLayer.h --- a/Source/GGBLayer.h Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/GGBLayer.h Sat Jul 05 17:46:43 2008 -0700 @@ -14,9 +14,13 @@ #endif +extern NSString* const GGBLayerStyleChangedNotification; + + @interface GGBLayer : CALayer { CABasicAnimation *_curAnimation; + NSMutableDictionary *_styleDict; #if ! TARGET_OS_IPHONE } @@ -35,6 +39,10 @@ - (void) animateAndBlock: (NSString*)keyPath from: (id)from to: (id)to duration: (NSTimeInterval)duration; +/** Change a property in this layer's 'style' dictionary (if it has one), + and update every other layer that shares the same style dictionary. */ +- (void) setValue: (id)value ofStyleProperty: (NSString*)prop; + @end diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/GGBLayer.m --- a/Source/GGBLayer.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/GGBLayer.m Sat Jul 05 17:46:43 2008 -0700 @@ -8,6 +8,10 @@ #import "GGBLayer.h" #import "QuartzUtils.h" +#import "GGBUtils.h" + + +NSString* const GGBLayerStyleChangedNotification = @"GGBLayerStyleChanged"; @implementation GGBLayer @@ -67,6 +71,56 @@ } +- (void) setStyle: (NSDictionary*)style +{ + if( style != _styleDict ) { + if( _styleDict ) + [[NSNotificationCenter defaultCenter] removeObserver: self + name: GGBLayerStyleChangedNotification + object: _styleDict]; + if( style ) + [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_styleChanged) + name: GGBLayerStyleChangedNotification + object: style]; + setObj(&_styleDict,style); + } + [super setStyle: style]; +} + +- (void) _styleChanged +{ + // Reapply the style, so any changes in the dict will take effect. + [super setStyle: _styleDict]; +} + +- (void) dealloc +{ + if( _styleDict ) + [[NSNotificationCenter defaultCenter] removeObserver: self + name: GGBLayerStyleChangedNotification + object: _styleDict]; + [super dealloc]; +} + + +- (void) setValue: (id)value ofStyleProperty: (NSString*)prop +{ + if( _styleDict ) { + id oldValue = [_styleDict objectForKey: prop]; + if( oldValue != value ) { + if( value ) + [_styleDict setObject: value forKey: prop]; + else + [_styleDict removeObjectForKey: prop]; + [[NSNotificationCenter defaultCenter] postNotificationName: GGBLayerStyleChangedNotification + object: _styleDict]; + } + } else + [self setValue: value forKey: prop]; +} + + + #if TARGET_OS_IPHONE #pragma mark - diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Grid.h --- a/Source/Grid.h Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Grid.h Sat Jul 05 17:46:43 2008 -0700 @@ -31,6 +31,7 @@ CGSize _spacing; Class _cellClass; CGColorRef _cellColor, _lineColor; + CGImageRef _backgroundImage; BOOL _usesDiagonals, _allowsMoves, _allowsCaptures; NSMutableArray *_cells; // Really a 2D array, in row-major order. } @@ -51,9 +52,12 @@ @property (readonly) unsigned rows, columns; // Dimensions of the grid @property (readonly) CGSize spacing; // x,y spacing of GridCells @property CGColorRef cellColor, lineColor; // Cell background color, line color (or nil) +@property CGImageRef backgroundImage; // Image drawn in background, behind lines and cells @property BOOL usesDiagonals; // Affects GridCell.neighbors, for rect grids @property BOOL allowsMoves, allowsCaptures; // Can pieces be moved, and can they land on others? +@property (readonly) NSArray *cells; + /** Returns the GridCell at the given coordinates, or nil if there is no cell there. It's OK to call this with off-the-board coordinates; it will just return nil.*/ - (GridCell*) cellAtRow: (unsigned)row column: (unsigned)col; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Grid.m --- a/Source/Grid.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Grid.m Sat Jul 05 17:46:43 2008 -0700 @@ -102,8 +102,18 @@ - (CGColorRef) lineColor {return _lineColor;} - (void) setLineColor: (CGColorRef)lineColor {setcolor(&_lineColor,lineColor);} +- (CGImageRef) backgroundImage {return _backgroundImage;} +- (void) setBackgroundImage: (CGImageRef)image +{ + if( image != _backgroundImage ) { + CGImageRelease(_backgroundImage); + _backgroundImage = CGImageRetain(image); + } +} + @synthesize cellClass=_cellClass, rows=_nRows, columns=_nColumns, spacing=_spacing, - usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures; + usesDiagonals=_usesDiagonals, allowsMoves=_allowsMoves, allowsCaptures=_allowsCaptures, + cells=_cells; #pragma mark - @@ -205,6 +215,10 @@ // Custom CALayer drawing implementation. Delegates to the cells to draw themselves // in me; this is more efficient than having each cell have its own drawing. [super drawInContext: ctx]; + + if( _backgroundImage ) + CGContextDrawImage(ctx, self.bounds, _backgroundImage); + if( _cellColor ) { CGContextSetFillColorWithColor(ctx, _cellColor); [self drawCellsInContext: ctx fill: YES]; @@ -348,13 +362,11 @@ #if ! TARGET_OS_IPHONE -// An image from another app can be dragged onto a Dispenser to change the Piece's appearance. - +// An image from another app can be dragged onto a Grid to change its background pattern. - (NSDragOperation)draggingEntered:(id )sender { - NSPasteboard *pb = [sender draggingPasteboard]; - if( [NSImage canInitWithPasteboard: pb] ) + if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) ) return NSDragOperationCopy; else return NSDragOperationNone; @@ -362,7 +374,7 @@ - (BOOL)performDragOperation:(id )sender { - CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); + CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender); if( image ) { CGColorRef pattern = CreatePatternColor(image); _grid.cellColor = pattern; @@ -456,7 +468,7 @@ - (BOOL)performDragOperation:(id )sender { - CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard]); + CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender); if( image ) { CGColorRef color = CreatePatternColor(image); RectGrid *rectGrid = (RectGrid*)_grid; diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/Piece.m --- a/Source/Piece.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/Piece.m Sat Jul 05 17:46:43 2008 -0700 @@ -27,13 +27,23 @@ @implementation Piece +- (id) init +{ + self = [super init]; + if (self != nil) { + self.zPosition = kPieceZ; + } + return self; +} + + + - (id) initWithImageNamed: (NSString*)imageName scale: (CGFloat)scale { - self = [super init]; + self = [self init]; if (self != nil) { [self setImageNamed: imageName scale: scale]; - self.zPosition = kPieceZ; } return self; } @@ -124,4 +134,29 @@ } +#if ! TARGET_OS_IPHONE + +// An image from another app can be dragged onto a Piece to change its background pattern. + +- (NSDragOperation)draggingEntered:(id )sender +{ + if( CanGetCGImageFromPasteboard([sender draggingPasteboard]) ) + return NSDragOperationCopy; + else + return NSDragOperationNone; +} + +- (BOOL)performDragOperation:(id )sender +{ + CGImageRef image = GetCGImageFromPasteboard([sender draggingPasteboard],sender); + if( image ) { + [self setValue: (id)image ofStyleProperty: @"contents"]; + return YES; + } else + return NO; +} + +#endif + + @end diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/QuartzUtils.h --- a/Source/QuartzUtils.h Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/QuartzUtils.h Sat Jul 05 17:46:43 2008 -0700 @@ -50,8 +50,10 @@ CGColorRef GetCGPatternNamed( NSString *name ); #if ! TARGET_OS_IPHONE +/** Is it possible to read a CGImage from this pasteboard? */ +BOOL CanGetCGImageFromPasteboard( NSPasteboard *pb ); /** Loads image data from the pasteboard into a CGImage. */ -CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb ); +CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb, iddragInfo ); #endif CGImageRef CreateScaledImage( CGImageRef srcImage, CGFloat scale ); diff -r 6c78cc6bd7a6 -r 436cbdf56810 Source/QuartzUtils.m --- a/Source/QuartzUtils.m Thu Jul 03 17:44:30 2008 -0700 +++ b/Source/QuartzUtils.m Sat Jul 05 17:46:43 2008 -0700 @@ -105,8 +105,12 @@ if( [name hasPrefix: @"/"] ) path = name; else { - path = [[NSBundle mainBundle] pathForResource: name ofType: nil]; - NSCAssert1(path,@"Couldn't find bundle image resource '%@'",name); + NSString *dir = [name stringByDeletingLastPathComponent]; + name = [name lastPathComponent]; + NSString *ext = name.pathExtension; + name = [name stringByDeletingPathExtension]; + path = [[NSBundle mainBundle] pathForResource: name ofType: ext inDirectory: dir]; + NSCAssert3(path,@"Couldn't find bundle image resource '%@' type '%@' in '%@'",name,ext,dir); } image = CreateCGImageFromFile(path); NSCAssert1(image,@"Failed to load image from %@",path); @@ -136,7 +140,21 @@ #if ! TARGET_OS_IPHONE -CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb ) + +BOOL CanGetCGImageFromPasteboard( NSPasteboard *pb ) +{ + return [NSImage canInitWithPasteboard: pb] + || [[pb types] containsObject: @"PixadexIconPathPboardType"]; + + /*if( [[pb types] containsObject: NSFilesPromisePboardType] ) { + NSArray *fileTypes = [pb propertyListForType: NSFilesPromisePboardType]; + NSLog(@"Got file promise! Types = %@",fileTypes); + //FIX: Check file types + return NSDragOperationCopy; + }*/ +} + +CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb, iddragInfo ) { CGImageSourceRef src = NULL; NSArray *paths = [pb propertyListForType: NSFilenamesPboardType]; @@ -144,6 +162,28 @@ // If a file is being dragged, read it: CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: [paths objectAtIndex: 0]]; src = CGImageSourceCreateWithURL(url, NULL); +/* + } else if( dragInfo && [[pb types] containsObject:NSFilesPromisePboardType] ) { + NSString *dropDir = NSTemporaryDirectory(); + NSArray *filenames = [dragInfo namesOfPromisedFilesDroppedAtDestination: [NSURL fileURLWithPath: dropDir]]; + NSLog(@"promised files are %@ / %@", dropDir,filenames); + src = nil; */ + } else if( [[pb types] containsObject: @"PixadexIconPathPboardType"] ) { + // Candybar 3 (nee Pixadex) doesn't drag out icons in any normal image type. + // It does support file-promises, but I couldn't get those to work using the Cocoa APIs. + // So instead I'm using its custom type that provides the path(s) to its internal ".pxicon" files. + // The icon is really easy to get from one of these: it's just file's custom icon. + NSArray *files = [pb propertyListForType: @"PixadexIconPathPboardType"]; + if( files.count == 1 ) { + NSString *path = [files objectAtIndex: 0]; + NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile: path]; + for( NSImageRep *rep in icon.representations ) { + if( [rep isKindOfClass: [NSBitmapImageRep class]] ) { + [rep retain]; //FIX: This leaks; but if the rep goes away, the CGImage breaks... + return [(NSBitmapImageRep*)rep CGImage]; + } + } + } } else { // Else look for an image type: NSString *type = [pb availableTypeFromArray: [NSImage imageUnfilteredPasteboardTypes]]; @@ -205,6 +245,7 @@ float GetPixelAlpha( CGImageRef image, CGSize imageSize, CGPoint pt ) { + NSCParameterAssert(image); #if TARGET_OS_IPHONE // iPhone uses "flipped" (i.e. normal) coords, so images are wrong-way-up pt.y = imageSize.height - pt.y;