Skip to content

Instantly share code, notes, and snippets.

@PsychoH13
Created November 28, 2010 00:25
Show Gist options
  • Save PsychoH13/718428 to your computer and use it in GitHub Desktop.
Save PsychoH13/718428 to your computer and use it in GitHub Desktop.
Layout manager using direct layer references
/*
* PSYLayerConstraintLayoutManager.h
* LayoutManager
*
* Created by Remy Demarest on 27/02/2010.
* Copyright (c) 2010, Remy Demarest
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenEmu Team nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Remy Demarest ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
extern CALayer *const kPSYSuperLayer;
@interface PSYLayerConstraintLayoutManager : NSObject
+ (id)layoutManager;
@end
@interface PSYLayerConstraint : NSObject
{
@private
__weak CALayer *_srcLayer;
CAConstraintAttribute _srcAttr :16;
CAConstraintAttribute _attr :16;
CGFloat _scale, _offset;
}
/* Create a new constraint object with the specified parameters. In the
* general case the new constraint will have the form:
*
* layer.attr = m * srcLayer.srcAttr + c
*
* 'm' defaults to one when undefined; 'c' defaults to zero. */
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
scale:(CGFloat)m offset:(CGFloat)c;
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
offset:(CGFloat)c;
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr;
/* Designated initializer. */
- (id)initWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
scale:(CGFloat)m offset:(CGFloat)c;
/* Accessors. */
@property(readonly) CALayer *sourceLayer;
@property(readonly) CAConstraintAttribute attribute;
@property(readonly) CAConstraintAttribute sourceAttribute;
@property(readonly) CGFloat scale, offset;
@end
@interface CALayer (PSYLayerConstraintLayoutManager)
@property(copy) NSArray *layerConstraints;
- (void)addLayerConstraint:(PSYLayerConstraint *)aConstraint;
@end
/*
* Created by Remy Demarest on 27/02/2010.
* Copyright (c) 2010, Remy Demarest
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenEmu Team nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Remy Demarest ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "PSYLayerConstraintLayoutManager.h"
CALayer *const kPSYSuperLayer = nil;
@interface CALayer (Private)
@property CGSize sizeRequisition;
@end
@implementation PSYLayerConstraintLayoutManager
+ (void)initialize
{
if(self == [PSYLayerConstraintLayoutManager class])
{
*((id *)&kPSYSuperLayer) = [CALayer class];
}
}
+ (id)layoutManager
{
static PSYLayerConstraintLayoutManager *layoutManager = nil;
if(layoutManager != nil) return [layoutManager retain];
@synchronized(self)
{
if(layoutManager == nil)
layoutManager = [[PSYLayerConstraintLayoutManager alloc] init];
}
return layoutManager;
}
typedef struct __attrDefs
{
NSUInteger count;
CAConstraintAttribute types[2];
CGFloat values[2];
} __attrDefs;
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
CGRect bounds = [layer bounds];
NSArray *sublayers = [layer sublayers];
NSMutableSet *laidOutSublayers = [NSMutableSet setWithCapacity:[sublayers count]];
__block void (^layoutSublayer)(CALayer *sublayer) =
^ void (CALayer *sublayer)
{
if([laidOutSublayers containsObject:sublayer]) return;
[laidOutSublayers addObject:sublayer];
NSArray *constraints = [sublayer layerConstraints];
if([constraints count] == 0) return;
// These structures cache constraint attributes for each axis
__attrDefs vertical;
__attrDefs horizontal;
bzero(&vertical, sizeof(vertical));
bzero(&horizontal, sizeof(horizontal));
// Constraint read, it only reads 2 constraints per axix
for(PSYLayerConstraint *constraint in constraints)
{
if(vertical.count >= 2 && horizontal.count >= 2) break;
CALayer *srcLayer = [constraint sourceLayer];
if(srcLayer != kPSYSuperLayer)
{
if(srcLayer == sublayer) continue;
if([srcLayer superlayer] == layer) layoutSublayer(srcLayer);
}
CAConstraintAttribute type = [constraint attribute];
__attrDefs *current = NULL;
if(type < kCAConstraintMinY) current = &horizontal;
else current = &vertical;
// If the axis is full, just continue
if(current->count >= 2) continue;
// If the value is already defined for this axis attribute, just continue
if(current->count == 1 && current->types[0] == type) continue;
current->types[current->count] = type;
CGRect srcFrame = CGRectZero;
if(srcLayer == kPSYSuperLayer)
{
srcLayer = layer;
srcFrame = bounds;
}
else srcFrame = [layer convertRect:[srcLayer bounds] fromLayer:srcLayer];
CGFloat srcAttr = 0.0;
switch([constraint sourceAttribute])
{
case kCAConstraintMinX : srcAttr = CGRectGetMinX(srcFrame); break;
case kCAConstraintMidX : srcAttr = CGRectGetMidX(srcFrame); break;
case kCAConstraintMaxX : srcAttr = CGRectGetMaxX(srcFrame); break;
case kCAConstraintWidth : srcAttr = CGRectGetWidth(srcFrame); break;
case kCAConstraintMinY : srcAttr = CGRectGetMinY(srcFrame); break;
case kCAConstraintMidY : srcAttr = CGRectGetMidY(srcFrame); break;
case kCAConstraintMaxY : srcAttr = CGRectGetMaxY(srcFrame); break;
case kCAConstraintHeight : srcAttr = CGRectGetHeight(srcFrame); break;
}
current->values[current->count] = srcAttr * [constraint scale] + [constraint offset];
current->count++;
}
CGRect frame = [sublayer frame];
#define SWAP(a, b) do { \
__typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
#define SET_AXIS(defs, axis, length, lowest) do { \
if(defs.count == 2 && defs.types[0] > defs.types[1]) \
{ \
SWAP(defs.types[0], defs.types[1]); \
SWAP(defs.values[0], defs.values[1]); \
} \
const CAConstraintAttribute attr0 = defs.types[0]; \
const CAConstraintAttribute attr1 = defs.types[1]; \
const CGFloat value0 = defs.values[0]; \
const CGFloat value1 = defs.values[1]; \
switch(defs.count) \
{ \
case 1 : \
{ \
CGFloat value = defs.values[0]; \
switch(attr0) \
{ \
case kCAConstraintMinX + lowest : frame.origin.axis = value0; break; \
case kCAConstraintMidX + lowest : frame.origin.axis = value0 - frame.size.length / 2.0; break; \
case kCAConstraintMaxX + lowest : frame.origin.axis = value0 - frame.size.length; break; \
case kCAConstraintWidth + lowest : frame.size.length = value0; break; \
} \
} break; \
case 2 : \
{ \
/* 6 cases to treat here */ \
/* cases kCAConstraintMinX / kCAConstraintMinY and kCAConstraintWidth / kCAConstraintHeight are managed by these 2 if */ \
if((kCAConstraintMinX + lowest) == attr0) frame.origin.axis = value0; \
if((kCAConstraintWidth + lowest) == attr1) frame.size.length = value1; \
\
/* case kCAConstraintMinX / kCAConstraintMinY and kCAConstraintMidX / kCAConstraintMidY */ \
if((kCAConstraintMinX + lowest) == attr0 && (kCAConstraintMidX + lowest) == attr1) \
frame.size.length = (value1 - value0) * 2.0; \
\
/* case kCAConstraintMinX / kCAConstraintMinY and kCAConstraintMaxX / kCAConstraintMaxY */ \
else if((kCAConstraintMinX + lowest) == attr0 && (kCAConstraintMaxX + lowest) == attr1) \
frame.size.length = (value1 - value0); \
\
/* case kCAConstraintMidX / kCAConstraintMidY and kCAConstraintMaxX / kCAConstraintMaxY */ \
else if((kCAConstraintMidX + lowest) == attr0 && (kCAConstraintMaxX + lowest) == attr1) \
{ \
CGFloat halfWidth = value1 - value0; \
frame.origin.axis = value0 - halfWidth; \
frame.size.length = halfWidth * 2.0; \
} \
\
/* case kCAConstraintMidX / kCAConstraintMidY and kCAConstraintWidth / kCAConstraintHeight */ \
else if((kCAConstraintMidX + lowest) == attr0 && (kCAConstraintWidth + lowest) == attr1) \
frame.origin.axis = value0 - value1 / 2.0; \
\
/* case kCAConstraintMaxX / kCAConstraintMaxY and kCAConstraintWidth / kCAConstraintHeight */ \
else if((kCAConstraintMaxX + lowest) == attr0 && (kCAConstraintWidth + lowest) == attr1) \
frame.origin.axis = value0 - value1; \
} break; \
} \
} while(0)
SET_AXIS(horizontal, x, width, kCAConstraintMinX);
SET_AXIS(vertical, y, height, kCAConstraintMinY);
[sublayer setFrame:frame];
};
for(CALayer *sub in sublayers) layoutSublayer(sub);
}
- (void)invalidateLayoutOfLayer:(CALayer *)layer
{
[layer setSizeRequisition:CGSizeZero];
}
- (CGSize)preferredSizeOfLayer:(CALayer *)layer
{
CGSize ret = [layer sizeRequisition];
if(!CGSizeEqualToSize(ret, CGSizeZero)) return ret;
return ret;
}
@end
@implementation PSYLayerConstraint
@synthesize sourceLayer = _srcLayer, attribute = _attr, sourceAttribute = _srcAttr, scale = _scale, offset = _offset;
/* Create a new constraint object with the specified parameters. In the
* general case the new constraint will have the form:
*
* layer.attr = m * srcLayer.srcAttr + c
*
* 'm' defaults to one when undefined; 'c' defaults to zero. */
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
scale:(CGFloat)m offset:(CGFloat)c
{
return [[[self alloc] initWithAttribute:attr relativeToLayer:srcLayer attribute:srcAttr scale:m offset:c] autorelease];
}
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
offset:(CGFloat)c
{
return [self constraintWithAttribute:attr relativeToLayer:srcLayer attribute:srcAttr scale:1.0 offset:c];
}
+ (id)constraintWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
{
return [self constraintWithAttribute:attr relativeToLayer:srcLayer attribute:srcAttr offset:0.0];
}
/* Designated initializer. */
- (id)initWithAttribute:(CAConstraintAttribute)attr
relativeToLayer:(CALayer *)srcLayer attribute:(CAConstraintAttribute)srcAttr
scale:(CGFloat)m offset:(CGFloat)c
{
self = [super init];
if(self != nil)
{
_srcLayer = srcLayer;
_attr = attr;
_srcAttr = srcAttr;
_scale = m;
_offset = c;
}
return self;
}
@end
@implementation CALayer (PSYLayerConstraintLayoutManager)
- (NSArray *)layerConstraints { return [self valueForKey:@"PSY_layerConstraints"]; }
- (void)setLayerConstraints:(NSArray *)value
{
[self setValue:[[value copy] autorelease] forKey:@"PSY_layerConstraints"];
}
- (void)addLayerConstraint:(PSYLayerConstraint *)aConstraint
{
NSArray *cs = [self layerConstraints];
if(cs == nil) cs = [NSArray array];
[self setLayerConstraints:[cs arrayByAddingObject:aConstraint]];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment