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