Source/GGBLayer.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 17 13:29:04 2008 -0700 (2008-07-17)
changeset 19 3b750982ff39
parent 15 73f8c889f053
child 22 4cb50131788f
permissions -rw-r--r--
Don't load Checkers sound files till a game is displayed on a board; this speeds up launch.
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@16
   127
#if 0
jens@15
   128
- (CATransform3D) aggregateTransform
jens@15
   129
{
jens@15
   130
    CATransform3D xform = CATransform3DIdentity;
jens@15
   131
    for( CALayer *layer=self; layer; layer=layer.superlayer ) {
jens@15
   132
        xform = CATransform3DConcat(layer.transform,xform);
jens@15
   133
        xform = CATransform3DConcat(layer.sublayerTransform,xform);
jens@15
   134
    }
jens@15
   135
    return xform;
jens@15
   136
}
jens@15
   137
jens@15
   138
jens@15
   139
NSString* StringFromTransform3D( CATransform3D xform )
jens@15
   140
{
jens@15
   141
    NSMutableString *str = [NSMutableString string];
jens@15
   142
    const CGFloat *np = (const CGFloat*)&xform;
jens@15
   143
    for( int i=0; i<16; i++ ) {
jens@15
   144
        if( i>0 && (i%4)==0 )
jens@15
   145
            [str appendString: @"\n"];
jens@15
   146
        [str appendFormat: @"%7.2f ", *np++];
jens@15
   147
    }
jens@15
   148
    return str;
jens@15
   149
}
jens@16
   150
#endif
jens@15
   151
jens@11
   152
jens@8
   153
#if TARGET_OS_IPHONE
jens@1
   154
jens@1
   155
#pragma mark -
jens@1
   156
#pragma mark IPHONE VERSION:
jens@1
   157
jens@1
   158
jens@1
   159
- (id) copyWithZone: (NSZone*)zone
jens@1
   160
{
jens@1
   161
    GGBLayer *clone = [[[self class] alloc] init];
jens@1
   162
    clone.bounds = self.bounds;
jens@1
   163
    clone.position = self.position;
jens@1
   164
    clone.zPosition = self.zPosition;
jens@1
   165
    clone.anchorPoint = self.anchorPoint;
jens@1
   166
    clone.transform = self.transform;
jens@1
   167
    clone.hidden = self.hidden;
jens@1
   168
    clone.doubleSided = self.doubleSided;
jens@1
   169
    clone.sublayerTransform = self.sublayerTransform;
jens@1
   170
    clone.masksToBounds = self.masksToBounds;
jens@1
   171
    clone.contents = self.contents;                 // doesn't copy contents (shallow-copy)
jens@1
   172
    clone.contentsRect = self.contentsRect;
jens@1
   173
    clone.contentsGravity = self.contentsGravity;
jens@1
   174
    clone.minificationFilter = self.minificationFilter;
jens@1
   175
    clone.magnificationFilter = self.magnificationFilter;
jens@1
   176
    clone.opaque = self.opaque;
jens@1
   177
    clone.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange;
jens@1
   178
    clone.edgeAntialiasingMask = self.edgeAntialiasingMask;
jens@1
   179
    clone.backgroundColor = self.backgroundColor;
jens@1
   180
    clone.opacity = self.opacity;
jens@1
   181
    clone.compositingFilter = self.compositingFilter;
jens@1
   182
    clone.filters = self.filters;
jens@1
   183
    clone.backgroundFilters = self.backgroundFilters;
jens@1
   184
    clone.actions = self.actions;
jens@1
   185
    clone.name = self.name;
jens@1
   186
    clone.style = self.style;
jens@1
   187
    
jens@1
   188
    clone.cornerRadius = self.cornerRadius;
jens@1
   189
    clone.borderWidth = self.borderWidth;
jens@1
   190
    clone.borderColor = self.borderColor;
jens@1
   191
    
jens@1
   192
    for( GGBLayer *sublayer in self.sublayers ) {
jens@1
   193
        sublayer = [sublayer copyWithZone: zone];
jens@1
   194
        [clone addSublayer: sublayer];
jens@1
   195
    }
jens@1
   196
    return clone;
jens@1
   197
}
jens@1
   198
jens@1
   199
jens@1
   200
