/* Implementation of class NSLayoutConstraint Copyright (C) 2020 Free Software Foundation, Inc. By: Gregory Casamento Date: Sat May 9 16:30:22 EDT 2020 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 #import #import #import "AppKit/NSControl.h" #import "AppKit/NSView.h" #import "AppKit/NSAnimation.h" #import "AppKit/NSLayoutConstraint.h" #import "NSViewPrivate.h" #import "NSWindowPrivate.h" #import "AppKit/NSWindow.h" #import "AppKit/NSApplication.h" #import "NSAutoresizingMaskLayoutConstraint.h" #import "GSFastEnumeration.h" #import "GSAutoLayoutVFLParser.h" #import "GSAutoLayoutEngine.h" static NSMutableArray *activeConstraints = nil; // static NSNotificationCenter *nc = nil; @implementation NSLayoutConstraint + (void) initialize { if (self == [NSLayoutConstraint class]) { [self setVersion: 1]; activeConstraints = [[NSMutableArray alloc] initWithCapacity: 10]; // nc = [NSNotificationCenter defaultCenter]; // [nc addObserver: self // selector: @selector(_setupNotifications:) // name: NSApplicationDidFinishLaunchingNotification // object: nil]; } } + (void) _setupNotifications: (NSNotification *)n { /* [nc addObserver: self selector: @selector(_handleWindowResize:) name: NSWindowDidResizeNotification object: nil]; */ } + (NSString *) _attributeToString: (NSLayoutAttribute)attr { NSString *name = nil; switch (attr) { case NSLayoutAttributeLeft: name = @"Left"; break; case NSLayoutAttributeRight: name = @"Right"; break; case NSLayoutAttributeTop: name = @"Top"; break; case NSLayoutAttributeBottom: name = @"Bottom"; break; case NSLayoutAttributeLeading: name = @"Leading"; break; case NSLayoutAttributeTrailing: name = @"Trailing"; break; case NSLayoutAttributeWidth: name = @"Width"; break; case NSLayoutAttributeHeight: name = @"Height"; break; case NSLayoutAttributeCenterX: name = @"CenterX"; break; case NSLayoutAttributeCenterY: name = @"CenterY"; break; //case NSLayoutAttributeLastBaseline: //name = @"LastBaseline"; //break; case NSLayoutAttributeBaseline: name = @"Baseline"; break; case NSLayoutAttributeFirstBaseline: name = @"FirstBaseline"; break; case NSLayoutAttributeNotAnAttribute: name = @"NotAnAttribute"; break; default: break; } return name; } + (NSLayoutAttribute) _stringToAttribute: (NSString *)str { NSLayoutAttribute a = 0; if ([@"Left" isEqualToString: str]) { a = NSLayoutAttributeLeft; } else if ([@"Right" isEqualToString: str]) { a = NSLayoutAttributeRight; } else if ([@"Top" isEqualToString: str]) { a = NSLayoutAttributeTop; } else if ([@"Bottom" isEqualToString: str]) { a = NSLayoutAttributeBottom; } else if ([@"Leading" isEqualToString: str]) { a = NSLayoutAttributeLeading; } else if ([@"Trailing" isEqualToString: str]) { a = NSLayoutAttributeTrailing; } else if ([@"Width" isEqualToString: str]) { a = NSLayoutAttributeWidth; } else if ([@"Height" isEqualToString: str]) { a = NSLayoutAttributeHeight; } else if ([@"CenterX" isEqualToString: str]) { a = NSLayoutAttributeCenterX; } else if ([@"CenterY" isEqualToString: str]) { a = NSLayoutAttributeCenterY; } else if ([@"Baseline" isEqualToString: str]) { a = NSLayoutAttributeBaseline; } else if ([@"FirstBaseline" isEqualToString: str]) { a = NSLayoutAttributeFirstBaseline; } else if ([@"NotAnAttribute" isEqualToString: str]) { a = NSLayoutAttributeNotAnAttribute; } return a; } + (NSString *) _relationToString: (NSLayoutRelation)rel { NSString *relation = nil; switch (rel) { case NSLayoutRelationLessThanOrEqual: relation = @"<="; break; case NSLayoutRelationEqual: relation = @"="; break; case NSLayoutRelationGreaterThanOrEqual: relation = @">="; break; default: break; } return relation; } + (NSLayoutRelation) _stringToRelation: (NSString *)str { NSLayoutRelation r = 0; if ([@"<=" isEqualToString: str]) { r = NSLayoutRelationLessThanOrEqual; } else if ([@"=" isEqualToString: str]) { r = NSLayoutRelationEqual; } else if ([@">=" isEqualToString: str]) { r = NSLayoutRelationGreaterThanOrEqual; } return r; } + (void) _activateConstraint: (NSLayoutConstraint *)constraint { // [activeConstraints addObject: constraint]; // activeConstraints = [[activeConstraints sortedArrayUsingSelector: @selector(compare:)] mutableCopy]; } + (void) _removeConstraint: (NSLayoutConstraint *)constraint { [activeConstraints removeObject: constraint]; } + (NSArray *) constraintsWithVisualFormat: (NSString *)fmt options: (NSLayoutFormatOptions)opt metrics: (NSDictionary *)metrics views: (NSDictionary *)views { GSAutoLayoutVFLParser *parser = [[GSAutoLayoutVFLParser alloc] initWithFormat: fmt options: opt metrics: metrics views: views]; NSArray *constraints = [parser parse]; RELEASE(parser); return constraints; } - (instancetype) initWithItem: (id)firstItem attribute: (NSLayoutAttribute)firstAttribute relatedBy: (NSLayoutRelation)relation toItem: (id)secondItem attribute: (NSLayoutAttribute)secondAttribute multiplier: (CGFloat)multiplier constant: (CGFloat)constant priority: (CGFloat)priority { self = [super init]; if (self != nil) { _firstItem = firstItem; _secondItem = secondItem; _firstAttribute = firstAttribute; _secondAttribute = secondAttribute; _relation = relation; _multiplier = multiplier; _constant = constant; _priority = priority; [NSLayoutConstraint _activateConstraint: self]; } return self; } + (instancetype) constraintWithItem: (id)view1 attribute: (NSLayoutAttribute)attr1 relatedBy: (NSLayoutRelation)relation toItem: (id)view2 attribute: (NSLayoutAttribute)attr2 multiplier: (CGFloat)mult constant: (CGFloat)c { NSLayoutConstraint *constraint = [[NSLayoutConstraint alloc] initWithItem: view1 attribute: attr1 relatedBy: relation toItem: view2 attribute: attr2 multiplier: mult constant: c priority: NSLayoutPriorityRequired]; AUTORELEASE(constraint); return constraint; } + (void) activateConstraints: (NSArray *)constraints { NSEnumerator *en = [constraints objectEnumerator]; NSLayoutConstraint *c = nil; while ((c = [en nextObject]) != nil) { [NSLayoutConstraint _activateConstraint: c]; } } + (void) deactivateConstraints: (NSArray *)constraints { NSEnumerator *en = [constraints objectEnumerator]; NSLayoutConstraint *c = nil; while ((c = [en nextObject]) != nil) { [NSLayoutConstraint _removeConstraint: c]; } } // Active - (BOOL) isActive { return [activeConstraints containsObject: self]; } - (void) setActive: (BOOL)flag { if (flag) { [NSLayoutConstraint _activateConstraint: self]; } else { [NSLayoutConstraint _removeConstraint: self]; } } // compare and isEqual... - (NSComparisonResult) compare: (NSLayoutConstraint *)constraint { if ([self priority] < [constraint priority]) { return NSOrderedAscending; } else if ([self priority] > [constraint priority]) { return NSOrderedDescending; } return NSOrderedSame; } - (BOOL) isEqual: (NSLayoutConstraint *)constraint { BOOL result = [super isEqual: constraint]; if (result == NO) { result = (_firstItem == [constraint firstItem] && _secondItem == [constraint secondItem] && _firstAttribute == [constraint firstAttribute] && _secondAttribute == [constraint secondAttribute] && _relation == [constraint relation] && _multiplier == [constraint multiplier] && _constant == [constraint constant] && _priority == [constraint priority]); } return result; } // Items - (id) firstItem { return _firstItem; } - (NSLayoutAttribute) firstAttribute { return _firstAttribute; } - (NSLayoutRelation) relation { return _relation; } - (id) secondItem { return _secondItem; } - (NSLayoutAttribute) secondAttribute { return _secondAttribute; } - (CGFloat) multiplier { return _multiplier; } - (CGFloat) constant { return _constant; } - (NSLayoutAnchor *) firstAnchor { return _firstAnchor; } - (NSLayoutAnchor *) secondAnchor { return _secondAnchor; } - (NSLayoutPriority) priority { return _priority; } - (void) setPriority: (NSLayoutPriority)priority { _priority = priority; } // Coding... - (instancetype) initWithCoder: (NSCoder *)coder { self = [super init]; if (self != nil) { if ([coder allowsKeyedCoding]) { if ([coder containsValueForKey: @"NSConstant"]) { _constant = [coder decodeFloatForKey: @"NSConstant"]; } if ([coder containsValueForKey: @"NSFirstAttribute"]) { _firstAttribute = [coder decodeIntegerForKey: @"NSFirstAttribute"]; } if ([coder containsValueForKey: @"NSFirstItem"]) { _firstItem = [coder decodeObjectForKey: @"NSFirstItem"]; } if ([coder containsValueForKey: @"NSSecondAttribute"]) { _secondAttribute = [coder decodeIntegerForKey: @"NSSecondAttribute"]; } if ([coder containsValueForKey: @"NSSecondItem"]) { _secondItem = [coder decodeObjectForKey: @"NSSecondItem"]; } if ([coder containsValueForKey: @"NSMultiplier"]) { _multiplier = [coder decodeFloatForKey: @"NSMultiplier"]; } else { _multiplier = 1.0; // identity multiplier if not present } if ([coder containsValueForKey: @"NSRelation"]) { _relation = [coder decodeIntegerForKey: @"NSRelation"]; } else { _relation = NSLayoutRelationEqual; } if ([coder containsValueForKey: @"NSPriority"]) { _priority = [coder decodeFloatForKey: @"NSPriority"]; } else { _priority = NSLayoutPriorityRequired; // if it is not present, this defaults to 1000... per testing with Cocoa. } } else { [coder decodeValueOfObjCType: @encode(float) at: &_constant]; [coder decodeValueOfObjCType: @encode(NSUInteger) at: &_firstAttribute]; _firstItem = RETAIN([coder decodeObject]); [coder decodeValueOfObjCType: @encode(NSUInteger) at: &_secondAttribute]; _secondItem = RETAIN([coder decodeObject]); [coder decodeValueOfObjCType: @encode(float) at: &_multiplier]; [coder decodeValueOfObjCType: @encode(NSUInteger) at: &_relation]; [coder decodeValueOfObjCType: @encode(float) at: &_priority]; } } [NSLayoutConstraint _activateConstraint: self]; return self; } - (void) encodeWithCoder: (NSCoder *)coder { if ([coder allowsKeyedCoding]) { [coder encodeFloat: _constant forKey: @"NSConstant"]; [coder encodeInteger: _firstAttribute forKey: @"NSFirstAttribute"]; [coder encodeObject: _firstItem forKey: @"NSFirstItem"]; [coder encodeInteger: _secondAttribute forKey: @"NSSecondAttribute"]; [coder encodeObject: _secondItem forKey: @"NSSecondItem"]; [coder encodeFloat: _multiplier forKey: @"NSMultiplier"]; [coder encodeInteger: _relation forKey: @"NSRelation"]; [coder encodeFloat: _priority forKey: @"NSPriority"]; } else { [coder encodeValueOfObjCType: @encode(float) at: &_constant]; [coder encodeValueOfObjCType: @encode(NSUInteger) at: &_firstAttribute]; [coder encodeObject: _firstItem]; [coder encodeValueOfObjCType: @encode(NSUInteger) at: &_secondAttribute]; [coder encodeObject: _secondItem]; [coder encodeValueOfObjCType: @encode(float) at: &_multiplier]; [coder encodeValueOfObjCType: @encode(NSUInteger) at: &_relation]; [coder encodeValueOfObjCType: @encode(float) at: &_priority]; } } - (id) copyWithZone: (NSZone *)z { NSLayoutConstraint *constraint = [[NSLayoutConstraint allocWithZone: z] initWithItem: _firstItem attribute: _firstAttribute relatedBy: _relation toItem: _secondItem attribute: _secondAttribute multiplier: _multiplier constant: _constant priority: _priority]; return constraint; } - (void) dealloc { [NSLayoutConstraint _removeConstraint: self]; [super dealloc]; } - (NSString *) description { return [NSString stringWithFormat: @"%@ ", [super description], _firstItem, _firstAttribute, _relation, _secondItem, _secondAttribute, _multiplier, _constant, _priority]; } // item1.attribute1 = multiplier × item2.attribute2 + constant - (void) _applyConstraint { // Currently not implemented. } + (void) _handleWindowResize: (NSNotification *)notification { /* NSLayoutConstraint *c = nil; NSEnumerator *en = [activeConstraints objectEnumerator]; // Only apply to the window in the notification... while ((c = [en nextObject]) != nil) { NSWindow *w = [[c firstItem] window]; if (w == [notification object]) { [c _applyConstraint]; } } */ } @end @implementation NSWindow (NSConstraintBasedLayoutCoreMethods) - (void) updateConstraintsIfNeeded { [[self contentView] updateConstraintsForSubtreeIfNeeded]; } - (void) layoutIfNeeded { [self updateConstraintsIfNeeded]; [[self contentView] _layoutViewAndSubViews]; } - (void) _bootstrapAutoLayout { GSAutoLayoutEngine *layoutEngine = [[GSAutoLayoutEngine alloc] init]; [self _setLayoutEngine: layoutEngine]; RELEASE(layoutEngine); } @end