mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-30 15:00:38 +00:00
Implement constraintsWithVisualFormat method on NSLayoutConstraint
This commit is contained in:
parent
30d0dc66ee
commit
1aff56cb9b
5 changed files with 569 additions and 5 deletions
|
@ -157,7 +157,12 @@ APPKIT_EXPORT_CLASS
|
||||||
|
|
||||||
- (NSLayoutAnchor *) secondAnchor;
|
- (NSLayoutAnchor *) secondAnchor;
|
||||||
|
|
||||||
- (NSLayoutPriority) priority;
|
#if GS_HAS_DECLARED_PROPERTIES
|
||||||
|
@property NSLayoutPriority priority;
|
||||||
|
#else
|
||||||
|
- (NSLayoutPriority) priority;
|
||||||
|
- (void) setPriority: (NSLayoutPriority)priority;
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -349,7 +349,8 @@ GSXibLoader.m \
|
||||||
GSXibLoading.m \
|
GSXibLoading.m \
|
||||||
GSXibKeyedUnarchiver.m \
|
GSXibKeyedUnarchiver.m \
|
||||||
GSXib5KeyedUnarchiver.m \
|
GSXib5KeyedUnarchiver.m \
|
||||||
GSHelpAttachment.m
|
GSHelpAttachment.m \
|
||||||
|
GSAutoLayoutVFLParser.m
|
||||||
|
|
||||||
# Turn off NSMenuItem warning that NSMenuItem conforms to <NSObject>,
|
# Turn off NSMenuItem warning that NSMenuItem conforms to <NSObject>,
|
||||||
# but does not implement <NSObject>'s methods itself (it inherits
|
# but does not implement <NSObject>'s methods itself (it inherits
|
||||||
|
@ -658,7 +659,8 @@ GSWindowDecorationView.h \
|
||||||
GSXibElement.h \
|
GSXibElement.h \
|
||||||
GSXibLoading.h \
|
GSXibLoading.h \
|
||||||
GSXibKeyedUnarchiver.h \
|
GSXibKeyedUnarchiver.h \
|
||||||
GSHelpAttachment.h
|
GSHelpAttachment.h \
|
||||||
|
GSAutoLayoutVFLParser.h
|
||||||
|
|
||||||
libgnustep-gui_HEADER_FILES = ${GUI_HEADERS}
|
libgnustep-gui_HEADER_FILES = ${GUI_HEADERS}
|
||||||
|
|
||||||
|
|
22
Source/GSAutoLayoutVFLParser.h
Normal file
22
Source/GSAutoLayoutVFLParser.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
@interface GSAutoLayoutVFLParser : NSObject
|
||||||
|
{
|
||||||
|
NSDictionary *_views;
|
||||||
|
NSLayoutFormatOptions _options;
|
||||||
|
NSDictionary *_metrics;
|
||||||
|
NSScanner *_scanner;
|
||||||
|
NSMutableArray *_constraints;
|
||||||
|
NSMutableArray *_layoutFormatConstraints;
|
||||||
|
NSView *_view;
|
||||||
|
BOOL _createLeadingConstraintToSuperview;
|
||||||
|
BOOL _isVerticalOrientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-(instancetype)initWithFormat: (NSString*)format options: (NSLayoutFormatOptions)options metrics: (NSDictionary*)metrics views: (NSDictionary*)views;
|
||||||
|
|
||||||
|
-(NSArray*)parse;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
526
Source/GSAutoLayoutVFLParser.m
Normal file
526
Source/GSAutoLayoutVFLParser.m
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
#import "GSAutoLayoutVFLParser.h"
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
struct GSObjectOfPredicate {
|
||||||
|
NSNumber *priority;
|
||||||
|
NSView *view;
|
||||||
|
NSLayoutRelation relation;
|
||||||
|
CGFloat constant;
|
||||||
|
};
|
||||||
|
typedef struct GSObjectOfPredicate GSObjectOfPredicate;
|
||||||
|
|
||||||
|
NSInteger const GS_DEFAULT_VIEW_SPACING = 8;
|
||||||
|
NSInteger const GS_DEFAULT_SUPERVIEW_SPACING = 20;
|
||||||
|
|
||||||
|
@implementation GSAutoLayoutVFLParser
|
||||||
|
|
||||||
|
-(instancetype)initWithFormat: (NSString*)format options: (NSLayoutFormatOptions)options metrics: (NSDictionary*)metrics views: (NSDictionary*)views
|
||||||
|
{
|
||||||
|
if (self = [super init]) {
|
||||||
|
if ([format length] == 0) {
|
||||||
|
[self failParseWithMessage:@"Cannot parse an empty string"];
|
||||||
|
}
|
||||||
|
|
||||||
|
_views = views;
|
||||||
|
_metrics = metrics;
|
||||||
|
_options = options;
|
||||||
|
|
||||||
|
_scanner = [NSScanner scannerWithString:format];
|
||||||
|
_constraints = [NSMutableArray array];
|
||||||
|
_layoutFormatConstraints = [NSMutableArray array];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray*)parse
|
||||||
|
{
|
||||||
|
[self parseOrientation];
|
||||||
|
NSNumber *spacingConstant = [self parseLeadingSuperViewConnection];
|
||||||
|
NSView *previousView = nil;
|
||||||
|
|
||||||
|
while (![_scanner isAtEnd]) {
|
||||||
|
NSArray *viewConstraints = [self parseView];
|
||||||
|
if (_createLeadingConstraintToSuperview) {
|
||||||
|
[self addLeadingSuperviewConstraint: spacingConstant];
|
||||||
|
_createLeadingConstraintToSuperview = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousView != nil) {
|
||||||
|
[self addViewSpacingConstraint:spacingConstant previousView:previousView];
|
||||||
|
[self addFormattingConstraints: previousView];
|
||||||
|
}
|
||||||
|
[_constraints addObjectsFromArray:viewConstraints];
|
||||||
|
|
||||||
|
spacingConstant = [self parseConnection];
|
||||||
|
if ([_scanner scanString:@"|" intoString:nil]) {
|
||||||
|
[self addTrailingToSuperviewConstraint: spacingConstant];
|
||||||
|
}
|
||||||
|
previousView = _view;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_constraints addObjectsFromArray:_layoutFormatConstraints];
|
||||||
|
|
||||||
|
return _constraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)addFormattingConstraints: (NSView*)lastView
|
||||||
|
{
|
||||||
|
BOOL hasFormatOptions = (_options & NSLayoutFormatAlignmentMask) > 0;
|
||||||
|
if (!hasFormatOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self assertHasValidFormatLayoutOptions];
|
||||||
|
|
||||||
|
NSArray *attributes = [self layoutAttributesForLayoutFormatOptions:_options];
|
||||||
|
for (NSNumber *layoutAttribute in attributes) {
|
||||||
|
NSLayoutConstraint *formatConstraint = [NSLayoutConstraint constraintWithItem:lastView attribute:[layoutAttribute integerValue] relatedBy:NSLayoutRelationEqual toItem:_view attribute:[layoutAttribute integerValue] multiplier:1.0 constant:0];
|
||||||
|
[_layoutFormatConstraints addObject:formatConstraint];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)assertHasValidFormatLayoutOptions
|
||||||
|
{
|
||||||
|
if (_isVerticalOrientation && [self isVerticalEdgeFormatLayoutOption: _options]) {
|
||||||
|
[self failParseWithMessage:@"A vertical alignment format option cannot be used with a vertical layout"];
|
||||||
|
} else if (!_isVerticalOrientation && ![self isVerticalEdgeFormatLayoutOption:_options]) {
|
||||||
|
[self failParseWithMessage:@"A horizontal alignment format option cannot be used with a horizontal layout"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)parseOrientation
|
||||||
|
{
|
||||||
|
if ([_scanner scanString:@"V:" intoString:nil]) {
|
||||||
|
_isVerticalOrientation = true;
|
||||||
|
} else {
|
||||||
|
[_scanner scanString:@"H:" intoString:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray*)parseView
|
||||||
|
{
|
||||||
|
[self parseViewOpen];
|
||||||
|
|
||||||
|
_view = [self parseViewName];
|
||||||
|
NSArray *viewConstraints = [self parsePredicateList];
|
||||||
|
[self parseViewClose];
|
||||||
|
|
||||||
|
return viewConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSView*)parseViewName
|
||||||
|
{
|
||||||
|
NSString *viewName = nil;
|
||||||
|
NSCharacterSet *viewTerminators = [NSCharacterSet characterSetWithCharactersInString:@"]("];
|
||||||
|
[_scanner scanUpToCharactersFromSet:viewTerminators intoString:&viewName];
|
||||||
|
|
||||||
|
if (viewName == nil) {
|
||||||
|
[self failParseWithMessage:@"Failed to parse view name"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![self isValidIdentifer:viewName]) {
|
||||||
|
[self failParseWithMessage:@"Invalid view name. A view name must be a valid C identifier and may only contain letters, numbers and underscores"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self resolveViewWithIdentifier:viewName];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)isVerticalEdgeFormatLayoutOption: (NSLayoutFormatOptions)options
|
||||||
|
{
|
||||||
|
if (options & NSLayoutFormatAlignAllTop) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllBaseline) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllFirstBaseline) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllBottom) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllCenterY) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)addViewSpacingConstraint: (NSNumber*)spacing previousView: (NSView*)previousView
|
||||||
|
{
|
||||||
|
CGFloat viewSpacingConstant = spacing ? [spacing doubleValue] : GS_DEFAULT_VIEW_SPACING;
|
||||||
|
NSLayoutAttribute firstAttribute;
|
||||||
|
NSLayoutAttribute secondAttribute;
|
||||||
|
NSView *firstItem;
|
||||||
|
NSView *secondItem;
|
||||||
|
|
||||||
|
NSLayoutFormatOptions directionOptions = _options & NSLayoutFormatDirectionMask;
|
||||||
|
if (_isVerticalOrientation) {
|
||||||
|
firstAttribute = NSLayoutAttributeTop;
|
||||||
|
secondAttribute = NSLayoutAttributeBottom;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = previousView;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionRightToLeft) {
|
||||||
|
firstAttribute = NSLayoutAttributeLeft;
|
||||||
|
secondAttribute = NSLayoutAttributeRight;
|
||||||
|
firstItem = previousView;
|
||||||
|
secondItem = _view;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionLeftToRight) {
|
||||||
|
firstAttribute = NSLayoutAttributeLeft;
|
||||||
|
secondAttribute = NSLayoutAttributeRight;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = previousView;
|
||||||
|
} else {
|
||||||
|
firstAttribute = NSLayoutAttributeLeading;
|
||||||
|
secondAttribute = NSLayoutAttributeTrailing;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = previousView;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLayoutConstraint *viewSeparatorConstraint = [NSLayoutConstraint constraintWithItem:firstItem attribute:firstAttribute relatedBy:NSLayoutRelationEqual toItem:secondItem attribute:secondAttribute multiplier:1.0 constant:viewSpacingConstant];
|
||||||
|
|
||||||
|
[_constraints addObject:viewSeparatorConstraint];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)addLeadingSuperviewConstraint: (NSNumber*)spacing
|
||||||
|
{
|
||||||
|
NSLayoutAttribute firstAttribute;
|
||||||
|
NSView *firstItem;
|
||||||
|
NSView *secondItem;
|
||||||
|
|
||||||
|
NSLayoutFormatOptions directionOptions = _options & NSLayoutFormatDirectionMask;
|
||||||
|
if (_isVerticalOrientation) {
|
||||||
|
firstAttribute = NSLayoutAttributeTop;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = _view.superview;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionRightToLeft) {
|
||||||
|
firstAttribute = NSLayoutAttributeRight;
|
||||||
|
firstItem = _view.superview;
|
||||||
|
secondItem = _view;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionLeftToRight) {
|
||||||
|
firstAttribute = NSLayoutAttributeLeft;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = _view.superview;
|
||||||
|
} else {
|
||||||
|
firstAttribute = _isVerticalOrientation ? NSLayoutAttributeTop : NSLayoutAttributeLeading;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = _view.superview;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGFloat viewSpacingConstant = spacing ? [spacing doubleValue] : GS_DEFAULT_SUPERVIEW_SPACING;
|
||||||
|
|
||||||
|
NSLayoutConstraint *leadingConstraintToSuperview = [NSLayoutConstraint constraintWithItem:firstItem attribute:firstAttribute relatedBy:NSLayoutRelationEqual toItem:secondItem attribute:firstAttribute multiplier:1.0 constant:viewSpacingConstant];
|
||||||
|
[_constraints addObject:leadingConstraintToSuperview];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)addTrailingToSuperviewConstraint: (NSNumber*)spacing
|
||||||
|
{
|
||||||
|
CGFloat viewSpacingConstant = spacing ? [spacing doubleValue] : GS_DEFAULT_SUPERVIEW_SPACING;
|
||||||
|
|
||||||
|
NSLayoutFormatOptions directionOptions = _options & NSLayoutFormatDirectionMask;
|
||||||
|
NSLayoutAttribute attribute;
|
||||||
|
NSView *firstItem;
|
||||||
|
NSView *secondItem;
|
||||||
|
|
||||||
|
if (_isVerticalOrientation) {
|
||||||
|
attribute = NSLayoutAttributeBottom;
|
||||||
|
firstItem = _view.superview;
|
||||||
|
secondItem = _view;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionRightToLeft) {
|
||||||
|
attribute = NSLayoutAttributeLeft;
|
||||||
|
firstItem = _view;
|
||||||
|
secondItem = _view.superview;
|
||||||
|
} else if (directionOptions & NSLayoutFormatDirectionLeftToRight) {
|
||||||
|
attribute = NSLayoutAttributeRight;
|
||||||
|
firstItem = _view.superview;
|
||||||
|
secondItem = _view;
|
||||||
|
} else {
|
||||||
|
attribute = NSLayoutAttributeTrailing;
|
||||||
|
firstItem = _view.superview;
|
||||||
|
secondItem = _view;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSLayoutConstraint *trailingConstraintToSuperview = [NSLayoutConstraint constraintWithItem: firstItem attribute:attribute relatedBy:NSLayoutRelationEqual toItem: secondItem attribute:attribute multiplier:1.0 constant:viewSpacingConstant];
|
||||||
|
[_constraints addObject:trailingConstraintToSuperview];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)parseLeadingSuperViewConnection
|
||||||
|
{
|
||||||
|
BOOL foundSuperview = [_scanner scanString:@"|" intoString:nil];
|
||||||
|
if (!foundSuperview) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
_createLeadingConstraintToSuperview = YES;
|
||||||
|
return [self parseConnection];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)parseConnection
|
||||||
|
{
|
||||||
|
BOOL foundConnection = [_scanner scanString:@"-" intoString:nil];
|
||||||
|
if (!foundConnection) {
|
||||||
|
return [NSNumber numberWithDouble:0];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNumber *simplePredicateValue = [self parseSimplePredicate];
|
||||||
|
BOOL endConnectionFound = [_scanner scanString:@"-" intoString:nil];
|
||||||
|
|
||||||
|
if (simplePredicateValue != nil && !endConnectionFound) {
|
||||||
|
[self failParseWithMessage:@"A connection must end with a '-'"];
|
||||||
|
} else if (simplePredicateValue == nil && endConnectionFound) {
|
||||||
|
[self failParseWithMessage:@"Found invalid connection"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return simplePredicateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)parseSimplePredicate
|
||||||
|
{
|
||||||
|
float constant;
|
||||||
|
BOOL scanConstantResult = [_scanner scanFloat:&constant];
|
||||||
|
if (scanConstantResult) {
|
||||||
|
return [NSNumber numberWithDouble:constant];
|
||||||
|
} else {
|
||||||
|
NSString *metricName = nil;
|
||||||
|
NSCharacterSet *simplePredicateTerminatorsCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"-[|"];
|
||||||
|
BOOL didParseMetricName = [_scanner scanUpToCharactersFromSet:simplePredicateTerminatorsCharacterSet intoString:&metricName];
|
||||||
|
if (!didParseMetricName) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
if (![self isValidIdentifer:metricName]) {
|
||||||
|
[self failParseWithMessage:@"Invalid metric identifier. Metric identifiers must be a valid C identifier and may only contain letters, numbers and underscores"];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNumber *metric = [self resolveMetricWithIdentifier:metricName];
|
||||||
|
return metric;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray*)parsePredicateList
|
||||||
|
{
|
||||||
|
BOOL startsWithPredicateList = [_scanner scanString:@"(" intoString:nil];
|
||||||
|
if (!startsWithPredicateList) {
|
||||||
|
return [NSArray array];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableArray *viewPredicateConstraints = [NSMutableArray array];
|
||||||
|
BOOL shouldParsePredicate = YES;
|
||||||
|
while (shouldParsePredicate) {
|
||||||
|
GSObjectOfPredicate *predicate = [self parseObjectOfPredicate];
|
||||||
|
[viewPredicateConstraints addObject:[self createConstraintFromParsedPredicate:predicate]];
|
||||||
|
[self freeObjectOfPredicate:predicate];
|
||||||
|
|
||||||
|
shouldParsePredicate = [_scanner scanString:@"," intoString:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (![_scanner scanString:@")" intoString:nil]) {
|
||||||
|
[self failParseWithMessage:@"A predicate on a view must end with ')'"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewPredicateConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSLayoutConstraint*)createConstraintFromParsedPredicate: (GSObjectOfPredicate*)predicate
|
||||||
|
{
|
||||||
|
NSLayoutConstraint *constraint = nil;
|
||||||
|
NSLayoutAttribute attribute = _isVerticalOrientation ? NSLayoutAttributeHeight : NSLayoutAttributeWidth;
|
||||||
|
if (predicate->view != nil) {
|
||||||
|
constraint = [NSLayoutConstraint constraintWithItem:_view attribute:attribute relatedBy:predicate->relation toItem:predicate->view attribute:attribute multiplier:1.0 constant:predicate->constant];
|
||||||
|
} else {
|
||||||
|
constraint = [NSLayoutConstraint constraintWithItem:_view attribute:attribute relatedBy:predicate->relation toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:predicate->constant];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predicate->priority) {
|
||||||
|
constraint.priority = [predicate->priority doubleValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraint;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(GSObjectOfPredicate*)parseObjectOfPredicate
|
||||||
|
{
|
||||||
|
NSLayoutRelation relation = [self parseRelation];
|
||||||
|
|
||||||
|
CGFloat parsedConstant;
|
||||||
|
NSView *predicatedView = nil;
|
||||||
|
BOOL scanConstantResult = [_scanner scanDouble:&parsedConstant];
|
||||||
|
if (!scanConstantResult) {
|
||||||
|
NSString *identiferName = [self parseIdentifier];
|
||||||
|
if (![self isValidIdentifer:identiferName]) {
|
||||||
|
[self failParseWithMessage:@"Invalid metric or view identifier. Metric/View identifiers must be a valid C identifier and may only contain letters, numbers and underscores"];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNumber *metric = [_metrics objectForKey:identiferName];
|
||||||
|
if (metric != nil) {
|
||||||
|
parsedConstant = [metric doubleValue];
|
||||||
|
} else if ([_views objectForKey:identiferName]) {
|
||||||
|
parsedConstant = 0;
|
||||||
|
predicatedView = [_views objectForKey:identiferName];
|
||||||
|
} else {
|
||||||
|
NSString *message = [NSString stringWithFormat:@"Failed to find constant or metric for identifier '%@'", identiferName];
|
||||||
|
[self failParseWithMessage:message];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNumber *priorityValue = [self parsePriority];
|
||||||
|
|
||||||
|
GSObjectOfPredicate *predicate = calloc(1, sizeof(GSObjectOfPredicate));
|
||||||
|
predicate->priority = priorityValue;
|
||||||
|
predicate->relation = relation;
|
||||||
|
predicate->constant = parsedConstant;
|
||||||
|
predicate->view = predicatedView;
|
||||||
|
|
||||||
|
return predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSLayoutRelation)parseRelation
|
||||||
|
{
|
||||||
|
if ([_scanner scanString:@"==" intoString:nil]) {
|
||||||
|
return NSLayoutRelationEqual;
|
||||||
|
} else if ([_scanner scanString:@">=" intoString:nil]) {
|
||||||
|
return NSLayoutRelationGreaterThanOrEqual;
|
||||||
|
} else if ([_scanner scanString:@"<=" intoString:nil]) {
|
||||||
|
return NSLayoutRelationLessThanOrEqual;
|
||||||
|
} else {
|
||||||
|
return NSLayoutRelationEqual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)parsePriority
|
||||||
|
{
|
||||||
|
NSCharacterSet *priorityMarkerCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"@"];
|
||||||
|
BOOL foundPriorityMarker = [_scanner scanCharactersFromSet:priorityMarkerCharacterSet intoString:nil];
|
||||||
|
if (!foundPriorityMarker) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self parseConstant];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)resolveMetricWithIdentifier: (NSString*)identifier
|
||||||
|
{
|
||||||
|
NSNumber *metric = [_metrics objectForKey:identifier];
|
||||||
|
if (metric == nil) {
|
||||||
|
[self failParseWithMessage:@"Found metric not inside metric dictionary"];
|
||||||
|
}
|
||||||
|
return metric;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSView*)resolveViewWithIdentifier: (NSString*)identifier
|
||||||
|
{
|
||||||
|
NSView *view = [_views objectForKey:identifier];
|
||||||
|
if (view == nil) {
|
||||||
|
[self failParseWithMessage:@"Found view not inside view dictionary"];
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSNumber*)parseConstant
|
||||||
|
{
|
||||||
|
CGFloat constant;
|
||||||
|
BOOL scanConstantResult = [_scanner scanDouble:&constant];
|
||||||
|
if (scanConstantResult) {
|
||||||
|
return [NSNumber numberWithFloat:constant];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *metricName = [self parseIdentifier];
|
||||||
|
if (![self isValidIdentifer:metricName]) {
|
||||||
|
[self failParseWithMessage:@"Invalid metric identifier. Metric identifiers must be a valid C identifier and may only contain letters, numbers and underscores"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self resolveMetricWithIdentifier:metricName];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSString*)parseIdentifier
|
||||||
|
{
|
||||||
|
NSString *identifierName = nil;
|
||||||
|
NSCharacterSet *identifierTerminators = [NSCharacterSet characterSetWithCharactersInString:@"),"];
|
||||||
|
BOOL scannedIdentifier = [_scanner scanUpToCharactersFromSet:identifierTerminators intoString:&identifierName];
|
||||||
|
if (!scannedIdentifier) {
|
||||||
|
[self failParseWithMessage:@"Failed to find constant or metric"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifierName;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)parseViewOpen
|
||||||
|
{
|
||||||
|
NSCharacterSet *openViewIdentifier = [NSCharacterSet characterSetWithCharactersInString:@"["];
|
||||||
|
BOOL scannedOpenBracket = [_scanner scanCharactersFromSet:openViewIdentifier intoString:nil];
|
||||||
|
if (!scannedOpenBracket) {
|
||||||
|
[[NSException exceptionWithName:NSInternalInconsistencyException reason:@"A view must start with a '['" userInfo:nil] raise];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)parseViewClose
|
||||||
|
{
|
||||||
|
NSCharacterSet *closeViewIdentifier = [NSCharacterSet characterSetWithCharactersInString:@"]"];
|
||||||
|
BOOL scannedCloseBracket = [_scanner scanCharactersFromSet:closeViewIdentifier intoString:nil];
|
||||||
|
if (!scannedCloseBracket) {
|
||||||
|
[[NSException exceptionWithName:NSInternalInconsistencyException reason:@"A view must end with a ']'" userInfo:nil] raise];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(BOOL)isValidIdentifer: (NSString*)identifer
|
||||||
|
{
|
||||||
|
NSRegularExpression *cIdentifierRegex = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z_][a-zA-Z0-9_]*$" options:0 error:nil];
|
||||||
|
NSArray *matches = [cIdentifierRegex matchesInString:identifer options:0 range:NSMakeRange(0, identifer.length)];
|
||||||
|
|
||||||
|
return [matches count] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSArray*)layoutAttributesForLayoutFormatOptions: (NSLayoutFormatOptions)options {
|
||||||
|
NSMutableArray *attributes = [NSMutableArray array];
|
||||||
|
|
||||||
|
if (options & NSLayoutFormatAlignAllLeft) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeLeft]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllRight) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeRight]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllTop) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeTop]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllBottom) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeBottom]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllLeading) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeLeading]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllTrailing) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeTrailing]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllCenterX) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeCenterX]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllCenterY) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeCenterY]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllBaseline) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeBaseline]];
|
||||||
|
}
|
||||||
|
if (options & NSLayoutFormatAlignAllFirstBaseline) {
|
||||||
|
[attributes addObject:[NSNumber numberWithInteger:NSLayoutAttributeFirstBaseline]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([attributes count] == 0) {
|
||||||
|
[self failParseWithMessage:@"Unrecognized layout formatting option"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)failParseWithMessage: (NSString*)parseErrorMessage
|
||||||
|
{
|
||||||
|
NSException *parseException = [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Unable to parse constraint format: %@", parseErrorMessage] userInfo:nil];
|
||||||
|
[parseException raise];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-(void)freeObjectOfPredicate: (GSObjectOfPredicate*)predicate
|
||||||
|
{
|
||||||
|
predicate->view = nil;
|
||||||
|
predicate->priority = nil;
|
||||||
|
free(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -32,6 +32,7 @@
|
||||||
#import "AppKit/NSLayoutConstraint.h"
|
#import "AppKit/NSLayoutConstraint.h"
|
||||||
#import "AppKit/NSWindow.h"
|
#import "AppKit/NSWindow.h"
|
||||||
#import "AppKit/NSApplication.h"
|
#import "AppKit/NSApplication.h"
|
||||||
|
#import "GSAutoLayoutVFLParser.h"
|
||||||
|
|
||||||
static NSMutableArray *activeConstraints = nil;
|
static NSMutableArray *activeConstraints = nil;
|
||||||
// static NSNotificationCenter *nc = nil;
|
// static NSNotificationCenter *nc = nil;
|
||||||
|
@ -236,8 +237,16 @@ static NSMutableArray *activeConstraints = nil;
|
||||||
metrics: (NSDictionary *)metrics
|
metrics: (NSDictionary *)metrics
|
||||||
views: (NSDictionary *)views
|
views: (NSDictionary *)views
|
||||||
{
|
{
|
||||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity: 10];
|
GSAutoLayoutVFLParser *parser = [[GSAutoLayoutVFLParser alloc]
|
||||||
return array;
|
initWithFormat: fmt
|
||||||
|
options: opt
|
||||||
|
metrics: metrics
|
||||||
|
views: views];
|
||||||
|
NSArray *constraints = [parser parse];
|
||||||
|
|
||||||
|
[parser release];
|
||||||
|
|
||||||
|
return constraints;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype) initWithItem: (id)firstItem
|
- (instancetype) initWithItem: (id)firstItem
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue