jens@1: // jens@1: // GGBLayer.m jens@1: // GGB-iPhone jens@1: // jens@1: // Created by Jens Alfke on 3/7/08. jens@1: // Copyright 2008 __MyCompanyName__. All rights reserved. jens@1: // jens@1: jens@1: #import "GGBLayer.h" jens@1: #import "QuartzUtils.h" jens@11: #import "GGBUtils.h" jens@11: jens@11: jens@11: NSString* const GGBLayerStyleChangedNotification = @"GGBLayerStyleChanged"; jens@1: jens@1: jens@1: @implementation GGBLayer jens@1: jens@1: jens@1: - (NSString*) description jens@1: { jens@1: return [NSString stringWithFormat: @"%@[(%g,%g)]", self.class,self.position.x,self.position.y]; jens@1: } jens@1: jens@1: jens@4: - (void) redisplayAll jens@4: { jens@4: [self setNeedsDisplay]; jens@4: for( CALayer *layer in self.sublayers ) jens@4: if( [layer isKindOfClass: [GGBLayer class]] ) jens@4: ((GGBLayer*)layer).redisplayAll; jens@4: else jens@4: [layer setNeedsDisplay]; jens@4: } jens@4: jens@4: jens@7: /* jens@7: - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key jens@7: { jens@7: NSLog(@"%@[%p] addAnimation: %p forKey: %@",[self class],self,anim,key); jens@7: [super addAnimation: anim forKey: key]; jens@7: } jens@7: */ jens@7: jens@7: jens@7: - (void) animateAndBlock: (NSString*)keyPath from: (id)from to: (id)to duration: (NSTimeInterval)duration jens@7: { jens@7: //WARNING: This code works, but is a mess. I hope to find a better way to do this. --Jens 3/16/08 jens@7: CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath: keyPath]; jens@7: anim.duration= duration; jens@7: anim.fromValue = from; jens@7: anim.toValue = to; jens@7: anim.isRemovedOnCompletion = YES; jens@7: anim.delegate = self; jens@7: [self addAnimation:anim forKey: @"animateAndBlock:"]; jens@7: _curAnimation = (id)[self animationForKey: @"animateAndBlock:"]; jens@7: [self setValue: to forKeyPath: keyPath]; // animation doesn't update the property value jens@7: jens@7: // Now wait for it to finish: jens@7: while( _curAnimation ) { jens@7: [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode//NSEventTrackingRunLoopMode jens@7: beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.1]]; jens@7: } jens@7: } jens@7: jens@7: - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag jens@7: { jens@7: if( anim==_curAnimation ) { jens@7: _curAnimation = nil; jens@7: } jens@7: } jens@7: jens@7: jens@11: - (void) setStyle: (NSDictionary*)style jens@11: { jens@11: if( style != _styleDict ) { jens@11: if( _styleDict ) jens@11: [[NSNotificationCenter defaultCenter] removeObserver: self jens@11: name: GGBLayerStyleChangedNotification jens@11: object: _styleDict]; jens@11: if( style ) jens@11: [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_styleChanged) jens@11: name: GGBLayerStyleChangedNotification jens@11: object: style]; jens@11: setObj(&_styleDict,style); jens@11: } jens@11: [super setStyle: style]; jens@11: } jens@11: jens@11: - (void) _styleChanged jens@11: { jens@11: // Reapply the style, so any changes in the dict will take effect. jens@11: [super setStyle: _styleDict]; jens@11: } jens@11: jens@11: - (void) dealloc jens@11: { jens@11: if( _styleDict ) jens@11: [[NSNotificationCenter defaultCenter] removeObserver: self jens@11: name: GGBLayerStyleChangedNotification jens@11: object: _styleDict]; jens@11: [super dealloc]; jens@11: } jens@11: jens@11: jens@11: - (void) setValue: (id)value ofStyleProperty: (NSString*)prop jens@11: { jens@11: if( _styleDict ) { jens@11: id oldValue = [_styleDict objectForKey: prop]; jens@11: if( oldValue != value ) { jens@11: if( value ) jens@11: [_styleDict setObject: value forKey: prop]; jens@11: else jens@11: [_styleDict removeObjectForKey: prop]; jens@11: [[NSNotificationCenter defaultCenter] postNotificationName: GGBLayerStyleChangedNotification jens@11: object: _styleDict]; jens@11: } jens@11: } else jens@11: [self setValue: value forKey: prop]; jens@11: } jens@11: jens@11: jens@11: jens@8: #if TARGET_OS_IPHONE jens@1: jens@1: #pragma mark - jens@1: #pragma mark IPHONE VERSION: jens@1: jens@1: jens@1: - (id) copyWithZone: (NSZone*)zone jens@1: { jens@1: GGBLayer *clone = [[[self class] alloc] init]; jens@1: clone.bounds = self.bounds; jens@1: clone.position = self.position; jens@1: clone.zPosition = self.zPosition; jens@1: clone.anchorPoint = self.anchorPoint; jens@1: clone.transform = self.transform; jens@1: clone.hidden = self.hidden; jens@1: clone.doubleSided = self.doubleSided; jens@1: clone.sublayerTransform = self.sublayerTransform; jens@1: clone.masksToBounds = self.masksToBounds; jens@1: clone.contents = self.contents; // doesn't copy contents (shallow-copy) jens@1: clone.contentsRect = self.contentsRect; jens@1: clone.contentsGravity = self.contentsGravity; jens@1: clone.minificationFilter = self.minificationFilter; jens@1: clone.magnificationFilter = self.magnificationFilter; jens@1: clone.opaque = self.opaque; jens@1: clone.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange; jens@1: clone.edgeAntialiasingMask = self.edgeAntialiasingMask; jens@1: clone.backgroundColor = self.backgroundColor; jens@1: clone.opacity = self.opacity; jens@1: clone.compositingFilter = self.compositingFilter; jens@1: clone.filters = self.filters; jens@1: clone.backgroundFilters = self.backgroundFilters; jens@1: clone.actions = self.actions; jens@1: clone.name = self.name; jens@1: clone.style = self.style; jens@1: jens@1: clone.cornerRadius = self.cornerRadius; jens@1: clone.borderWidth = self.borderWidth; jens@1: clone.borderColor = self.borderColor; jens@1: jens@1: for( GGBLayer *sublayer in self.sublayers ) { jens@1: sublayer = [sublayer copyWithZone: zone]; jens@1: [clone addSublayer: sublayer]; jens@1: } jens@1: return clone; jens@1: } jens@1: jens@1: jens@1: - (CGFloat) cornerRadius {return _cornerRadius;} jens@1: - (CGFloat) borderWidth {return _borderWidth;} jens@9: - (CGColorRef) backgroundColor {return _realBGColor;} jens@1: - (CGColorRef) borderColor {return _borderColor;} jens@1: jens@1: - (void) setCornerRadius: (CGFloat)r jens@1: { jens@1: if( r != _cornerRadius ) { jens@1: _cornerRadius = r; jens@1: [self setNeedsDisplay]; jens@1: } jens@1: } jens@1: jens@1: jens@1: - (void) setBorderWidth: (CGFloat)w jens@1: { jens@1: if( w != _borderWidth ) { jens@1: _borderWidth = w; jens@1: self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL); jens@1: [self setNeedsDisplay]; jens@1: } jens@1: } jens@1: jens@1: jens@1: - (void) setBackgroundColor: (CGColorRef)color jens@1: { jens@1: if( color != _realBGColor ) { jens@1: CGColorRelease(_realBGColor); jens@1: _realBGColor = CGColorRetain(color); jens@1: [self setNeedsDisplay]; jens@1: } jens@1: } jens@1: jens@1: jens@1: - (void) setBorderColor: (CGColorRef)color jens@1: { jens@1: if( color != _borderColor ) { jens@1: CGColorRelease(_borderColor); jens@1: _borderColor = CGColorRetain(color); jens@1: self.needsDisplayOnBoundsChange = (_borderWidth>0.0 && _borderColor!=NULL); jens@1: [self setNeedsDisplay]; jens@1: } jens@1: } jens@1: jens@1: jens@1: - (void)drawInContext:(CGContextRef)ctx jens@1: { jens@4: [super drawInContext: ctx]; jens@4: jens@1: CGContextSaveGState(ctx); jens@1: jens@1: if( _realBGColor ) { jens@1: CGRect interior = CGRectInset(self.bounds, _borderWidth,_borderWidth); jens@1: CGContextSetFillColorWithColor(ctx, _realBGColor); jens@1: if( _cornerRadius <= 0.0 ) { jens@1: CGContextFillRect(ctx,interior); jens@1: } else { jens@1: CGContextBeginPath(ctx); jens@1: AddRoundRect(ctx,interior,_cornerRadius); jens@1: CGContextFillPath(ctx); jens@1: } jens@1: } jens@1: jens@1: if( _borderWidth > 0.0 && _borderColor!=NULL ) { jens@1: CGRect border = CGRectInset(self.bounds, _borderWidth/2.0, _borderWidth/2.0); jens@1: CGContextSetStrokeColorWithColor(ctx, _borderColor); jens@1: CGContextSetLineWidth(ctx, _borderWidth); jens@1: jens@1: if( _cornerRadius <= 0.0 ) { jens@1: CGContextStrokeRect(ctx,border); jens@1: } else { jens@1: CGContextBeginPath(ctx); jens@1: AddRoundRect(ctx,border,_cornerRadius); jens@1: CGContextStrokePath(ctx); jens@1: } jens@1: } jens@1: jens@1: CGContextRestoreGState(ctx); jens@1: } jens@1: jens@1: jens@1: #else jens@1: jens@1: #pragma mark - jens@1: #pragma mark MAC OS VERSION: jens@1: jens@1: jens@1: - (id) copyWithZone: (NSZone*)zone jens@1: { jens@1: // NSLayer isn't copyable, but it is archivable. So create a copy by archiving to jens@1: // a temporary data block, then unarchiving a new layer from that block. jens@1: jens@1: // One complication is that, due to a bug in Core Animation, CALayer can't archive jens@1: // a pattern-based CGColor. So as a workaround, clear the background before archiving, jens@1: // then restore it afterwards. jens@1: jens@1: // Also, archiving a CALayer with an image in it leaks memory. (Filed as rdar://5786865 ) jens@1: // As a workaround, clear the contents before archiving, then restore. jens@1: jens@1: CGColorRef bg = CGColorRetain(self.backgroundColor); jens@1: self.backgroundColor = NULL; jens@1: id contents = [self.contents retain]; jens@1: self.contents = nil; jens@1: jens@1: NSData *data = [NSKeyedArchiver archivedDataWithRootObject: self]; jens@1: jens@1: self.backgroundColor = bg; jens@1: self.contents = contents; jens@1: jens@1: GGBLayer *clone = [NSKeyedUnarchiver unarchiveObjectWithData: data]; jens@1: clone.backgroundColor = bg; jens@1: clone.contents = contents; jens@1: CGColorRelease(bg); jens@1: [contents release]; jens@1: jens@1: return [clone retain]; jens@1: } jens@1: jens@1: jens@1: #endif jens@1: jens@1: jens@1: @end jens@9: jens@9: jens@9: jens@9: #pragma mark - jens@9: #pragma mark UTILITIES: jens@9: jens@9: jens@9: void BeginDisableAnimations(void) jens@9: { jens@9: [CATransaction begin]; jens@9: [CATransaction setValue:(id)kCFBooleanTrue jens@9: forKey:kCATransactionDisableActions]; jens@9: } jens@9: jens@9: void EndDisableAnimations(void) jens@9: { jens@9: [CATransaction commit]; jens@9: } jens@9: jens@9: jens@9: void ChangeSuperlayer( CALayer *layer, CALayer *newSuperlayer, int index ) jens@9: { jens@9: // Disable actions, else the layer will move to the wrong place and then back! jens@9: [CATransaction flush]; jens@9: BeginDisableAnimations(); jens@9: jens@9: CGPoint pos = layer.position; jens@9: if( layer.superlayer ) jens@9: pos = [newSuperlayer convertPoint: pos fromLayer: layer.superlayer]; jens@9: [layer retain]; jens@9: [layer removeFromSuperlayer]; jens@9: layer.position = pos; jens@9: if( index >= 0 ) jens@9: [newSuperlayer insertSublayer: layer atIndex: index]; jens@9: else jens@9: [newSuperlayer addSublayer: layer]; jens@9: [layer release]; jens@9: jens@9: EndDisableAnimations(); jens@9: } jens@9: jens@9: jens@9: void RemoveImmediately( CALayer *layer ) jens@9: { jens@9: [CATransaction flush]; jens@9: BeginDisableAnimations(); jens@9: [layer removeFromSuperlayer]; jens@9: EndDisableAnimations(); jens@9: } jens@9: jens@9: jens@10: CGColorRef GetEffectiveBackground( CALayer *layer ) jens@10: { jens@10: for( ; layer; layer=layer.superlayer ) { jens@10: CGColorRef bg = layer.backgroundColor; jens@10: if( bg ) jens@10: return bg; jens@10: } jens@10: return nil; jens@10: }