Source/GGBLayer.m
author Jens Alfke <jens@mooseyard.com>
Sat Jul 05 17:46:43 2008 -0700 (2008-07-05)
changeset 11 436cbdf56810
parent 10 6c78cc6bd7a6
child 14 4585c74d809c
permissions -rw-r--r--
* Improved drag-and-drop (supports CandyBar)
* Fixed DiscPiece
* Inheritable layer styles
etc.
     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     // Now wait for it to finish:
    60     while( _curAnimation ) {
    61         [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode//NSEventTrackingRunLoopMode
    62                                  beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]];
    63     }
    64 }
    65 
    66 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
    67 {
    68     if( anim==_curAnimation ) {
    69         _curAnimation = nil;
    70     }
    71 }
    72 
    73 
    74 - (void) setStyle: (NSDictionary*)style
    75 {
    76     if( style != _styleDict ) {
    77         if( _styleDict )
    78             [[NSNotificationCenter defaultCenter] removeObserver: self 
    79                                                             name: GGBLayerStyleChangedNotification
    80                                                           object: _styleDict];
    81         if( style )
    82             [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_styleChanged)
    83                                                          name: GGBLayerStyleChangedNotification
    84                                                        object: style];
    85         setObj(&_styleDict,style);
    86     }
    87     [super setStyle: style];
    88 }
    89 
    90 - (void) _styleChanged
    91 {
    92     // Reapply the style, so any changes in the dict will take effect.
    93     [super setStyle: _styleDict];
    94 }
    95 
    96 - (void) dealloc
    97 {
    98     if( _styleDict )
    99         [[NSNotificationCenter defaultCenter] removeObserver: self
   100                                                         name: GGBLayerStyleChangedNotification
   101                                                       object: _styleDict];
   102     [super dealloc];
   103 }
   104 
   105 
   106 - (void) setValue: (id)value ofStyleProperty: (NSString*)prop
   107 {
   108     if( _styleDict ) {
   109         id oldValue = [_styleDict objectForKey: prop];
   110         if( oldValue != value ) {
   111             if( value )
   112                 [_styleDict setObject: value forKey: prop];
   113             else
   114                 [_styleDict removeObjectForKey: prop];
   115             [[NSNotificationCenter defaultCenter] postNotificationName: GGBLayerStyleChangedNotification
   116                                                                 object: _styleDict];
   117         }
   118     } else
   119         [self setValue: value forKey: prop];
   120 }
   121 
   122 
   123 
   124 #if TARGET_OS_IPHONE
   125 
   126 #pragma mark -
   127 #pragma mark IPHONE VERSION:
   128 
   129 
   130 - (id) copyWithZone: (NSZone*)zone
   131 {
   132     GGBLayer *clone = [[[self class] alloc] init];
   133     clone.bounds = self.bounds;
   134     clone.position = self.position;
   135     clone.zPosition = self.zPosition;
   136     clone.anchorPoint = self.anchorPoint;
   137     clone.transform = self.transform;
   138     clone.hidden = self.hidden;
   139     clone.doubleSided = self.doubleSided;
   140     clone.sublayerTransform = self.sublayerTransform;
   141     clone.masksToBounds = self.masksToBounds;
   142     clone.contents = self.contents;                 // doesn't copy contents (shallow-copy)
   143     clone.contentsRect = self.contentsRect;
   144     clone.contentsGravity = self.contentsGravity;
   145     clone.minificationFilter = self.minificationFilter;
   146     clone.magnificationFilter = self.magnificationFilter;
   147     clone.opaque = self.opaque;
   148     clone.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange;
   149     clone.edgeAntialiasingMask = self.edgeAntialiasingMask;
   150     clone.backgroundColor = self.backgroundColor;
   151     clone.opacity = self.opacity;
   152     clone.compositingFilter = self.compositingFilter;
   153     clone.filters = self.filters;
   154     clone.backgroundFilters = self.backgroundFilters;
   155     clone.actions = self.actions;
   156     clone.name = self.name;
   157     clone.style = self.style;
   158     
   159     clone.cornerRadius = self.cornerRadius;
   160     clone.borderWidth = self.borderWidth;
   161     clone.borderColor = self.borderColor;
   162     
   163     for( GGBLayer *sublayer in self.sublayers ) {
   164         sublayer = [sublayer copyWithZone: zone];
   165         [clone addSublayer: sublayer];
   166     }
   167     return clone;
   168 }
   169 
   170 
   171 - (CGFloat) cornerRadius    {return _cornerRadius;}
   172 - (CGFloat) borderWidth     {return _borderWidth;}
   173 - (CGColorRef) backgroundColor {return _realBGColor;}
   174 - (CGColorRef) borderColor  {return _borderColor;}
   175 
   176 - (void) setCornerRadius: (CGFloat)r
   177 {
   178     if( r != _cornerRadius ) {
   179         _cornerRadius = r;
   180         [self setNeedsDisplay];
   181     }
   182 }
   183 
   184 
   185 - (void) setBorderWidth: (CGFloat)w
   186 {
   187     if( w != _borderWidth ) {
   188         _borderWidth = w;
   189         self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
   190         [self setNeedsDisplay];
   191     }
   192 }
   193 
   194 
   195 - (void) setBackgroundColor: (CGColorRef)color
   196 {
   197     if( color != _realBGColor ) {
   198         CGColorRelease(_realBGColor);
   199         _realBGColor = CGColorRetain(color);
   200         [self setNeedsDisplay];
   201     }
   202 }
   203 
   204 
   205 - (void) setBorderColor: (CGColorRef)color
   206 {
   207     if( color != _borderColor ) {
   208         CGColorRelease(_borderColor);
   209         _borderColor = CGColorRetain(color);
   210         self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL);
   211         [self setNeedsDisplay];
   212     }
   213 }
   214 
   215 
   216 - (void)drawInContext:(CGContextRef)ctx
   217 {
   218     [super drawInContext: ctx];
   219     
   220     CGContextSaveGState(ctx);
   221 
   222     if( _realBGColor ) {
   223         CGRect interior = CGRectInset(self.bounds, _borderWidth,_borderWidth);
   224         CGContextSetFillColorWithColor(ctx, _realBGColor);
   225         if( _cornerRadius <= 0.0 ) {
   226             CGContextFillRect(ctx,interior);
   227         } else {
   228             CGContextBeginPath(ctx);
   229             AddRoundRect(ctx,interior,_cornerRadius);
   230             CGContextFillPath(ctx);
   231         }
   232     }
   233     
   234     if( _borderWidth > 0.0 && _borderColor!=NULL ) {
   235         CGRect border = CGRectInset(self.bounds, _borderWidth/2.0, _borderWidth/2.0);
   236         CGContextSetStrokeColorWithColor(ctx, _borderColor);
   237         CGContextSetLineWidth(ctx, _borderWidth);
   238         
   239         if( _cornerRadius <= 0.0 ) {
   240             CGContextStrokeRect(ctx,border);
   241         } else {
   242             CGContextBeginPath(ctx);
   243             AddRoundRect(ctx,border,_cornerRadius);
   244             CGContextStrokePath(ctx);
   245         }
   246     }
   247     
   248     CGContextRestoreGState(ctx);
   249 }
   250 
   251 
   252 #else
   253 
   254 #pragma mark -
   255 #pragma mark MAC OS VERSION:
   256 
   257 
   258 - (id) copyWithZone: (NSZone*)zone
   259 {
   260     // NSLayer isn't copyable, but it is archivable. So create a copy by archiving to
   261     // a temporary data block, then unarchiving a new layer from that block.
   262     
   263     // One complication is that, due to a bug in Core Animation, CALayer can't archive
   264     // a pattern-based CGColor. So as a workaround, clear the background before archiving,
   265     // then restore it afterwards.
   266     
   267     // Also, archiving a CALayer with an image in it leaks memory. (Filed as rdar://5786865 )
   268     // As a workaround, clear the contents before archiving, then restore.
   269     
   270     CGColorRef bg = CGColorRetain(self.backgroundColor);
   271     self.backgroundColor = NULL;
   272     id contents = [self.contents retain];
   273     self.contents = nil;
   274     
   275     NSData *data = [NSKeyedArchiver archivedDataWithRootObject: self];
   276     
   277     self.backgroundColor = bg;
   278     self.contents = contents;
   279 
   280     GGBLayer *clone = [NSKeyedUnarchiver unarchiveObjectWithData: data];
   281     clone.backgroundColor = bg;
   282     clone.contents = contents;
   283     CGColorRelease(bg);
   284     [contents release];
   285 
   286     return [clone retain];
   287 }
   288 
   289 
   290 #endif
   291 
   292 
   293 @end
   294 
   295 
   296 
   297 #pragma mark -
   298 #pragma mark UTILITIES:
   299 
   300 
   301 void BeginDisableAnimations(void)
   302 {
   303     [CATransaction begin];
   304     [CATransaction setValue:(id)kCFBooleanTrue
   305                      forKey:kCATransactionDisableActions];
   306 }
   307 
   308 void EndDisableAnimations(void)
   309 {
   310     [CATransaction commit];
   311 } 
   312 
   313 
   314 void ChangeSuperlayer( CALayer *layer, CALayer *newSuperlayer, int index )
   315 {
   316     // Disable actions, else the layer will move to the wrong place and then back!
   317     [CATransaction flush];
   318     BeginDisableAnimations();
   319     
   320     CGPoint pos = layer.position;
   321     if( layer.superlayer )
   322         pos = [newSuperlayer convertPoint: pos fromLayer: layer.superlayer];
   323     [layer retain];
   324     [layer removeFromSuperlayer];
   325     layer.position = pos;
   326     if( index >= 0 )
   327         [newSuperlayer insertSublayer: layer atIndex: index];
   328     else
   329         [newSuperlayer addSublayer: layer];
   330     [layer release];
   331     
   332     EndDisableAnimations();
   333 }
   334 
   335 
   336 void RemoveImmediately( CALayer *layer )
   337 {
   338     [CATransaction flush];
   339     BeginDisableAnimations();
   340     [layer removeFromSuperlayer];
   341     EndDisableAnimations();
   342 }    
   343 
   344 
   345 CGColorRef GetEffectiveBackground( CALayer *layer )
   346 {
   347     for( ; layer; layer=layer.superlayer ) {
   348         CGColorRef bg = layer.backgroundColor;
   349         if( bg )
   350             return bg;
   351     }
   352     return nil;
   353 }