- (CGFloat) cornerRadius    {return _cornerRadius;}
jens@1
   201
- (CGFloat) borderWidth     {return _borderWidth;}
jens@9
   202
- (CGColorRef) backgroundColor {return _realBGColor;}
jens@1
   203
- (CGColorRef) borderColor  {return _borderColor;}
jens@1
   204
jens@1
   205
- (void) setCornerRadius: (CGFloat)r
jens@1
   206
{
jens@1
   207
    if( r != _cornerRadius ) {
jens@1
   208
        _cornerRadius = r;
jens@1
   209
        [self setNeedsDisplay];
jens@1
   210
    }
jens@1
   211
}
jens@1
   212
jens@1
   213
jens@1
   214
- (void) setBorderWidth: (CGFloat)w
jens@1
   215
{
jens@1
   216
    if( w != _borderWidth ) {
jens@1
   217
        _borderWidth = w;
jens@1
   218
        self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
jens@1
   219
        [self setNeedsDisplay];
jens@1
   220
    }
jens@1
   221
}
jens@1
   222
jens@1
   223
jens@1
   224
- (void) setBackgroundColor: (CGColorRef)color
jens@1
   225
{
jens@1
   226
    if( color != _realBGColor ) {
jens@1
   227
        CGColorRelease(_realBGColor);
jens@1
   228
        _realBGColor = CGColorRetain(color);
jens@1
   229
        [self setNeedsDisplay];
jens@1
   230
    }
jens@1
   231
}
jens@1
   232
jens@1
   233
jens@1
   234
- (void) setBorderColor: (CGColorRef)color
jens@1
   235
{
jens@1
   236
    if( color != _borderColor ) {
jens@1
   237
        CGColorRelease(_borderColor);
jens@1
   238
        _borderColor = CGColorRetain(color);
jens@1
   239
        self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
jens@1
   240
        [self setNeedsDisplay];
jens@1
   241
    }
jens@1
   242
}
jens@1
   243
jens@1
   244
jens@1
   245
- (void)drawInContext:(CGContextRef)ctx
jens@1
   246
{
jens@4
   247
    [super drawInContext: ctx];
jens@4
   248
    
jens@1
   249
    CGContextSaveGState(ctx);
jens@1
   250
jens@1
   251
    if( _realBGColor ) {
jens@1
   252
        CGRect interior = CGRectInset(self.bounds, _borderWidth,_borderWidth);
jens@1
   253
        CGContextSetFillColorWithColor(ctx, _realBGColor);
jens@1
   254
        if( _cornerRadius <= 0.0 ) {
jens@1
   255
            CGContextFillRect(ctx,interior);
jens@1
   256
        } else {
jens@1
   257
            CGContextBeginPath(ctx);
jens@1
   258
            AddRoundRect(ctx,interior,_cornerRadius);
jens@1
   259
            CGContextFillPath(ctx);
jens@1
   260
        }
jens@1
   261
    }
jens@1
   262
    
jens@1
   263
    if( _borderWidth > 0.0 && _borderColor!=NULL ) {
jens@1
   264
        CGRect border = CGRectInset(self.bounds, _borderWidth/2.0, _borderWidth/2.0);
jens@1
   265
        CGContextSetStrokeColorWithColor(ctx, _borderColor);
jens@1
   266
        CGContextSetLineWidth(ctx, _borderWidth);
jens@1
   267
        
jens@1
   268
        if( _cornerRadius <= 0.0 ) {
jens@1
   269
            CGContextStrokeRect(ctx,border);
jens@1
   270
        } else {
jens@1
   271
            CGContextBeginPath(ctx);
jens@1
   272
            AddRoundRect(ctx,border,_cornerRadius);
jens@1
   273
            CGContextStrokePath(ctx);
jens@1
   274
        }
jens@1
   275
    }
jens@1
   276
    
jens@1
   277
    CGContextRestoreGState(ctx);
jens@1
   278
}
jens@1
   279
jens@1
   280
jens@1
   281
#else
jens@1
   282
jens@1
   283
#pragma mark -
jens@1
   284
#pragma mark MAC OS VERSION:
jens@1
   285
jens@1
   286
jens@1
   287
