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