Source/GGBLayer.m
author snej@snej.local
Sun Jan 11 00:02:27 2009 -0800 (2009-01-11)
changeset 26 e7a464fb6d39
parent 16 28392c9a969f
child 27 b0affce7beb1
permissions -rw-r--r--
Added ball graphics to Xcode project, which fixes the Go game. Fixed Tic-Tac-Toe. Fixed an exception in the demo app when changing games.
jens@1
     1
//
jens@1
     2
//  GGBLayer.m
jens@1
     3
//  GGB-iPhone
jens@1
     4
//
jens@1
     5
//  Created by Jens Alfke on 3/7/08.
jens@1
     6
//  Copyright 2008 __MyCompanyName__. All rights reserved.
jens@1
     7
//
jens@1
     8
jens@1
     9
#import "GGBLayer.h"
jens@1
    10
#import "QuartzUtils.h"
jens@11
    11
#import "GGBUtils.h"
jens@11
    12
jens@11
    13
jens@11
    14
NSString* const GGBLayerStyleChangedNotification = @"GGBLayerStyleChanged";
jens@1
    15
jens@1
    16
jens@1
    17
@implementation GGBLayer
jens@1
    18
jens@1
    19
jens@1
    20
- (NSString*) description
jens@1
    21
{
jens@1
    22
    return [NSString stringWithFormat: @"%@[(%g,%g)]", self.class,self.position.x,self.position.y];
jens@1
    23
}
jens@1
    24
jens@1
    25
jens@4
    26
- (void) redisplayAll
jens@4
    27
{
jens@4
    28
    [self setNeedsDisplay];
jens@4
    29
    for( CALayer *layer in self.sublayers )
jens@4
    30
        if( [layer isKindOfClass: [GGBLayer class]] )
jens@4
    31
            ((GGBLayer*)layer).redisplayAll;
jens@4
    32
        else
jens@4
    33
            [layer setNeedsDisplay];
jens@4
    34
}
jens@4
    35
jens@4
    36
jens@7
    37
/*
jens@7
    38
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key 
jens@7
    39
{
jens@7
    40
    NSLog(@"%@[%p] addAnimation: %p forKey: %@",[self class],self,anim,key);
jens@7
    41
    [super addAnimation: anim forKey: key];
jens@7
    42
}
jens@7
    43
*/
jens@7
    44
jens@7
    45
jens@7
    46
- (void) animateAndBlock: (NSString*)keyPath from: (id)from to: (id)to duration: (NSTimeInterval)duration
jens@7
    47
{
jens@7
    48
    //WARNING: This code works, but is a mess. I hope to find a better way to do this. --Jens 3/16/08
jens@7
    49
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath: keyPath];
jens@7
    50
    anim.duration= duration;
jens@7
    51
    anim.fromValue = from;
jens@7
    52
    anim.toValue = to;
jens@7
    53
    anim.isRemovedOnCompletion = YES;
jens@7
    54
    anim.delegate = self;
jens@7
    55
    [self addAnimation:anim forKey: @"animateAndBlock:"];
jens@7
    56
    _curAnimation = (id)[self animationForKey: @"animateAndBlock:"];
jens@7
    57
    [self setValue: to forKeyPath: keyPath];    // animation doesn't update the property value
jens@7
    58
jens@14
    59
    if( self.presentationLayer ) {
jens@14
    60
        // Now wait for it to finish:
jens@14
    61
        while( _curAnimation ) {
jens@14
    62
            [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode//NSEventTrackingRunLoopMode
jens@14
    63
                                     beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
jens@14
    64
        }
jens@14
    65
    } else {
jens@14
    66
        _curAnimation = nil;
jens@7
    67
    }
jens@7
    68
}
jens@7
    69
jens@7
    70
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
jens@7
    71
{
jens@7
    72
    if( anim==_curAnimation ) {
jens@7
    73
        _curAnimation = nil;
jens@7
    74
    }
jens@7
    75
}
jens@7
    76