- (id) copyWithZone: (NSZone*)zone
jens@1
   288
{
jens@1
   289
    // NSLayer isn't copyable, but it is archivable. So create a copy by archiving to
jens@1
   290
    // a temporary data block, then unarchiving a new layer from that block.
jens@1
   291
    
jens@1
   292
    // One complication is that, due to a bug in Core Animation, CALayer can't archive
jens@1
   293
    // a pattern-based CGColor. So as a workaround, clear the background before archiving,
jens@1
   294
    // then restore it afterwards.
jens@1
   295
    
jens@1
   296
    // Also, archiving a CALayer with an image in it leaks memory. (Filed as rdar://5786865 )
jens@1
   297
    // As a workaround, clear the contents before archiving, then restore.
jens@1
   298
    
jens@1
   299
    CGColorRef bg = CGColorRetain(self.backgroundColor);
jens@1
   300
    self.backgroundColor = NULL;
jens@1
   301
    id contents = [self.contents retain];
jens@1
   302
    self.contents = nil;
jens@1
   303
    
jens@1
   304
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject: self];
jens@1
   305
    
jens@1
   306
    self.backgroundColor = bg;
jens@1
   307
    self.contents = contents;
jens@1
   308
jens@1
   309
    GGBLayer *clone = [NSKeyedUnarchiver unarchiveObjectWithData: data];
jens@1
   310
    clone.backgroundColor = bg;
jens@1
   311
    clone.contents = contents;
jens@1
   312
    CGColorRelease(bg);
jens@1
   313
    [contents release];
jens@1
   314
jens@1
   315
    return [clone retain];
jens@1
   316
}
jens@1
   317
jens@1
   318
jens@1
   319
#endif
jens@1
   320
jens@1
   321
jens@1
   322
@end
jens@9
   323
jens@9
   324
jens@9
   325
jens@9
   326
#pragma mark -
jens@9
   327
#pragma mark UTILITIES:
jens@9
   328
jens@9
   329
jens@9
   330
void BeginDisableAnimations(void)
jens@9
   331
{
jens@9
   332
    [CATransaction begin];
jens@9
   333
    [CATransaction setValue:(id)kCFBooleanTrue
jens@9
   334
                     forKey:kCATransactionDisableActions];
jens@9
   335
}
jens@9
   336
jens@9
   337
void EndDisableAnimations(void)
jens@9
   338
{
jens@9
   339
    [CATransaction commit];
jens@9
   340
} 
jens@9
   341
jens@9
   342
jens@9
   343
void ChangeSuperlayer( CALayer *layer, CALayer *newSuperlayer, int index )
jens@9
   344
{
jens@9
   345
    // Disable actions, else the layer will move to the wrong place and then back!
jens@9
   346
    [CATransaction flush];
jens@9
   347
    BeginDisableAnimations();
jens@9
   348
    
jens@9
   349
    CGPoint pos = layer.position;
jens@9
   350
    if( layer.superlayer )
jens@9
   351
        pos = [newSuperlayer convertPoint: pos fromLayer: layer.superlayer];
jens@9
   352
    [layer retain];
jens@9
   353
    [layer removeFromSuperlayer];
jens@9
   354
    layer.position = pos;
jens@9
   355
    if( index >= 0 )
jens@9
   356
        [newSuperlayer insertSublayer: layer atIndex: index];
jens@9
   357
    else
jens@9
   358
        [newSuperlayer addSublayer: layer];
jens@9
   359
    [layer release];
jens@9
   360
    
jens@9
   361
    EndDisableAnimations();
jens@9
   362
}
jens@9
   363
jens@9
   364
jens@9
   365
void RemoveImmediately( CALayer *layer )
jens@9
   366
{
jens@9
   367
    [CATransaction flush];
jens@9
   368
    BeginDisableAnimations();
jens@9
   369
    [layer removeFromSuperlayer];
jens@9
   370
    EndDisableAnimations();
jens@9
   371
}    
jens@9
   372
jens@9
   373
jens@10
   374
CGColorRef GetEffectiveBackground( CALayer *layer )
jens@10
   375
{
jens@10
   376
    for( ; layer; layer=layer.superlayer ) {
jens@10
   377
        CGColorRef bg = layer.backgroundColor;
jens@10
   378
        if( bg )
jens@10
   379
            return bg;
jens@10
   380
    }
jens@10
   381
    return nil;
jens@10
   382
}