libs-gui/Source/GSAutoLayoutEngine.m
2025-03-05 16:08:10 +01:00

1294 lines
47 KiB
Objective-C

/* Copyright (C) 2023 Free Software Foundation, Inc.
By: Benjamin Johnson
Date: 28-2-2023
This file is part of the GNUstep Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110 USA.
*/
#import "GSAutoLayoutEngine.h"
#import "AppKit/NSLayoutConstraint.h"
#import "GSCSConstraint.h"
#import "GSFastEnumeration.h"
#import "NSViewPrivate.h"
enum
{
GSLayoutAttributeNotAnAttribute = 0,
GSLayoutAttributeLeft = 1,
GSLayoutAttributeRight,
GSLayoutAttributeTop,
GSLayoutAttributeBottom,
GSLayoutAttributeLeading,
GSLayoutAttributeTrailing,
GSLayoutAttributeWidth,
GSLayoutAttributeHeight,
GSLayoutAttributeCenterX,
GSLayoutAttributeCenterY,
GSLayoutAttributeLastBaseline,
GSLayoutAttributeBaseline = GSLayoutAttributeLastBaseline,
GSLayoutAttributeFirstBaseline,
GSLayoutAttributeMinX = 32,
GSLayoutAttributeMinY = 33,
GSLayoutAttributeMaxX = 36,
GSLayoutAttributeMaxY = 37
};
typedef NSInteger GSLayoutAttribute;
enum
{
GSLayoutViewAttributeBaselineOffsetFromBottom = 1,
GSLayoutViewAttributeFirstBaselineOffsetFromTop,
GSLayoutViewAttributeIntrinsicWidth,
GSLayoutViewAttributeIntrinsicHeight,
} GSLayoutViewAttribue;
typedef NSInteger GSLayoutViewAttribute;
@interface GSAutoLayoutEngine (PrivateMethods)
- (GSCSConstraint *) solverConstraintForConstraint:
(NSLayoutConstraint *)constraint;
- (void) mapLayoutConstraint: (NSLayoutConstraint *)layoutConstraint
toSolverConstraint: (GSCSConstraint *)solverConstraint;
- (void) updateAlignmentRectsForTrackedViews;
- (void) updateAlignmentRectsForTrackedViewsForSolution:
(GSCSSolution *)solution;
- (BOOL) hasConstraintsForView: (NSView *)view;
- (GSCSConstraint *) getExistingConstraintForAutolayoutConstraint:
(NSLayoutConstraint *)constraint;
- (void) addConstraintAgainstViewConstraintsArray:
(NSLayoutConstraint *)constraint;
- (void) removeConstraintAgainstViewConstraintsArray:
(NSLayoutConstraint *)constraint;
- (void) addSupportingInternalConstraintsToView: (NSView *)view
forAttribute: (NSLayoutAttribute)attribute
constraint: (GSCSConstraint *)constraint;
- (void) removeInternalConstraintsForView: (NSView *)view;
- (void) addSolverConstraint: (GSCSConstraint *)constraint;
- (void) removeSolverConstraint: (GSCSConstraint *)constraint;
- (NSArray *) constraintsForView: (NSView *)view;
- (NSNumber *) indexForView: (NSView *)view;
- (void) notifyViewsOfAlignmentRectChange: (NSArray *)viewsWithChanges;
- (BOOL) solverCanSolveAlignmentRectForView: (NSView *)view
solution: (GSCSSolution *)solution;
- (void) recordAlignmentRect: (NSRect)alignmentRect
forViewIndex: (NSNumber *)viewIndex;
- (NSRect) solverAlignmentRectForView: (NSView *)view
solution: (GSCSSolution *)solution;
- (NSRect) currentAlignmentRectForViewAtIndex: (NSNumber *)viewIndex;
- (BOOL) updateViewAligmentRect: (GSCSSolution *)solution view: (NSView *)view;
- (BOOL) hasAddedWidthAndHeightConstraintsToView: (NSView *)view;
- (GSCSVariable *) variableForView: (NSView *)view
andAttribute: (GSLayoutAttribute)attribute;
- (GSCSVariable *) variableForView: (NSView *)view
andViewAttribute: (GSLayoutViewAttribute)attribute;
- (void) resolveVariableForView: (NSView *)view
attribute: (GSLayoutViewAttribute)attribute;
- (GSCSVariable *) createVariableWithName: (NSString *)name;
- (GSCSVariable *) getExistingVariableForView: (NSView *)view
withAttribute: (GSLayoutAttribute)attribute;
- (GSCSVariable *) getExistingVariableForView: (NSView *)view
withViewAttribute:
(GSLayoutViewAttribute)attribute;
- (GSCSVariable *) variableWithName: (NSString *)variableName;
- (NSString *) getVariableIdentifierForView: (NSView *)view
withAttribute: (GSLayoutAttribute)attribute;
- (NSString *) getAttributeName: (GSLayoutAttribute)attribute;
- (NSString *) getLayoutViewAttributeName: (GSLayoutViewAttribute)attribute;
- (NSString *) getDynamicVariableIdentifierForView: (NSView *)view
withViewAttribute:
(GSLayoutViewAttribute)attribute;
- (NSString *) getIdentifierForView: (NSView *)view;
- (NSInteger) registerView: (NSView *)view;
- (void) addInternalWidthConstraintForView: (NSView *)view;
- (void) addInternalHeightConstraintForView: (NSView *)view;
- (void) addInternalLeadingConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalTrailingConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalLeftConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalRightConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalBottomConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalTopConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalCenterXConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalCenterYConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalFirstBaselineConstraintsForView: (NSView *)view
constraint:
(GSCSConstraint *)constraint;
- (void) addInternalBaselineConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint;
- (void) addInternalSolverConstraint: (GSCSConstraint *)constraint
forView: (NSView *)view;
- (void) addIntrinsicContentSizeConstraintsToView: (NSView *)view;
- (void) addSupportingIntrinsicSizeConstraintsToView: (NSView *)view
orientation:
(NSLayoutConstraintOrientation)
orientation
intrinsicSizeAttribute:
(GSLayoutViewAttribute)intrinsicSizeAttribute
dimensionAttribute:
(GSLayoutAttribute)dimensionAttribute;
- (void) addSupportingConstraintForLayoutViewAttribute:
(GSLayoutViewAttribute)attribute
view: (NSView *)view
constraint:
(GSCSConstraint *)constraint;
- (GSCSConstraint *) solverConstraintForRelationalConstraint:
(NSLayoutConstraint *)constraint;
- (GSCSConstraint *) solverConstraintForNonRelationalConstraint:
(NSLayoutConstraint *)constraint;
- (void) addObserverToConstraint: (NSLayoutConstraint *)constranit;
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary *)change
context: (void *)context;
- (void) updateConstraint: (NSLayoutConstraint *)constraint;
- (void) addSupportingSolverConstraint: (GSCSConstraint *)supportingConstraint
forSolverConstraint: (GSCSConstraint *)constraint;
- (CGFloat) valueForView: (NSView *)view
attribute: (GSLayoutViewAttribute)attribute;
- (int) getConstantMultiplierForLayoutAttribute: (NSLayoutAttribute)attribute;
@end
@implementation GSAutoLayoutEngine
- (instancetype) initWithSolver: (GSCassowarySolver *)cassowarySolver;
{
self = [super init];
if (self != nil)
{
ASSIGN(_solver, cassowarySolver);
ASSIGN(_variablesByKey, [NSMapTable strongToStrongObjectsMapTable]);
ASSIGN(_constraintsByAutoLayoutConstaintHash, [NSMapTable strongToStrongObjectsMapTable]);
ASSIGN(_layoutConstraintsBySolverConstraint, [NSMapTable strongToStrongObjectsMapTable]);
ASSIGN(_solverConstraints, [NSMutableArray array]);
ASSIGN(_trackedViews, [NSMutableArray array]);
ASSIGN(_supportingConstraintsByConstraint, [NSMapTable strongToStrongObjectsMapTable]);
ASSIGN(_viewAlignmentRectByViewIndex, [NSMutableDictionary dictionary]);
ASSIGN(_viewIndexByViewHash, [NSMutableDictionary dictionary]);
ASSIGN(_constraintsByViewIndex, [NSMutableDictionary dictionary]);
ASSIGN(_internalConstraintsByViewIndex, [NSMapTable strongToStrongObjectsMapTable]);
}
return self;
}
- (instancetype) init
{
GSCassowarySolver *solver = [[GSCassowarySolver alloc] init];
self = [self initWithSolver: solver];
RELEASE(solver);
return self;
}
- (void) addConstraints: (NSArray *)constraints
{
FOR_IN(NSLayoutConstraint *, constraint, constraints)
[self addConstraint: constraint];
END_FOR_IN(constraints);
}
- (void) addConstraint: (NSLayoutConstraint *)constraint
{
GSCSConstraint *solverConstraint =
[self solverConstraintForConstraint: constraint];
[self mapLayoutConstraint: constraint toSolverConstraint: solverConstraint];
[self addSupportingInternalConstraintsToView: [constraint firstItem]
forAttribute: [constraint firstAttribute]
constraint: solverConstraint];
if ([constraint secondItem])
{
[self
addSupportingInternalConstraintsToView: [constraint secondItem]
forAttribute: [constraint secondAttribute]
constraint: solverConstraint];
}
[self addSolverConstraint: solverConstraint];
[self updateAlignmentRectsForTrackedViews];
[self addConstraintAgainstViewConstraintsArray: constraint];
}
- (void) removeConstraint: (NSLayoutConstraint *)constraint
{
GSCSConstraint *solverConstraint =
[self getExistingConstraintForAutolayoutConstraint: constraint];
if (solverConstraint == nil)
{
return;
}
[self removeSolverConstraint: solverConstraint];
NSArray *internalConstraints =
[_supportingConstraintsByConstraint objectForKey: solverConstraint];
if (internalConstraints)
{
FOR_IN(GSCSConstraint *, internalConstraint, internalConstraints)
[self removeSolverConstraint: internalConstraint];
END_FOR_IN(internalConstraints);
}
[_supportingConstraintsByConstraint setObject: nil forKey: solverConstraint];
[self updateAlignmentRectsForTrackedViews];
[self removeConstraintAgainstViewConstraintsArray: constraint];
if ([self hasConstraintsForView: [constraint firstItem]])
{
[self removeInternalConstraintsForView: [constraint firstItem]];
}
if ([constraint secondItem] != nil &&
[self hasConstraintsForView: [constraint secondItem]])
{
[self removeInternalConstraintsForView: [constraint secondItem]];
}
}
- (void) removeConstraints: (NSArray *)constraints
{
FOR_IN(NSLayoutConstraint *, constraint, constraints)
[self removeConstraint: constraint];
END_FOR_IN(constraints);
}
- (GSCSConstraint *) solverConstraintForConstraint:
(NSLayoutConstraint *)constraint
{
if ([constraint secondItem] == nil)
{
return [self solverConstraintForNonRelationalConstraint: constraint];
}
else
{
return [self solverConstraintForRelationalConstraint: constraint];
}
}
- (GSCSConstraint *) solverConstraintForNonRelationalConstraint:
(NSLayoutConstraint *)constraint
{
GSCSVariable *firstItemConstraintVariable =
[self variableForView: [constraint firstItem]
andAttribute: (GSLayoutAttribute)[constraint firstAttribute]];
GSCSConstraint *newConstraint;
switch ([constraint relation])
{
case NSLayoutRelationLessThanOrEqual:
newConstraint = [GSCSConstraint constraintWithLeftVariable: firstItemConstraintVariable operator: GSCSConstraintOperatorLessThanOrEqual rightConstant: [constraint constant]];
break;
case NSLayoutRelationEqual:
default:
newConstraint = [GSCSConstraint constraintWithLeftVariable: firstItemConstraintVariable operator: GSCSConstraintOperatorEqual rightConstant: [constraint constant]];
break;
case NSLayoutRelationGreaterThanOrEqual:
newConstraint = [GSCSConstraint constraintWithLeftVariable: firstItemConstraintVariable operator: GSCSConstraintOperationGreaterThanOrEqual rightConstant: [constraint constant]];
break;
}
GSCSStrength *strength =
[[GSCSStrength alloc] initWithName: nil strength: [constraint priority]];
[newConstraint setStrength: strength];
RELEASE(strength);
return newConstraint;
}
- (GSCSConstraint *) solverConstraintForRelationalConstraint:
(NSLayoutConstraint *)constraint
{
GSCSVariable *firstItemConstraintVariable =
[self variableForView: [constraint firstItem]
andAttribute: (GSLayoutAttribute)[constraint firstAttribute]];
GSCSVariable *secondItemConstraintVariable =
[self variableForView: [constraint secondItem]
andAttribute: (GSLayoutAttribute)[constraint secondAttribute]];
CGFloat multiplier = [constraint multiplier];
GSCSConstraintOperator op = GSCSConstraintOperatorEqual;
switch ([constraint relation])
{
case NSLayoutRelationEqual:
op = GSCSConstraintOperatorEqual;
break;
case NSLayoutRelationLessThanOrEqual:
op = GSCSConstraintOperatorLessThanOrEqual;
break;
case NSLayoutRelationGreaterThanOrEqual:
op = GSCSConstraintOperationGreaterThanOrEqual;
break;
}
double constant =
[self getConstantMultiplierForLayoutAttribute: [constraint secondAttribute]] * [constraint constant];
GSCSLinearExpression *rightExpression = [[GSCSLinearExpression alloc]
initWithVariable: secondItemConstraintVariable
coefficient: multiplier
constant: constant];
GSCSConstraint *newConstraint = [GSCSConstraint constraintWithLeftVariable: firstItemConstraintVariable operator: op rightExpression: rightExpression];
RELEASE(rightExpression);
GSCSStrength *strength =
[[GSCSStrength alloc] initWithName: nil strength: [constraint priority]];
[newConstraint setStrength: strength];
RELEASE(strength);
return newConstraint;
}
- (int) getConstantMultiplierForLayoutAttribute: (NSLayoutAttribute)attribute
{
switch (attribute)
{
case NSLayoutAttributeTop:
return -1;
case NSLayoutAttributeBottom:
return 1;
case NSLayoutAttributeLeading:
return 1;
case NSLayoutAttributeTrailing:
return -1;
case NSLayoutAttributeLeft:
return 1;
case NSLayoutAttributeRight:
return -1;
default:
return 1;
}
}
- (GSCSVariable *) variableForView: (NSView *)view
andViewAttribute: (GSLayoutViewAttribute)attribute
{
NSString *variableName = [self getDynamicVariableIdentifierForView: view
withViewAttribute: attribute];
GSCSVariable *existingVariable = [self variableWithName: variableName];
if (existingVariable != nil)
{
return existingVariable;
}
else
{
return [self createVariableWithName: variableName];
}
}
- (GSCSVariable *) variableForView: (NSView *)view
andAttribute: (GSLayoutAttribute)attribute
{
NSString *variableName =
[self getVariableIdentifierForView: view withAttribute: attribute];
GSCSVariable *existingVariable = [self variableWithName: variableName];
if (existingVariable != nil)
{
return existingVariable;
}
else
{
return [self createVariableWithName: variableName];
}
}
- (GSCSVariable *) getExistingVariableForView: (NSView *)view
withAttribute: (GSLayoutAttribute)attribute
{
NSString *variableIdentifier =
[self getVariableIdentifierForView: view
withAttribute: (GSLayoutAttribute)attribute];
return [self variableWithName: variableIdentifier];
}
- (GSCSVariable *) createVariableWithName: (NSString *)name
{
GSCSVariable *variable = [GSCSVariable variableWithValue: 0 name: name];
[_variablesByKey setObject: variable forKey: name];
return variable;
}
- (GSCSVariable *) getExistingVariableForView: (NSView *)view
withViewAttribute: (GSLayoutViewAttribute)attribute
{
NSString *variableIdentifier =
[self getDynamicVariableIdentifierForView: view
withViewAttribute: attribute];
return [self variableWithName: variableIdentifier];
}
- (GSCSVariable *) variableWithName: (NSString *)variableName
{
return [_variablesByKey objectForKey: variableName];
}
- (NSString *) getVariableIdentifierForView: (NSView *)view
withAttribute: (GSLayoutAttribute)attribute
{
NSString *viewIdentifier = [self getIdentifierForView: view];
return [NSString stringWithFormat: @"%@.%@", viewIdentifier,
[self getAttributeName: attribute]];
}
- (NSString *) getAttributeName: (GSLayoutAttribute)attribute
{
switch (attribute)
{
case GSLayoutAttributeTop:
return @"top";
case GSLayoutAttributeBottom:
return @"bottom";
case GSLayoutAttributeLeading:
return @"leading";
case GSLayoutAttributeLeft:
return @"left";
case GSLayoutAttributeRight:
return @"right";
case GSLayoutAttributeTrailing:
return @"trailing";
case GSLayoutAttributeHeight:
return @"height";
case GSLayoutAttributeWidth:
return @"width";
case GSLayoutAttributeCenterX:
return @"centerX";
case GSLayoutAttributeCenterY:
return @"centerY";
case GSLayoutAttributeBaseline:
return @"baseline";
case GSLayoutAttributeFirstBaseline:
return @"firstBaseline";
case GSLayoutAttributeMinX:
return @"minX";
case GSLayoutAttributeMinY:
return @"minY";
case GSLayoutAttributeMaxX:
return @"maxX";
case GSLayoutAttributeMaxY:
return @"maxY";
default:
[[NSException exceptionWithName: @"Not handled"
reason: @"GSLayoutAttribute not handled"
userInfo: nil] raise];
return nil;
}
}
- (NSString *) getLayoutViewAttributeName: (GSLayoutViewAttribute)attribute
{
switch (attribute)
{
case GSLayoutViewAttributeBaselineOffsetFromBottom:
return @"baselineOffsetFromBottom";
case GSLayoutViewAttributeFirstBaselineOffsetFromTop:
return @"firstBaselineOffsetFromTop";
case GSLayoutViewAttributeIntrinsicWidth:
return @"intrinsicContentSize.width";
case GSLayoutViewAttributeIntrinsicHeight:
return @"intrinsicContentSize.height";
default:
[[NSException
exceptionWithName: @"GSLayoutViewAttribute Not handled"
reason: @"The provided GSLayoutViewAttribute does "
@"not have a name"
userInfo: nil] raise];
return nil;
}
}
- (NSString *) getDynamicVariableIdentifierForView: (NSView *)view
withViewAttribute:
(GSLayoutViewAttribute)attribute
{
NSString *viewIdentifier = [self getIdentifierForView: view];
return
[NSString stringWithFormat: @"%@.%@", viewIdentifier,
[self getLayoutViewAttributeName: attribute]];
}
- (NSString *) getIdentifierForView: (NSView *)view
{
NSUInteger viewIndex;
NSNumber *existingViewIndex = [self indexForView: view];
if (existingViewIndex)
{
viewIndex = [existingViewIndex unsignedIntegerValue];
}
else
{
viewIndex = [self registerView: view];
}
return [NSString stringWithFormat: @"view%ld", (long)viewIndex];
}
- (NSInteger) registerView: (NSView *)view
{
NSUInteger viewIndex = [_trackedViews count];
[_trackedViews addObject: view];
[_viewIndexByViewHash
setObject: [NSNumber numberWithUnsignedInteger: viewIndex]
forKey: [NSNumber numberWithUnsignedInteger: [view hash]]];
return viewIndex;
}
- (void) mapLayoutConstraint: (NSLayoutConstraint *)layoutConstraint
toSolverConstraint: (GSCSConstraint *)solverConstraint
{
[_constraintsByAutoLayoutConstaintHash setObject: solverConstraint
forKey: layoutConstraint];
[_layoutConstraintsBySolverConstraint setObject: layoutConstraint
forKey: solverConstraint];
}
- (void) addSupportingInternalConstraintsToView: (NSView *)view
forAttribute: (NSLayoutAttribute)attribute
constraint: (GSCSConstraint *)constraint
{
if (![self hasAddedWidthAndHeightConstraintsToView: view])
{
[self addInternalWidthConstraintForView: view];
[self addInternalHeightConstraintForView: view];
[self addIntrinsicContentSizeConstraintsToView: view];
}
switch (attribute)
{
case NSLayoutAttributeTrailing:
[self addInternalTrailingConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeLeading:
[self addInternalLeadingConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeLeft:
[self addInternalLeftConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeRight:
[self addInternalRightConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeTop:
[self addInternalTopConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeBottom:
[self addInternalBottomConstraintForView: view
constraint: constraint];
break;
case NSLayoutAttributeCenterX:
[self addInternalCenterXConstraintsForView: view
constraint: constraint];
break;
case NSLayoutAttributeCenterY:
[self addInternalCenterYConstraintsForView: view
constraint: constraint];
break;
case NSLayoutAttributeBaseline:
[self addInternalBaselineConstraintsForView: view
constraint: constraint];
break;
case NSLayoutAttributeFirstBaseline:
[self addInternalFirstBaselineConstraintsForView: view
constraint: constraint];
default:
break;
}
}
- (BOOL) hasAddedWidthAndHeightConstraintsToView: (NSView *)view
{
NSArray *added = [_internalConstraintsByViewIndex objectForKey: view];
return added != nil;
}
- (void) addInternalWidthConstraintForView: (NSView *)view
{
GSCSVariable *widthConstraintVariable =
[self variableForView: view andAttribute: GSLayoutAttributeWidth];
GSCSVariable *minX = [self variableForView: view
andAttribute: GSLayoutAttributeMinX];
GSCSVariable *maxX = [self variableForView: view
andAttribute: GSLayoutAttributeMaxX];
GSCSLinearExpression *maxXMinusMinX =
[[GSCSLinearExpression alloc] initWithVariable: maxX];
[maxXMinusMinX addVariable: minX coefficient: -1];
GSCSConstraint *widthRelationshipToMaxXAndMinXConstraint = [GSCSConstraint constraintWithLeftVariable: widthConstraintVariable operator: GSCSConstraintOperatorEqual rightExpression: maxXMinusMinX];
[self addInternalSolverConstraint: widthRelationshipToMaxXAndMinXConstraint
forView: view];
RELEASE(maxXMinusMinX);
}
- (void) addInternalHeightConstraintForView: (NSView *)view
{
GSCSVariable *heightConstraintVariable =
[self variableForView: view andAttribute: GSLayoutAttributeHeight];
GSCSVariable *minY = [self variableForView: view
andAttribute: GSLayoutAttributeMinY];
GSCSVariable *maxY = [self variableForView: view
andAttribute: GSLayoutAttributeMaxY];
GSCSLinearExpression *maxYMinusMinY =
[[GSCSLinearExpression alloc] initWithVariable: maxY];
[maxYMinusMinY addVariable: minY coefficient: -1];
GSCSConstraint *heightConstraint = [GSCSConstraint constraintWithLeftVariable: heightConstraintVariable operator: GSCSConstraintOperatorEqual rightExpression: maxYMinusMinY];
[self addInternalSolverConstraint: heightConstraint forView: view];
RELEASE(maxYMinusMinY);
}
- (void) addInternalLeadingConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *minX = [self variableForView: view
andAttribute: GSLayoutAttributeMinX];
GSCSVariable *leadingVariable =
[self variableForView: view andAttribute: GSLayoutAttributeLeading];
GSCSConstraint *minXLeadingRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: minX operator: GSCSConstraintOperatorEqual rightVariable: leadingVariable];
[self addSupportingSolverConstraint: minXLeadingRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalTrailingConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *trailingVariable =
[self variableForView: view andAttribute: GSLayoutAttributeTrailing];
GSCSVariable *maxX = [self variableForView: view
andAttribute: GSLayoutAttributeMaxX];
GSCSConstraint *maxXTrailingRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: maxX operator: GSCSConstraintOperatorEqual rightVariable: trailingVariable];
[self addSupportingSolverConstraint: maxXTrailingRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalLeftConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *minX = [self variableForView: view
andAttribute: GSLayoutAttributeMinX];
GSCSVariable *leftVariable = [self variableForView: view
andAttribute: GSLayoutAttributeLeft];
GSCSConstraint *minXLeadingRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: minX operator: GSCSConstraintOperatorEqual rightVariable: leftVariable];
[self addSupportingSolverConstraint: minXLeadingRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalRightConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *maxX = [self variableForView: view
andAttribute: GSLayoutAttributeMaxX];
GSCSVariable *rightVariable =
[self variableForView: view andAttribute: GSLayoutAttributeRight];
GSCSConstraint *maxXRightRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: maxX operator: GSCSConstraintOperatorEqual rightVariable: rightVariable];
[self addSupportingSolverConstraint: maxXRightRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalBottomConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *minY = [self variableForView: view
andAttribute: GSLayoutAttributeMinY];
GSCSVariable *bottomVariable =
[self variableForView: view andAttribute: GSLayoutAttributeBottom];
GSCSConstraint *minYBottomRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: minY operator: GSCSConstraintOperatorEqual rightVariable: bottomVariable];
[self addSupportingSolverConstraint: minYBottomRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalTopConstraintForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *maxY = [self variableForView: view
andAttribute: GSLayoutAttributeMaxY];
GSCSVariable *topVariable = [self variableForView: view
andAttribute: GSLayoutAttributeTop];
GSCSConstraint *maxYTopRelationshipConstraint = [GSCSConstraint constraintWithLeftVariable: maxY operator: GSCSConstraintOperatorEqual rightVariable: topVariable];
[self addSupportingSolverConstraint: maxYTopRelationshipConstraint
forSolverConstraint: constraint];
}
- (void) addInternalCenterXConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *centerXVariable =
[self variableForView: view andAttribute: GSLayoutAttributeCenterX];
GSCSVariable *width = [self variableForView: view
andAttribute: GSLayoutAttributeWidth];
GSCSVariable *minX = [self variableForView: view
andAttribute: GSLayoutAttributeMinX];
GSCSLinearExpression *exp =
[[GSCSLinearExpression alloc] initWithVariable: minX];
[exp addVariable: width coefficient: 0.5];
GSCSConstraint *centerXConstraint = [GSCSConstraint constraintWithLeftVariable: centerXVariable operator: GSCSConstraintOperatorEqual rightExpression: exp];
[self addSupportingSolverConstraint: centerXConstraint
forSolverConstraint: constraint];
RELEASE(exp);
}
- (void) addInternalCenterYConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *centerYVariable =
[self variableForView: view andAttribute: GSLayoutAttributeCenterY];
GSCSVariable *height = [self variableForView: view
andAttribute: GSLayoutAttributeHeight];
GSCSVariable *minY = [self variableForView: view
andAttribute: GSLayoutAttributeMinY];
GSCSLinearExpression *exp =
[[GSCSLinearExpression alloc] initWithVariable: minY];
[exp addVariable: height coefficient: 0.5];
GSCSConstraint *centerYConstraint = [GSCSConstraint constraintWithLeftVariable: centerYVariable operator: GSCSConstraintOperatorEqual rightExpression: exp];
RELEASE(exp);
[self addSupportingSolverConstraint: centerYConstraint
forSolverConstraint: constraint];
}
- (void) addInternalFirstBaselineConstraintsForView: (NSView *)view
constraint:
(GSCSConstraint *)constraint
{
GSCSVariable *firstBaselineVariable =
[self variableForView: view
andAttribute: GSLayoutAttributeFirstBaseline];
GSCSVariable *maxY = [self variableForView: view
andAttribute: GSLayoutAttributeMaxY];
GSCSVariable *firstBaselineOffsetVariable =
[self variableForView: view
andViewAttribute: GSLayoutViewAttributeFirstBaselineOffsetFromTop];
GSCSLinearExpression *exp =
[[GSCSLinearExpression alloc] initWithVariable: maxY];
[exp addVariable: firstBaselineOffsetVariable coefficient: -1];
GSCSConstraint *firstBaselineConstraint = [GSCSConstraint constraintWithLeftVariable: firstBaselineVariable operator: GSCSConstraintOperatorEqual rightExpression: exp];
RELEASE(exp);
[self addSupportingConstraintForLayoutViewAttribute:
GSLayoutViewAttributeFirstBaselineOffsetFromTop
view: view
constraint: constraint];
[self addSupportingSolverConstraint: firstBaselineConstraint
forSolverConstraint: constraint];
}
- (void) addInternalBaselineConstraintsForView: (NSView *)view
constraint: (GSCSConstraint *)constraint
{
GSCSVariable *baselineVariable =
[self variableForView: view andAttribute: GSLayoutAttributeBaseline];
GSCSVariable *minY = [self variableForView: view
andAttribute: GSLayoutAttributeMinY];
GSCSVariable *baselineOffsetVariable =
[self variableForView: view
andViewAttribute: GSLayoutViewAttributeBaselineOffsetFromBottom];
[self addSupportingConstraintForLayoutViewAttribute:
GSLayoutViewAttributeBaselineOffsetFromBottom
view: view
constraint: constraint];
GSCSLinearExpression *exp =
[[GSCSLinearExpression alloc] initWithVariable: minY];
[exp addVariable: baselineOffsetVariable];
GSCSConstraint *baselineConstraint = [GSCSConstraint constraintWithLeftVariable: baselineVariable operator: GSCSConstraintOperatorEqual rightExpression: exp];
RELEASE(exp);
[self addSupportingSolverConstraint: baselineConstraint
forSolverConstraint: constraint];
}
- (void) addInternalSolverConstraint: (GSCSConstraint *)constraint
forView: (NSView *)view
{
[self addSolverConstraint: constraint];
NSArray *internalViewConstraints =
[_internalConstraintsByViewIndex objectForKey: view];
if (internalViewConstraints == nil)
{
[_internalConstraintsByViewIndex setObject: [NSMutableArray array]
forKey: view];
}
[[_internalConstraintsByViewIndex objectForKey: view]
addObject: constraint];
}
- (void) addIntrinsicContentSizeConstraintsToView: (NSView *)view
{
NSSize intrinsicContentSize = [view intrinsicContentSize];
if (intrinsicContentSize.width != NSViewNoIntrinsicMetric)
{
[self
addSupportingIntrinsicSizeConstraintsToView: view
orientation: NSLayoutConstraintOrientationHorizontal
intrinsicSizeAttribute: GSLayoutViewAttributeIntrinsicWidth
dimensionAttribute: GSLayoutAttributeWidth];
}
if (intrinsicContentSize.height != NSViewNoIntrinsicMetric)
{
[self
addSupportingIntrinsicSizeConstraintsToView: view
orientation: NSLayoutConstraintOrientationVertical
intrinsicSizeAttribute: GSLayoutViewAttributeIntrinsicHeight
dimensionAttribute: GSLayoutAttributeHeight];
}
}
- (void) addSupportingIntrinsicSizeConstraintsToView: (NSView *)view
orientation: (NSLayoutConstraintOrientation)orientation
intrinsicSizeAttribute: (GSLayoutViewAttribute)intrinsicSizeAttribute
dimensionAttribute: (GSLayoutAttribute)dimensionAttribute
{
GSCSVariable *intrinsicContentDimension =
[self variableForView: view andViewAttribute: intrinsicSizeAttribute];
GSCSVariable *dimensionVariable =
[self variableForView: view andAttribute: dimensionAttribute];
GSCSVariable *intrinsicSizeVariable =
[self getExistingVariableForView: view
withViewAttribute: intrinsicSizeAttribute];
GSCSConstraint *intrinsicSizeConstraint =
[GSCSConstraint editConstraintWithVariable: intrinsicSizeVariable];
[self addInternalSolverConstraint: intrinsicSizeConstraint forView: view];
[self resolveVariableForView: view attribute: intrinsicSizeAttribute];
double huggingPriority =
[view contentHuggingPriorityForOrientation: orientation];
GSCSConstraint *huggingConstraint = [GSCSConstraint constraintWithLeftVariable: dimensionVariable operator: GSCSConstraintOperatorLessThanOrEqual rightVariable: intrinsicContentDimension];
GSCSStrength *huggingConstraintStrength =
[[GSCSStrength alloc] initWithName: nil strength: huggingPriority];
[huggingConstraint setStrength: huggingConstraintStrength];
RELEASE(huggingConstraintStrength);
[self addInternalSolverConstraint: huggingConstraint forView: view];
double compressionPriority =
[view contentCompressionResistancePriorityForOrientation: orientation];
GSCSConstraint *compressionConstraint = [GSCSConstraint constraintWithLeftVariable: dimensionVariable operator: GSCSConstraintOperationGreaterThanOrEqual rightVariable: intrinsicContentDimension];
GSCSStrength *compressionConstraintStrength =
[[GSCSStrength alloc] initWithName: nil strength: compressionPriority];
[compressionConstraint setStrength: compressionConstraintStrength];
RELEASE(compressionConstraintStrength);
[self addInternalSolverConstraint: compressionConstraint forView: view];
}
- (void) addSupportingConstraintForLayoutViewAttribute:
(GSLayoutViewAttribute)attribute
view: (NSView *)view
constraint:
(GSCSConstraint *)constraint
{
GSCSVariable *variable = [self getExistingVariableForView: view
withViewAttribute: attribute];
GSCSConstraint *editConstraint =
[GSCSConstraint editConstraintWithVariable: variable];
[self addSupportingSolverConstraint: editConstraint
forSolverConstraint: constraint];
[self resolveVariableForView: view attribute: attribute];
}
- (void) addObserverToConstraint: (NSLayoutConstraint *)constranit
{
[constranit addObserver: self
forKeyPath: @"constant"
options: NSKeyValueObservingOptionNew
context: nil];
}
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
change: (NSDictionary *)change
context: (void *)context
{
if ([object isKindOfClass: [NSLayoutConstraint class]])
{
NSLayoutConstraint *constraint = (NSLayoutConstraint *)object;
[self updateConstraint: constraint];
}
}
- (void) updateConstraint: (NSLayoutConstraint *)constraint
{
GSCSConstraint *kConstraint =
[self getExistingConstraintForAutolayoutConstraint: constraint];
[self removeSolverConstraint: kConstraint];
GSCSConstraint *newKConstraint =
[self solverConstraintForConstraint: constraint];
[self mapLayoutConstraint: constraint toSolverConstraint: newKConstraint];
[self addSolverConstraint: newKConstraint];
[self updateAlignmentRectsForTrackedViews];
}
- (void) removeInternalConstraintsForView: (NSView *)view
{
NSArray *internalViewConstraints =
[_internalConstraintsByViewIndex objectForKey: view];
FOR_IN(GSCSConstraint *, constraint, internalViewConstraints)
[self removeSolverConstraint: constraint];
END_FOR_IN(internalViewConstraints);
[_internalConstraintsByViewIndex setObject: nil forKey: view];
}
- (BOOL) hasConstraintsForView: (NSView *)view
{
NSNumber *viewIndex = [self indexForView: view];
return [[_constraintsByViewIndex objectForKey: viewIndex] count] > 0;
}
- (GSCSConstraint *) getExistingConstraintForAutolayoutConstraint:
(NSLayoutConstraint *)constraint
{
return [_constraintsByAutoLayoutConstaintHash objectForKey: constraint];
}
- (void) addConstraintAgainstViewConstraintsArray:
(NSLayoutConstraint *)constraint
{
NSNumber *firstItemViewIndex = [self indexForView: [constraint firstItem]];
NSMutableArray *constraintsForView =
[_constraintsByViewIndex objectForKey: firstItemViewIndex];
if (!constraintsForView)
{
constraintsForView = [NSMutableArray array];
[_constraintsByViewIndex setObject: constraintsForView
forKey: firstItemViewIndex];
}
[constraintsForView addObject: constraint];
if ([constraint secondItem] != nil)
{
NSNumber *secondItemViewIndex =
[self indexForView: [constraint secondItem]];
if ([_constraintsByViewIndex objectForKey: secondItemViewIndex])
{
[_constraintsByViewIndex setObject: [NSMutableArray array]
forKey: secondItemViewIndex];
}
[[_constraintsByViewIndex objectForKey: secondItemViewIndex]
addObject: constraint];
}
}
- (void) removeConstraintAgainstViewConstraintsArray:
(NSLayoutConstraint *)constraint
{
NSNumber *firstItemViewIndex = [self indexForView: [constraint firstItem]];
NSMutableArray *constraintsForFirstItem =
[_constraintsByViewIndex objectForKey: firstItemViewIndex];
NSUInteger indexOfConstraintInFirstItem =
[constraintsForFirstItem indexOfObject: constraint];
[constraintsForFirstItem removeObjectAtIndex: indexOfConstraintInFirstItem];
if ([constraint secondItem] != nil)
{
NSNumber *secondItemViewIndexIndex =
[self indexForView: [constraint secondItem]];
NSMutableArray *constraintsForSecondItem =
[_constraintsByViewIndex objectForKey: secondItemViewIndexIndex];
NSUInteger indexOfConstraintInSecondItem =
[constraintsForSecondItem indexOfObject: constraint];
[constraintsForSecondItem
removeObjectAtIndex: indexOfConstraintInSecondItem];
}
}
- (void) updateAlignmentRectsForTrackedViews
{
GSCSSolution *solution = [_solver solve];
[self updateAlignmentRectsForTrackedViewsForSolution: solution];
}
- (void) updateAlignmentRectsForTrackedViewsForSolution:
(GSCSSolution *)solution
{
NSMutableArray *viewsWithChanges = [NSMutableArray array];
FOR_IN(NSView *, view, _trackedViews)
BOOL viewAlignmentRectUpdated = [self updateViewAligmentRect: solution
view: view];
if (viewAlignmentRectUpdated)
{
[viewsWithChanges addObject: view];
}
END_FOR_IN(_trackedViews);
[self notifyViewsOfAlignmentRectChange: viewsWithChanges];
}
- (BOOL) updateViewAligmentRect: (GSCSSolution *)solution view: (NSView *)view
{
NSNumber *viewIndex = [self indexForView: view];
if ([self solverCanSolveAlignmentRectForView: view solution: solution])
{
NSRect existingAlignmentRect =
[self currentAlignmentRectForViewAtIndex: viewIndex];
BOOL isExistingAlignmentRect = NSIsEmptyRect(existingAlignmentRect);
NSRect solverAlignmentRect = [self solverAlignmentRectForView: view
solution: solution];
[self recordAlignmentRect: solverAlignmentRect forViewIndex: viewIndex];
if (isExistingAlignmentRect == NO
|| !NSEqualRects(solverAlignmentRect, existingAlignmentRect))
{
return YES;
}
}
return NO;
}
- (BOOL) solverCanSolveAlignmentRectForView: (NSView *)view
solution: (GSCSSolution *)solution
{
// FIXME
return NO;
}
- (void) recordAlignmentRect: (NSRect)alignmentRect
forViewIndex: (NSNumber *)viewIndex
{
NSValue *newRectValue = [NSValue valueWithRect: alignmentRect];
[_viewAlignmentRectByViewIndex setObject: newRectValue forKey: viewIndex];
}
- (NSRect) currentAlignmentRectForViewAtIndex: (NSNumber *)viewIndex
{
NSValue *existingRectValue =
[_viewAlignmentRectByViewIndex objectForKey: viewIndex];
if (existingRectValue == nil)
{
return NSZeroRect;
}
NSRect existingAlignmentRect;
[existingRectValue getValue: &existingAlignmentRect];
return existingAlignmentRect;
}
- (NSRect) solverAlignmentRectForView: (NSView *)view
solution: (GSCSSolution *)solution
{
// FIXME Get view solution from solver
return NSZeroRect;
}
- (void) notifyViewsOfAlignmentRectChange: (NSArray *)viewsWithChanges
{
FOR_IN(NSView *, view, viewsWithChanges)
[view _layoutEngineDidChangeAlignmentRect];
END_FOR_IN(viewsWithChanges);
}
- (NSRect) alignmentRectForView: (NSView *)view
{
// FIXME Get alignment rect for view from solver
return NSZeroRect;
}
- (void) addSupportingSolverConstraint: (GSCSConstraint *)supportingConstraint
forSolverConstraint: (GSCSConstraint *)constraint
{
[self addSolverConstraint: supportingConstraint];
if ([_supportingConstraintsByConstraint objectForKey: constraint] == nil)
{
[_supportingConstraintsByConstraint
setObject: [NSMutableArray array]
forKey: constraint];
}
[[_supportingConstraintsByConstraint objectForKey: constraint]
addObject: supportingConstraint];
}
- (void) addSolverConstraint: (GSCSConstraint *)constraint
{
[_solverConstraints addObject: constraint];
[_solver addConstraint: constraint];
}
- (void) removeSolverConstraint: (GSCSConstraint *)constraint
{
[_solver removeConstraint: constraint];
[_solverConstraints removeObject: constraint];
}
- (NSNumber *) indexForView: (NSView *)view
{
return [_viewIndexByViewHash
objectForKey: [NSNumber numberWithUnsignedInteger: [view hash]]];
}
- (NSArray *) constraintsForView: (NSView *)view
{
NSNumber *viewIndex = [self indexForView: view];
if (!viewIndex)
{
return [NSArray array];
}
NSMutableArray *constraintsForView = [NSMutableArray array];
NSArray *viewConstraints = [_constraintsByViewIndex objectForKey: viewIndex];
FOR_IN(NSLayoutConstraint *, constraint, viewConstraints)
if ([constraint firstItem] == view)
{
[constraintsForView addObject: constraint];
}
END_FOR_IN(viewConstraints);
return constraintsForView;
}
- (void) resolveVariableForView: (NSView *)view
attribute: (GSLayoutViewAttribute)attribute
{
GSCSVariable *editVariable = [self getExistingVariableForView: view
withViewAttribute: attribute];
CGFloat value = [self valueForView: view attribute: attribute];
[_solver suggestEditVariable: editVariable equals: value];
[self updateAlignmentRectsForTrackedViews];
}
- (CGFloat) valueForView: (NSView *)view
attribute: (GSLayoutViewAttribute)attribute
{
switch (attribute)
{
case GSLayoutViewAttributeBaselineOffsetFromBottom:
return [view baselineOffsetFromBottom];
case GSLayoutViewAttributeFirstBaselineOffsetFromTop:
return [view firstBaselineOffsetFromTop];
case GSLayoutViewAttributeIntrinsicWidth:
return [view intrinsicContentSize].width;
case GSLayoutViewAttributeIntrinsicHeight:
return [view intrinsicContentSize].height;
default:
[[NSException exceptionWithName: @"Not handled"
reason: @"GSLayoutAttribute not handled"
userInfo: nil] raise];
return 0;
}
}
- (void) dealloc
{
RELEASE(_trackedViews);
RELEASE(_viewAlignmentRectByViewIndex);
RELEASE(_viewIndexByViewHash);
RELEASE(_constraintsByViewIndex);
RELEASE(_layoutConstraintsBySolverConstraint);
RELEASE(_supportingConstraintsByConstraint);
RELEASE(_constraintsByAutoLayoutConstaintHash);
RELEASE(_internalConstraintsByViewIndex);
RELEASE(_solverConstraints);
RELEASE(_variablesByKey);
RELEASE(_solver);
[super dealloc];
}
@end