jens@7
    77
jens@11
    78
- (void) setStyle: (NSDictionary*)style
jens@11
    79
{
jens@11
    80
    if( style != _styleDict ) {
jens@11
    81
        if( _styleDict )
jens@11
    82
            [[NSNotificationCenter defaultCenter] removeObserver: self 
jens@11
    83
                                                            name: GGBLayerStyleChangedNotification
jens@11
    84
                                                          object: _styleDict];
jens@11
    85
        if( style )
jens@11
    86
            [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_styleChanged)
jens@11
    87
                                                         name: GGBLayerStyleChangedNotification
jens@11
    88
                                                       object: style];
jens@11
    89
        setObj(&_styleDict,style);
jens@11
    90
    }
jens@11
    91
    [super setStyle: style];
jens@11
    92
}
jens@11
    93
jens@11
    94
- (void) _styleChanged
jens@11
    95
{
jens@11
    96
    // Reapply the style, so any changes in the dict will take effect.
jens@11
    97
    [super setStyle: _styleDict];
jens@11
    98
}
jens@11
    99
jens@11
   100
- (void) dealloc
jens@11
   101
{
jens@11
   102
    if( _styleDict )
jens@11
   103
        [[NSNotificationCenter defaultCenter] removeObserver: self
jens@11
   104
                                                        name: GGBLayerStyleChangedNotification
jens@11
   105
                                                      object: _styleDict];
jens@11
   106
    [super dealloc];
jens@11
   107
}
jens@11
   108
jens@11
   109
jens@11
   110
- (void) setValue: (id)value ofStyleProperty: (NSString*)prop
jens@11
   111
{
jens@11
   112
    if( _styleDict ) {
jens@11
   113
        id oldValue = [_styleDict objectForKey: prop];
jens@11
   114
        if( oldValue != value ) {
jens@11
   115
            if( value )
jens@11
   116
                [_styleDict setObject: value forKey: prop];
jens@11
   117
            else
jens@11
   118
                [_styleDict removeObjectForKey: prop];
jens@11
   119
            [[NSNotificationCenter defaultCenter] postNotificationName: GGBLayerStyleChangedNotification
jens@11
   120
                                                                object: _styleDict];
jens@11
   121
        }
jens@11
   122
    } else
jens@11
   123
        [self setValue: value forKey: prop];
jens@11
   124
}
jens@11
   125
jens@11
   126
jens@22
   127
- (void) makeSublayersPerformSelector: (SEL)selector withObject: (id)object
jens@22
   128
{
jens@22
   129
    for( GGBLayer *layer in self.sublayers ) {
jens@22
   130
        [layer performSelector: selector withObject: object withObject: nil];
jens@22
   131
        [layer makeSublayersPerformSelector: selector withObject: object];
jens@22
   132
    }
jens@22
   133
}
jens@22
   134
jens@22
   135
- (void) changedTransform
jens@22
   136
{
jens@22
   137
    [self makeSublayersPerformSelector: @selector(aggregateTransformChanged) withObject: nil];
jens@22
   138
}
jens@22
   139
jens@22
   140
- (void) aggregateTransformChanged
jens@22
   141
{
jens@22
   142
}
jens@22
   143
jens@22
   144
jens@15
   145
- (CATransform3D) aggregateTransform
jens@15
   146
{
jens@15
   147
    CATransform3D xform = CATransform3DIdentity;
jens@15
   148
    for( CALayer *layer=self; layer; layer=layer.superlayer ) {
jens@15
   149
        xform = CATransform3DConcat(layer.transform,xform);
jens@15
   150
        xform = CATransform3DConcat(layer.sublayerTransform,xform);
jens@15
   151
    }
jens@15
   152
    return xform;
jens@15
   153
}
jens@15
   154
jens@15
   155
jens@15
   156
NSString* StringFromTransform3D( CATransform3D xform )
jens@15
   157
{
jens@15
   158
    NSMutableString *str = [NSMutableString string];
jens@15
   159
    const CGFloat *np = (const CGFloat*)&xform;
jens@15
   160
    for( int i=0; i<16; i++ ) {
jens@15
   161
        if( i>0 && (i%4)==0 )
jens@15
   162
            [str appendString: @"\n"];
jens@15
   163
        [str appendFormat: @"%7.2f ", *np++];
jens@15
   164
    }
jens@15
   165
    return str;
jens@15
   166
}
jens@15
   167
jens@11
   168
jens@8
   169
#if TARGET_OS_IPHONE
jens@1
   170
jens@1
   171
#pragma mark -
jens@1
   172
#pragma mark IPHONE VERSION:
jens@1
   173
jens@1
   174
jens@1
   175
- (id) copyWithZone: (NSZone*)zone
jens@1
   176
{
jens@1
   177
    GGBLayer *clone = [[[self class] alloc] init];
jens@1
   178
    clone.bounds = self.bounds;
jens@1
   179
    clone.position = self.position;
jens@1
   180
    clone.zPosition = self.zPosition;
jens@1
   181
    clone.anchorPoint = self.anchorPoint;
jens@1
   182
    clone.transform = self.transform;
jens@1
   183
    clone.hidden = self.hidden;
jens@1
   184
    clone.doubleSided = self.doubleSided;
jens@1
   185
    clone.sublayerTransform = self.sublayerTransform;
jens@1
   186
    clone.masksToBounds = self.masksToBounds;
jens@1
   187
    clone.contents = self.contents;                 // doesn't copy contents (shallow-copy)
jens@1
   188
    clone.contentsRect = self.contentsRect;
jens@1
   189
    clone.contentsGravity = self.contentsGravity;
jens@1
   190
    clone.minificationFilter = self.minificationFilter;
jens@1
   191
    clone.magnificationFilter = self.magnificationFilter;
jens@1
   192
    clone.opaque = self.opaque;
jens@1
   193
    clone.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange;
jens@1
   194
    clone.edgeAntialiasingMask = self.edgeAntialiasingMask;
jens@1
   195
    clone.backgroundColor = self.backgroundColor;
jens@1
   196
    clone.opacity = self.opacity;
jens@1
   197
    clone.compositingFilter = self.compositingFilter;
jens@1
   198
    clone.filters = self.filters;
jens@1
   199
    clone.backgroundFilters = self.backgroundFilters;
jens@1
   200
    clone.actions = self.actions;
jens@1
   201
    clone.name = self.name;
jens@1
   202
    clone.style = self.style;
jens@1
   203
    
jens@1
   204
    clone.cornerRadius = self.cornerRadius;
jens@1
   205
    clone.borderWidth = self.borderWidth;
jens@1
   206
    clone.borderColor = self.borderColor;
jens@1
   207
    
jens@1
   208
    for( GGBLayer *sublayer in self.sublayers ) {
jens@1
   209
        sublayer = [sublayer copyWithZone: zone];
jens@1
   210
        [clone addSublayer: sublayer];
jens@1
   211
    }
jens@1
   212
    return clone;
jens@1
   213
}
jens@1
   214
jens@1
   215
jens@1
   216
- (CGFloat) cornerRadius    {return _cornerRadius;}
jens@1
   217
- (CGFloat) borderWidth     {return _borderWidth;}
jens@9
   218
- (CGColorRef) backgroundColor {return _realBGColor;}
jens@1
   219
- (CGColorRef) borderColor  {return _borderColor;}
jens@1
   220
jens@1
   221
- (void) setCornerRadius: (CGFloat)r
jens@1
   222
{
jens@1
   223
    if( r != _cornerRadius ) {
jens@1
   224
        _cornerRadius = r;
jens@1
   225
        [self setNeedsDisplay];
jens@1
   226
    }
jens@1
   227
}
jens@1
   228
jens@1
   229
jens@1
   230
- (void) setBorderWidth: (CGFloat)w
jens@1
   231
{
jens@1
   232
    if( w != _borderWidth ) {
jens@1
   233
        _borderWidth = w;
jens@1
   234
        self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
jens@1
   235
        [self setNeedsDisplay];
jens@1
   236
    }
jens@1
   237
}
jens@1
   238
jens@1
   239
jens@1
   240
- (void) setBackgroundColor: (CGColorRef)color
jens@1
   241
{
jens@1
   242
    if( color != _realBGColor ) {
jens@1
   243
        CGColorRelease(_realBGColor);
jens@1
   244
        _realBGColor = CGColorRetain(color);
jens@1
   245
        [self setNeedsDisplay];
jens@1
   246
    }
jens@1
   247
}
jens@1
   248
jens@1
   249
jens@1
   250
- (void) setBorderColor: (CGColorRef)color
jens@1
   251
{
jens@1
   252
    if( color != _borderColor ) {
jens@1
   253
        CGColorRelease(_borderColor);
jens@1
   254
        _borderColor = CGColorRetain(color);
jens@1
   255
        self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
jens@1
   256
        [self setNeedsDisplay];
jens@1
   257
    }
jens@1
   258
}
jens@1
   259
jens@1
   260
jens@1
   261
- (void)drawInContext:(CGContextRef)ctx
jens@1
   262
{
jens@4
   263
    [super drawInContext: ctx];
jens@4
   264
    
jens@1
   265
    CGContextSaveGState(ctx);
jens@1
   266
jens@1
   267
    if( _realBGColor ) {
jens@1
   268
        CGRect interior = CGRectInset(self.bounds, _borderWidth,_borderWidth);
jens@1
   269
        CGContextSetFillColorWithColor(ctx, _realBGColor);
jens@1
   270
        if( _cornerRadius <= 0.0 ) {
jens@1
   271
            CGContextFillRect(ctx,interior);
jens@1
   272
        } else {
jens@1
   273
            CGContextBeginPath(ctx);
jens@1
   274
            AddRoundRect(ctx,interior,_cornerRadius);
jens@1
   275
            CGContextFillPath(ctx);
jens@1
   276
        }
jens@1
   277
    }
jens@1
   278
    
jens@1
   279
    if( _borderWidth > 0.0 && _borderColor!=NULL ) {
jens@1
   280
        CGRect border = CGRectInset(self.bounds, _borderWidth/2.0, _borderWidth/2.0);
jens@1
   281
        CGContextSetStrokeColorWithColor(ctx, _borderColor);
jens@1
   282
        CGContextSetLineWidth(ctx, _borderWidth);
jens@1
   283
        
jens@1
   284
        if( _cornerRadius <= 0.0 ) {
jens@1
   285
            CGContextStrokeRect(ctx,border);
jens@1
   286
        } else {
jens@1
   287
            CGContextBeginPath(ctx);
jens@1
   288
            AddRoundRect(ctx,border,_cornerRadius);
jens@1
   289
            CGContextStrokePath(ctx);
jens@1
   290
        }
jens@1
   291
    }
jens@1
   292
    
jens@1
   293
    CGContextRestoreGState(ctx);
jens@1
   294
}
jens@1
   295
jens@1
   296
jens@1
   297
#else
jens@1
   298
jens@1
   299
#pragma mark -
jens@1
   300
#pragma mark MAC OS VERSION:
jens@1
   301
jens@1
   302
jens@1
   303
- (id) copyWithZone: (NSZone*)zone
jens@1
   304
{
jens@1
   305
    // NSLayer isn't copyable, but it is archivable. So create a copy by archiving to
jens@1
   306
    // a temporary data block, then unarchiving a new layer from that block.
jens@1
   307
    
jens@1
   308
    // One complication is that, due to a bug in Core Animation, CALayer can't archive
jens@1
   309
    // a pattern-based CGColor. So as a workaround, clear the background before archiving,
jens@1
   310
    // then restore it afterwards.
jens@1
   311
    
jens@1
   312
    // Also, archiving a CALayer with an image in it leaks memory. (Filed as rdar://5786865 )
jens@1
   313
    // As a workaround, clear the contents before archiving, then restore.
jens@1
   314
    
jens@1
   315
    CGColorRef bg = CGColorRetain(self.backgroundColor);
jens@1
   316
    self.backgroundColor = NULL;
jens@1
   317
    id contents = [self.contents retain];
jens@1
   318
    self.contents = nil;
jens@1
   319
    
jens@1
   320
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject: self];
jens@1
   321
    
jens@1
   322
    self.backgroundColor = bg;
jens@1
   323
    self.contents = contents;
jens@1
   324
jens@1
   325
    GGBLayer *clone = [NSKeyedUnarchiver unarchiveObjectWithData: data];
jens@1
   326
    clone.backgroundColor = bg;
jens@1
   327
    clone.contents = contents;
jens@1
   328
    CGColorRelease(bg);
jens@1
   329
    [contents release];
jens@1
   330
jens@1
   331
    return [clone retain];
jens@1
   332
}
jens@1
   333
jens@1
   334
jens@1
   335
#endif
jens@1
   336
jens@1
   337
jens@1
   338
@end
jens@9
   339
jens@9
   340
jens@9
   341
jens@9
   342
#pragma mark -
jens@9
   343
#pragma mark UTILITIES:
jens@9
   344
jens@9
   345
jens@9
   346
void BeginDisableAnimations(void)
jens@9
   347
{
jens@9
   348
    [CATransaction begin];
jens@9
   349
    [CATransaction setValue:(id)kCFBooleanTrue
jens@9
   350
                     forKey:kCATransactionDisableActions];
jens@9
   351
}
jens@9
   352
jens@9
   353
void EndDisableAnimations(void)
jens@9
   354
{
jens@9
   355
    [CATransaction commit];
jens@9
   356
} 
jens@9
   357
jens@9
   358
jens@9
   359
void ChangeSuperlayer( CALayer *layer, CALayer *newSuperlayer, int index )
jens@9
   360
{
jens@9
   361
    // Disable actions, else the layer will move to the wrong place and then back!
jens@9
   362
    [CATransaction flush];
jens@9
   363
    BeginDisableAnimations();
jens@9
   364
    
jens@9
   365
    CGPoint pos = layer.position;
jens@9
   366
    if( layer.superlayer )
jens@9
   367
        pos = [newSuperlayer convertPoint: pos fromLayer: layer.superlayer];
jens@9
   368
    [layer retain];
jens@9
   369
    [layer removeFromSuperlayer];
jens@9
   370
    layer.position = pos;
jens@9
   371
    if( index >= 0 )
jens@9
   372
        [newSuperlayer insertSublayer: layer atIndex: index];
jens@9
   373
    else
jens@9
   374
        [newSuperlayer addSublayer: layer];
jens@9
   375
    [layer release];
jens@9
   376
    
jens@9
   377
    EndDisableAnimations();
jens@9
   378
}
jens@9
   379
jens@9
   380
jens@9
   381
void RemoveImmediately( CALayer *layer )
jens@9
   382
{
jens@9
   383
    [CATransaction flush];
jens@9
   384
    BeginDisableAnimations();
jens@9
   385
    [layer removeFromSuperlayer];
jens@9
   386
    EndDisableAnimations();
jens@9
   387
}    
jens@9
   388
jens@9
   389
jens@10
   390
CGColorRef GetEffectiveBackground( CALayer *layer )
jens@10
   391
{
jens@10
   392
    for( ; layer; layer=layer.superlayer ) {
jens@10
   393
        CGColorRef bg = layer.backgroundColor;
jens@10
   394
        if( bg )
jens@10
   395
            return bg;
jens@10
   396
    }
jens@10
   397
    return nil;
jens@10
   398
}