1458 lines
66 KiB
Objective-C
1458 lines
66 KiB
Objective-C
//
|
|
// Copyright 2019 ShortcutRecorder Contributors
|
|
// CC BY 4.0
|
|
//
|
|
|
|
#import <os/trace.h>
|
|
#import <os/activity.h>
|
|
|
|
#import "SRRecorderControl.h"
|
|
#import "SRRecorderControlStyle.h"
|
|
|
|
|
|
NSAttributedStringKey const SRMinimalDrawableWidthAttributeName = @"SRMinimalDrawableWidthAttributeName";
|
|
|
|
|
|
SRRecorderControlStyleComponentsAppearance SRRecorderControlStyleComponentsAppearanceFromSystem(NSAppearanceName aSystemAppearanceName)
|
|
{
|
|
static NSDictionary<NSAppearanceName, NSNumber *> *Map = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
Map = @{
|
|
NSAppearanceNameAqua: @(SRRecorderControlStyleComponentsAppearanceAqua),
|
|
NSAppearanceNameVibrantLight: @(SRRecorderControlStyleComponentsAppearanceVibrantLight),
|
|
NSAppearanceNameVibrantDark: @(SRRecorderControlStyleComponentsAppearanceVibrantDark)
|
|
}.mutableCopy;
|
|
|
|
if (@available(macOS 10.14, *))
|
|
[(NSMutableDictionary *)Map setObject:@(SRRecorderControlStyleComponentsAppearanceDarkAqua) forKey:NSAppearanceNameDarkAqua];
|
|
});
|
|
|
|
NSNumber *appearance = Map[aSystemAppearanceName];
|
|
|
|
|
|
if (@available(macOS 10.14, *))
|
|
{
|
|
if (!appearance)
|
|
{
|
|
NSAppearance *systemAppearance = [NSAppearance appearanceNamed:aSystemAppearanceName];
|
|
aSystemAppearanceName = [systemAppearance bestMatchFromAppearancesWithNames:Map.allKeys];
|
|
|
|
if (aSystemAppearanceName)
|
|
appearance = Map[aSystemAppearanceName];
|
|
}
|
|
}
|
|
|
|
if (appearance)
|
|
return appearance.unsignedIntegerValue;
|
|
else
|
|
return SRRecorderControlStyleComponentsAppearanceUnspecified;
|
|
}
|
|
|
|
|
|
NSAppearanceName SRRecorderControlStyleComponentsAppearanceToSystem(SRRecorderControlStyleComponentsAppearance anAppearance)
|
|
{
|
|
switch (anAppearance)
|
|
{
|
|
case SRRecorderControlStyleComponentsAppearanceAqua:
|
|
return NSAppearanceNameAqua;
|
|
case SRRecorderControlStyleComponentsAppearanceVibrantLight:
|
|
return NSAppearanceNameVibrantLight;
|
|
case SRRecorderControlStyleComponentsAppearanceVibrantDark:
|
|
return NSAppearanceNameVibrantDark;
|
|
case SRRecorderControlStyleComponentsAppearanceDarkAqua:
|
|
{
|
|
if (@available(macOS 10.14, *))
|
|
return NSAppearanceNameDarkAqua;
|
|
}
|
|
case SRRecorderControlStyleComponentsAppearanceUnspecified:
|
|
default:
|
|
[NSException raise:NSInvalidArgumentException format:@"%lu cannot be represented as NSAppearanceName", anAppearance];
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
|
|
|
|
SRRecorderControlStyleComponentsTint SRRecorderControlStyleComponentsTintFromSystem(NSControlTint aSystemTint)
|
|
{
|
|
switch (aSystemTint)
|
|
{
|
|
case NSBlueControlTint:
|
|
return SRRecorderControlStyleComponentsTintBlue;
|
|
case NSGraphiteControlTint:
|
|
return SRRecorderControlStyleComponentsTintGraphite;
|
|
default:
|
|
return SRRecorderControlStyleComponentsTintUnspecified;
|
|
}
|
|
}
|
|
|
|
|
|
NSControlTint SRRecorderControlStyleComponentsTintToSystem(SRRecorderControlStyleComponentsTint aTint)
|
|
{
|
|
switch (aTint)
|
|
{
|
|
case SRRecorderControlStyleComponentsTintBlue:
|
|
return NSBlueControlTint;
|
|
break;
|
|
case SRRecorderControlStyleComponentsTintGraphite:
|
|
return NSGraphiteControlTint;
|
|
case SRRecorderControlStyleComponentsTintUnspecified:
|
|
default:
|
|
[NSException raise:NSInvalidArgumentException format:@"%lu cannot be represented as NSControlTint", aTint];
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
|
|
|
|
SRRecorderControlStyleComponentsLayoutDirection SRRecorderControlStyleComponentsLayoutDirectionFromSystem(NSUserInterfaceLayoutDirection aSystemLayoutDirection)
|
|
{
|
|
switch (aSystemLayoutDirection)
|
|
{
|
|
case NSUserInterfaceLayoutDirectionLeftToRight:
|
|
return SRRecorderControlStyleComponentsLayoutDirectionLeftToRight;
|
|
case NSUserInterfaceLayoutDirectionRightToLeft:
|
|
return SRRecorderControlStyleComponentsLayoutDirectionRightToLeft;
|
|
default:
|
|
return SRRecorderControlStyleComponentsLayoutDirectionUnspecified;
|
|
}
|
|
}
|
|
|
|
|
|
NSUserInterfaceLayoutDirection SRRecorderControlStyleComponentsLayoutDirectionToSystem(SRRecorderControlStyleComponentsLayoutDirection aLayoutDirection)
|
|
{
|
|
switch (aLayoutDirection)
|
|
{
|
|
case SRRecorderControlStyleComponentsLayoutDirectionLeftToRight:
|
|
return NSUserInterfaceLayoutDirectionLeftToRight;
|
|
case SRRecorderControlStyleComponentsLayoutDirectionRightToLeft:
|
|
return NSUserInterfaceLayoutDirectionRightToLeft;
|
|
case SRRecorderControlStyleComponentsLayoutDirectionUnspecified:
|
|
default:
|
|
[NSException raise:NSInvalidArgumentException format:@"%lu cannot be represented as NSUserInterfaceLayoutDirection", aLayoutDirection];
|
|
__builtin_unreachable();
|
|
}
|
|
}
|
|
|
|
|
|
@implementation SRRecorderControlStyleComponents
|
|
|
|
+ (SRRecorderControlStyleComponents *)currentComponents
|
|
{
|
|
return [self currentComponentsForView:nil];
|
|
}
|
|
|
|
+ (SRRecorderControlStyleComponents *)currentComponentsForView:(NSView *)aView
|
|
{
|
|
NSAppearanceName effectiveSystemAppearance = nil;
|
|
|
|
if (aView)
|
|
effectiveSystemAppearance = aView.effectiveAppearance.name;
|
|
else
|
|
effectiveSystemAppearance = NSAppearance.currentAppearance.name;
|
|
|
|
__auto_type appearance = SRRecorderControlStyleComponentsAppearanceFromSystem(effectiveSystemAppearance);
|
|
__auto_type tint = SRRecorderControlStyleComponentsTintFromSystem(NSColor.currentControlTint);
|
|
__auto_type accessibility = SRRecorderControlStyleComponentsAccessibilityUnspecified;
|
|
|
|
if (NSWorkspace.sharedWorkspace.accessibilityDisplayShouldIncreaseContrast)
|
|
accessibility = SRRecorderControlStyleComponentsAccessibilityHighContrast;
|
|
else
|
|
accessibility = SRRecorderControlStyleComponentsAccessibilityNone;
|
|
|
|
__auto_type layoutDirection = SRRecorderControlStyleComponentsLayoutDirectionUnspecified;
|
|
|
|
if (aView)
|
|
layoutDirection = SRRecorderControlStyleComponentsLayoutDirectionFromSystem(aView.userInterfaceLayoutDirection);
|
|
else
|
|
layoutDirection = SRRecorderControlStyleComponentsLayoutDirectionFromSystem(NSApp.userInterfaceLayoutDirection);
|
|
|
|
return [[SRRecorderControlStyleComponents alloc] initWithAppearance:appearance
|
|
accessibility:accessibility
|
|
layoutDirection:layoutDirection
|
|
tint:tint];
|
|
}
|
|
|
|
- (instancetype)initWithAppearance:(SRRecorderControlStyleComponentsAppearance)anAppearance
|
|
accessibility:(SRRecorderControlStyleComponentsAccessibility)anAccessibility
|
|
layoutDirection:(SRRecorderControlStyleComponentsLayoutDirection)aDirection
|
|
tint:(SRRecorderControlStyleComponentsTint)aTint
|
|
{
|
|
NSAssert(anAppearance >= SRRecorderControlStyleComponentsAppearanceUnspecified && anAppearance < SRRecorderControlStyleComponentsAppearanceMax,
|
|
@"anAppearance is outside of the allowed range.");
|
|
NSAssert(aTint >= SRRecorderControlStyleComponentsTintUnspecified && aTint < SRRecorderControlStyleComponentsTintMax,
|
|
@"aTint is outside of the allowed range.");
|
|
NSAssert((anAccessibility & ~SRRecorderControlStyleComponentsAccessibilityMask) == 0,
|
|
@"anAccessibility is outside of the allowed range.");
|
|
NSAssert(anAccessibility == SRRecorderControlStyleComponentsAccessibilityNone ||
|
|
(anAccessibility & SRRecorderControlStyleComponentsAccessibilityNone) == 0, @"None cannot be combined with other accessibility options.");
|
|
NSAssert(aDirection >= SRRecorderControlStyleComponentsLayoutDirectionUnspecified && aTint < SRRecorderControlStyleComponentsLayoutDirectionMax,
|
|
@"aDirection is outside of the allowed range.");
|
|
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_appearance = anAppearance;
|
|
_accessibility = anAccessibility;
|
|
_layoutDirection = aDirection;
|
|
_tint = aTint;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithAppearance:SRRecorderControlStyleComponentsAppearanceUnspecified
|
|
accessibility:SRRecorderControlStyleComponentsAccessibilityUnspecified
|
|
layoutDirection:SRRecorderControlStyleComponentsLayoutDirectionUnspecified
|
|
tint:SRRecorderControlStyleComponentsTintUnspecified];
|
|
}
|
|
|
|
#pragma mark Properties
|
|
|
|
- (BOOL)isSpecified
|
|
{
|
|
return _appearance != SRRecorderControlStyleComponentsAppearanceUnspecified &&
|
|
_accessibility != SRRecorderControlStyleComponentsAccessibilityUnspecified &&
|
|
_layoutDirection != SRRecorderControlStyleComponentsLayoutDirectionUnspecified &&
|
|
_tint != SRRecorderControlStyleComponentsTintUnspecified;
|
|
}
|
|
|
|
- (NSString *)stringRepresentation
|
|
{
|
|
NSString *appearance = nil;
|
|
NSString *tint = nil;
|
|
NSString *acc = nil;
|
|
NSString *direction = nil;
|
|
|
|
switch (self.appearance)
|
|
{
|
|
case SRRecorderControlStyleComponentsAppearanceDarkAqua:
|
|
appearance = @"-darkaqua";
|
|
break;
|
|
case SRRecorderControlStyleComponentsAppearanceAqua:
|
|
appearance = @"-aqua";
|
|
break;
|
|
case SRRecorderControlStyleComponentsAppearanceVibrantDark:
|
|
appearance = @"-vibrantdark";
|
|
break;
|
|
case SRRecorderControlStyleComponentsAppearanceVibrantLight:
|
|
appearance = @"-vibrantlight";
|
|
break;
|
|
case SRRecorderControlStyleComponentsAppearanceUnspecified:
|
|
appearance = @"";
|
|
break;
|
|
|
|
default:
|
|
NSAssert(NO, @"Unexpected appearance.");
|
|
break;
|
|
}
|
|
|
|
switch (self.accessibility)
|
|
{
|
|
case SRRecorderControlStyleComponentsAccessibilityHighContrast:
|
|
acc = @"-acc";
|
|
break;
|
|
case SRRecorderControlStyleComponentsAccessibilityNone:
|
|
case SRRecorderControlStyleComponentsAccessibilityUnspecified:
|
|
acc = @"";
|
|
break;
|
|
|
|
default:
|
|
NSAssert(NO, @"Unexpected appearance.");
|
|
break;
|
|
}
|
|
|
|
switch (self.layoutDirection)
|
|
{
|
|
case SRRecorderControlStyleComponentsLayoutDirectionLeftToRight:
|
|
direction = @"-ltr";
|
|
break;
|
|
case SRRecorderControlStyleComponentsLayoutDirectionRightToLeft:
|
|
direction = @"-rtl";
|
|
break;
|
|
case SRRecorderControlStyleComponentsLayoutDirectionUnspecified:
|
|
direction = @"";
|
|
break;
|
|
|
|
default:
|
|
NSAssert(NO, @"Unexpected appearance.");
|
|
break;
|
|
}
|
|
|
|
switch (self.tint)
|
|
{
|
|
case SRRecorderControlStyleComponentsTintBlue:
|
|
tint = @"-blue";
|
|
break;
|
|
case SRRecorderControlStyleComponentsTintGraphite:
|
|
tint = @"-graphite";
|
|
break;
|
|
case SRRecorderControlStyleComponentsTintUnspecified:
|
|
tint = @"";
|
|
break;
|
|
|
|
default:
|
|
NSAssert(NO, @"Unexpected appearance.");
|
|
break;
|
|
}
|
|
|
|
return [NSString stringWithFormat:@"%@%@%@%@", appearance, acc, direction, tint];
|
|
}
|
|
|
|
#pragma mark Methods
|
|
|
|
- (BOOL)isEqualToComponents:(SRRecorderControlStyleComponents *)anObject
|
|
{
|
|
if (anObject == self)
|
|
return YES;
|
|
else if (![anObject isKindOfClass:SRRecorderControlStyleComponents.class])
|
|
return NO;
|
|
else
|
|
return self.appearance == anObject.appearance &&
|
|
self.accessibility == anObject.accessibility &&
|
|
self.layoutDirection == anObject.layoutDirection &&
|
|
self.tint == anObject.tint;
|
|
}
|
|
|
|
- (NSComparisonResult)compare:(SRRecorderControlStyleComponents *)anOtherComponents
|
|
relativeToComponents:(SRRecorderControlStyleComponents *)anIdealComponents
|
|
{
|
|
static NSDictionary<NSNumber *, NSArray<NSNumber *> *> *AppearanceOrderMap = nil;
|
|
static NSDictionary<NSNumber *, NSArray<NSNumber *> *> *TintOrderMap = nil;
|
|
static NSDictionary<NSNumber *, NSArray<NSNumber *> *> *DirectionOrderMap = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
AppearanceOrderMap = @{
|
|
@(SRRecorderControlStyleComponentsAppearanceAqua): @[@(SRRecorderControlStyleComponentsAppearanceAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantLight),
|
|
@(SRRecorderControlStyleComponentsAppearanceDarkAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantDark),
|
|
@(SRRecorderControlStyleComponentsAppearanceUnspecified)],
|
|
@(SRRecorderControlStyleComponentsAppearanceDarkAqua): @[@(SRRecorderControlStyleComponentsAppearanceDarkAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantDark),
|
|
@(SRRecorderControlStyleComponentsAppearanceAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantLight),
|
|
@(SRRecorderControlStyleComponentsAppearanceUnspecified)],
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantLight): @[@(SRRecorderControlStyleComponentsAppearanceVibrantLight),
|
|
@(SRRecorderControlStyleComponentsAppearanceAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantDark),
|
|
@(SRRecorderControlStyleComponentsAppearanceDarkAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceUnspecified)],
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantDark): @[@(SRRecorderControlStyleComponentsAppearanceVibrantDark),
|
|
@(SRRecorderControlStyleComponentsAppearanceDarkAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceVibrantLight),
|
|
@(SRRecorderControlStyleComponentsAppearanceAqua),
|
|
@(SRRecorderControlStyleComponentsAppearanceUnspecified)]
|
|
};
|
|
|
|
TintOrderMap = @{
|
|
@(SRRecorderControlStyleComponentsTintBlue): @[@(SRRecorderControlStyleComponentsTintBlue),
|
|
@(SRRecorderControlStyleComponentsTintGraphite),
|
|
@(SRRecorderControlStyleComponentsTintUnspecified)],
|
|
@(SRRecorderControlStyleComponentsTintGraphite): @[@(SRRecorderControlStyleComponentsTintGraphite),
|
|
@(SRRecorderControlStyleComponentsTintBlue),
|
|
@(SRRecorderControlStyleComponentsTintUnspecified)]
|
|
};
|
|
|
|
DirectionOrderMap = @{
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionLeftToRight): @[@(SRRecorderControlStyleComponentsLayoutDirectionLeftToRight),
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionRightToLeft)],
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionRightToLeft): @[@(SRRecorderControlStyleComponentsLayoutDirectionRightToLeft),
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionLeftToRight)]
|
|
};
|
|
});
|
|
|
|
__auto_type CompareEnum = ^(NSUInteger a, NSUInteger b, NSArray<NSNumber *> *order) {
|
|
NSUInteger aIndex = [order indexOfObject:@(a)];
|
|
NSUInteger bIndex = [order indexOfObject:@(b)];
|
|
|
|
if (aIndex < bIndex)
|
|
return NSOrderedAscending;
|
|
else if (aIndex > bIndex)
|
|
return NSOrderedDescending;
|
|
else
|
|
return NSOrderedSame;
|
|
};
|
|
|
|
__auto_type CompareOptions = ^(NSUInteger a, NSUInteger b, NSUInteger ideal) {
|
|
// How many bits match.
|
|
int aSimilarity = __builtin_popcountl(a & ideal);
|
|
int bSimilarity = __builtin_popcountl(b & ideal);
|
|
|
|
// How many bits mismatch.
|
|
int aDissimilarity = __builtin_popcountl(a & ~ideal);
|
|
int bDissimilarity = __builtin_popcountl(b & ~ideal);
|
|
|
|
if (aSimilarity > bSimilarity)
|
|
return NSOrderedAscending;
|
|
else if (aSimilarity < bSimilarity)
|
|
return NSOrderedDescending;
|
|
else if (aDissimilarity < bDissimilarity)
|
|
return NSOrderedAscending;
|
|
else if (aDissimilarity > bDissimilarity)
|
|
return NSOrderedDescending;
|
|
else
|
|
return NSOrderedSame;
|
|
};
|
|
|
|
if (self.appearance != anOtherComponents.appearance)
|
|
return CompareEnum(self.appearance,
|
|
anOtherComponents.appearance,
|
|
AppearanceOrderMap[@(anIdealComponents.appearance)]);
|
|
else if (self.accessibility != anOtherComponents.accessibility)
|
|
return CompareOptions(self.accessibility,
|
|
anOtherComponents.accessibility,
|
|
anIdealComponents.accessibility);
|
|
else if (self.layoutDirection != anOtherComponents.layoutDirection)
|
|
return CompareEnum(self.layoutDirection,
|
|
anOtherComponents.layoutDirection,
|
|
AppearanceOrderMap[@(anIdealComponents.layoutDirection)]);
|
|
else if (self.tint != anOtherComponents.tint)
|
|
return CompareEnum(self.tint,
|
|
anOtherComponents.tint,
|
|
AppearanceOrderMap[@(anIdealComponents.tint)]);
|
|
else
|
|
return NSOrderedSame;
|
|
}
|
|
|
|
#pragma mark NSCopying
|
|
|
|
- (instancetype)copyWithZone:(NSZone *)aZone
|
|
{
|
|
return self;
|
|
}
|
|
|
|
#pragma mark NSObject
|
|
|
|
- (BOOL)isEqual:(SRRecorderControlStyleComponents *)anObject
|
|
{
|
|
return [self SR_isEqual:anObject
|
|
usingSelector:@selector(isEqualToComponents:)
|
|
ofCommonAncestor:SRRecorderControlStyleComponents.class];
|
|
}
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
int tintBitSize = sizeof(NSUInteger) * CHAR_BIT - __builtin_clzl(SRRecorderControlStyleComponentsTintMax);
|
|
int layoutDirectionBitSize = sizeof(NSUInteger) * CHAR_BIT - __builtin_clzl(SRRecorderControlStyleComponentsLayoutDirectionMax);
|
|
int appearanceBitSize = sizeof(NSUInteger) * CHAR_BIT - __builtin_clzl(SRRecorderControlStyleComponentsAppearanceMax);
|
|
return self.tint |
|
|
(self.layoutDirection << tintBitSize) |
|
|
(self.appearance << (tintBitSize + layoutDirectionBitSize)) |
|
|
(self.accessibility << (tintBitSize + layoutDirectionBitSize + appearanceBitSize));
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [self stringRepresentation];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface _SRRecorderControlStyleResourceLoaderCacheLookupPrefixesKey: NSObject <NSCopying>
|
|
@property NSString *identifier;
|
|
@property SRRecorderControlStyleComponents *components;
|
|
@end
|
|
|
|
|
|
@implementation _SRRecorderControlStyleResourceLoaderCacheLookupPrefixesKey
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
return (self.components.hash << 32) ^ self.identifier.hash;
|
|
}
|
|
|
|
- (BOOL)isEqual:(_SRRecorderControlStyleResourceLoaderCacheLookupPrefixesKey *)anObject
|
|
{
|
|
if (![anObject isKindOfClass:self.class])
|
|
return NO;
|
|
|
|
return [self.identifier isEqual:anObject.identifier] && [self.components isEqual:anObject.components];
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)aZone
|
|
{
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@interface _SRRecorderControlStyleResourceLoaderCacheImageKey: NSObject <NSCopying>
|
|
@property NSString *identifier;
|
|
@property SRRecorderControlStyleComponents *components;
|
|
@property NSString *name;
|
|
@end
|
|
|
|
|
|
@implementation _SRRecorderControlStyleResourceLoaderCacheImageKey
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
return (self.components.hash << 32) ^ (self.name.hash << 32) ^ self.components.hash;
|
|
}
|
|
|
|
- (BOOL)isEqual:(_SRRecorderControlStyleResourceLoaderCacheImageKey *)anObject
|
|
{
|
|
if (![anObject isKindOfClass:self.class])
|
|
return NO;
|
|
|
|
return [self.identifier isEqual:anObject.identifier] &&
|
|
[self.components isEqual:anObject.components] &&
|
|
[self.name isEqual:anObject];
|
|
}
|
|
|
|
- (id)copyWithZone:(NSZone *)aZone
|
|
{
|
|
return self;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation SRRecorderControlStyleResourceLoader
|
|
{
|
|
NSCache *_cache;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_cache = [NSCache new];
|
|
_cache.name = @"SRRecorderControlStyleResourceLoader";
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (NSDictionary<NSString *, id> *)infoForStyle:(SRRecorderControlStyle *)aStyle
|
|
{
|
|
typedef id (^Transformer)(id anObject, NSString *aKey);
|
|
typedef void (^Verifier)(id anObject, NSString *aKey);
|
|
|
|
__auto_type VerifyIsType = ^(NSObject *anObject, NSString *aKey, Class aType) {
|
|
if (![anObject isKindOfClass:aType])
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%@: expected %@ but got %@", aKey, NSStringFromClass(aType), NSStringFromClass(anObject.class)];
|
|
};
|
|
|
|
__auto_type VerifyNumberInInterval = ^(NSNumber *anObject, NSString *aKey, NSNumber *aMin, NSNumber *aMax) {
|
|
VerifyIsType(anObject, aKey, NSNumber.class);
|
|
|
|
if ([anObject compare:aMax] != NSOrderedAscending)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: value >= %@", aKey, aMax];
|
|
|
|
if ([anObject compare:aMin] == NSOrderedAscending)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: value < %@", aKey, aMin];
|
|
};
|
|
|
|
__auto_type VerifyNumberWithMask = ^(NSNumber *anObject, NSString *aKey, NSUInteger aMask) {
|
|
if (!anObject)
|
|
return;
|
|
|
|
VerifyIsType(anObject, aKey, NSNumber.class);
|
|
|
|
if ((anObject.unsignedIntegerValue & ~aMask) != 0)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: value must be with mask %lu", aKey, aMask];
|
|
};
|
|
|
|
__auto_type VerifyDictionaryHasKey = ^(NSDictionary *anObject, NSString *aKey, NSString *aSubKey) {
|
|
if (!anObject[aSubKey])
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: missing %@", aKey, aSubKey];
|
|
};
|
|
|
|
Verifier VerifyIsArray = ^(NSArray *anObject, NSString *aKey) {
|
|
VerifyIsType(anObject, aKey, NSArray.class);
|
|
};
|
|
|
|
Verifier VerifyIsDictionary = ^(NSDictionary *anObject, NSString *aKey) {
|
|
VerifyIsType(anObject, aKey, NSDictionary.class);
|
|
};
|
|
|
|
Verifier VerifyIsNumber = ^(NSNumber *anObject, NSString *aKey) {
|
|
VerifyIsType(anObject, aKey, NSNumber.class);
|
|
};
|
|
|
|
Verifier VerifyIsString = ^(NSString *anObject, NSString *aKey) {
|
|
VerifyIsType(anObject, aKey, NSString.class);
|
|
};
|
|
|
|
Verifier VerifyIsComponents = ^(NSDictionary *anObject, NSString *aKey) {
|
|
VerifyIsDictionary(anObject, aKey);
|
|
|
|
if (anObject[@"appearance"])
|
|
VerifyNumberInInterval(anObject[@"appearance"],
|
|
[NSString stringWithFormat:@"%@.appearance", aKey],
|
|
@(SRRecorderControlStyleComponentsAppearanceUnspecified),
|
|
@(SRRecorderControlStyleComponentsAppearanceMax));
|
|
|
|
if (anObject[@"accessibility"])
|
|
VerifyNumberWithMask(anObject[@"accessibility"],
|
|
[NSString stringWithFormat:@"%@.accessibility", aKey],
|
|
SRRecorderControlStyleComponentsAccessibilityMask);
|
|
|
|
if (anObject[@"layoutDirection"])
|
|
VerifyNumberInInterval(anObject[@"layoutDirection"],
|
|
[NSString stringWithFormat:@"%@.layoutDirection", aKey],
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionUnspecified),
|
|
@(SRRecorderControlStyleComponentsLayoutDirectionMax));
|
|
|
|
if (anObject[@"tint"])
|
|
VerifyNumberInInterval(anObject[@"tint"],
|
|
[NSString stringWithFormat:@"%@.tint", aKey],
|
|
@(SRRecorderControlStyleComponentsTintUnspecified),
|
|
@(SRRecorderControlStyleComponentsTintMax));
|
|
};
|
|
|
|
Verifier VerifyIsSize = ^(NSDictionary *anObject, NSString *aKey) {
|
|
VerifyIsDictionary(anObject, aKey);
|
|
|
|
if (anObject.count != 2)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: unexpected keys", aKey];
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"width");
|
|
VerifyIsNumber(anObject[@"width"], [NSString stringWithFormat:@"%@.width", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"height");
|
|
VerifyIsNumber(anObject[@"height"], [NSString stringWithFormat:@"%@.height", aKey]);
|
|
};
|
|
|
|
Verifier VerifyIsEdgeInsets = ^(NSDictionary *anObject, NSString *aKey) {
|
|
VerifyIsDictionary(anObject, aKey);
|
|
|
|
if (anObject.count != 4)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: unexpected keys", aKey];
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"top");
|
|
VerifyIsNumber(anObject[@"top"], [NSString stringWithFormat:@"%@.top", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"left");
|
|
VerifyIsNumber(anObject[@"left"], [NSString stringWithFormat:@"%@.left", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"bottom");
|
|
VerifyIsNumber(anObject[@"bottom"], [NSString stringWithFormat:@"%@.bottom", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"right");
|
|
VerifyIsNumber(anObject[@"right"], [NSString stringWithFormat:@"%@.right", aKey]);
|
|
};
|
|
|
|
Verifier VerifyIsLabelAttributes = ^(NSDictionary *anObject, NSString *aKey) {
|
|
VerifyIsDictionary(anObject, aKey);
|
|
|
|
if (anObject.count != 4)
|
|
[NSException raise:NSInternalInconsistencyException format:@"%@: unexpected keys", aKey];
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"fontName");
|
|
VerifyIsString(anObject[@"fontName"], [NSString stringWithFormat:@"%@.fontName", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"fontSize");
|
|
VerifyIsNumber(anObject[@"fontSize"], [NSString stringWithFormat:@"%@.fontSize", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"fontColorCatalogName");
|
|
VerifyIsString(anObject[@"fontColorCatalogName"], [NSString stringWithFormat:@"%@.fontColorCatalogName", aKey]);
|
|
|
|
VerifyDictionaryHasKey(anObject, aKey, @"fontColorName");
|
|
VerifyIsString(anObject[@"fontColorName"], [NSString stringWithFormat:@"%@.fontColorName", aKey]);
|
|
};
|
|
|
|
Transformer TransformComponents = ^(NSDictionary<NSString *, NSNumber *> *anObject, NSString *aKey) {
|
|
return [[SRRecorderControlStyleComponents alloc] initWithAppearance:anObject[@"appearance"].unsignedIntegerValue
|
|
accessibility:anObject[@"accessibility"].unsignedIntegerValue
|
|
layoutDirection:anObject[@"layoutDirection"].unsignedIntegerValue
|
|
tint:anObject[@"tint"].unsignedIntegerValue];
|
|
};
|
|
|
|
Transformer TransformSize = ^(NSDictionary<NSString *, NSNumber *> *anObject, NSString *aKey) {
|
|
return [NSValue valueWithSize:NSMakeSize(anObject[@"width"].doubleValue, anObject[@"height"].doubleValue)];
|
|
};
|
|
|
|
Transformer TransformEdgeInsets = ^(NSDictionary<NSString *, NSNumber *> *anObject, NSString *aKey) {
|
|
return [NSValue valueWithEdgeInsets:NSEdgeInsetsMake(anObject[@"top"].doubleValue,
|
|
anObject[@"left"].doubleValue,
|
|
anObject[@"bottom"].doubleValue,
|
|
anObject[@"right"].doubleValue)];
|
|
};
|
|
|
|
Transformer TransformLabelAttributes = ^(NSDictionary<NSAttributedStringKey, id> *anObject, NSString *aKey) {
|
|
NSMutableParagraphStyle *p = [[NSMutableParagraphStyle alloc] init];
|
|
p.alignment = NSTextAlignmentCenter;
|
|
p.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
|
|
|
NSString *fontName = anObject[@"fontName"];
|
|
CGFloat fontSize = [anObject[@"fontSize"] doubleValue];
|
|
NSFont *font = [fontName isEqual:@".AppleSystemUIFont"] ? [NSFont systemFontOfSize:fontSize] : [NSFont fontWithName:fontName size:fontSize];
|
|
|
|
NSColor *fontColor = [NSColor colorWithCatalogName:anObject[@"fontColorCatalogName"] colorName:anObject[@"fontColorName"]];
|
|
|
|
NSMutableDictionary *attributes = @{
|
|
NSParagraphStyleAttributeName: [p copy],
|
|
NSFontAttributeName: font,
|
|
NSForegroundColorAttributeName: fontColor
|
|
}.mutableCopy;
|
|
attributes[SRMinimalDrawableWidthAttributeName] = @([@"…" sizeWithAttributes:attributes].width);
|
|
|
|
return [attributes copy];
|
|
};
|
|
|
|
__auto_type Get = ^(NSDictionary *aSource, NSString *aKey, Verifier aVerifier, Transformer aTransformer) {
|
|
id value = aSource[aKey];
|
|
|
|
if (aVerifier)
|
|
aVerifier(value, aKey);
|
|
|
|
if (aTransformer)
|
|
value = aTransformer(value, aKey);
|
|
|
|
return value;
|
|
};
|
|
|
|
__auto_type Set = ^(NSMutableDictionary *aDestination, NSDictionary *aSource, NSString *aKey, Verifier aVerifier, Transformer aTransformer) {
|
|
aDestination[aKey] = Get(aSource, aKey, aVerifier, aTransformer);
|
|
};
|
|
|
|
__block NSDictionary *info = nil;
|
|
os_activity_initiate("-[SRRecorderControlStyleResourceLoader infoForStyle:]", OS_ACTIVITY_FLAG_DEFAULT, (^{
|
|
os_trace_debug_with_payload("Fetching info", ^(xpc_object_t d) {
|
|
xpc_dictionary_set_string(d, "identifier", aStyle.identifier.UTF8String);
|
|
});
|
|
|
|
@synchronized (self)
|
|
{
|
|
info = [self->_cache objectForKey:aStyle.identifier];
|
|
|
|
if (!info)
|
|
{
|
|
os_trace_debug("Info is not in cache");
|
|
NSString *resourceName = [NSString stringWithFormat:@"%@-info", aStyle.identifier];
|
|
NSData *data = [[NSDataAsset alloc] initWithName:resourceName bundle:SRBundle()].data;
|
|
|
|
if (!data)
|
|
data = [[NSDataAsset alloc] initWithName:resourceName bundle:SRBundle()].data;
|
|
|
|
if (!data)
|
|
[NSException raise:NSInternalInconsistencyException format:@"Missing %@", resourceName];
|
|
|
|
NSError *error = nil;
|
|
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
|
if (!json)
|
|
[NSException raise:NSInternalInconsistencyException
|
|
format:@"%@ is an invalid JSON: %@", resourceName, error.localizedFailureReason];
|
|
|
|
NSMutableDictionary *infoInProgress = NSMutableDictionary.dictionary;
|
|
|
|
Set(infoInProgress, json, @"supportedComponents", VerifyIsArray, ^(NSArray *anObject, NSString *aKey) {
|
|
NSMutableArray *components = [NSMutableArray arrayWithCapacity:anObject.count];
|
|
|
|
[anObject enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
NSString *subkey = [NSString stringWithFormat:@"%@.[%lu]", aKey, idx];
|
|
VerifyIsComponents(obj, subkey);
|
|
[components addObject:TransformComponents(obj, subkey)];
|
|
}];
|
|
|
|
[components addObject:[SRRecorderControlStyleComponents new]];
|
|
|
|
return (NSArray *)[components copy];
|
|
});
|
|
|
|
Set(infoInProgress, json, @"metrics", VerifyIsDictionary, ^(NSDictionary *anObject, NSString *aKey) {
|
|
NSMutableDictionary *metricsInProgress = [NSMutableDictionary dictionaryWithCapacity:anObject.count];
|
|
|
|
Set(metricsInProgress, anObject, @"minSize", VerifyIsSize, TransformSize);
|
|
Set(metricsInProgress, anObject, @"labelToCancel", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"cancelToClear", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"buttonToAlignment", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"baselineFromTop", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"alignmentToLabel", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"labelToAlignment", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"baselineLayoutOffsetFromBottom", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"baselineDrawingOffsetFromBottom", VerifyIsNumber, nil);
|
|
Set(metricsInProgress, anObject, @"focusRingCornerRadius", VerifyIsSize, TransformSize);
|
|
Set(metricsInProgress, anObject, @"focusRingInsets", VerifyIsEdgeInsets, TransformEdgeInsets);
|
|
Set(metricsInProgress, anObject, @"alignmentInsets", VerifyIsEdgeInsets, TransformEdgeInsets);
|
|
Set(metricsInProgress, anObject, @"normalLabelAttributes", VerifyIsLabelAttributes, TransformLabelAttributes);
|
|
Set(metricsInProgress, anObject, @"recordingLabelAttributes", VerifyIsLabelAttributes, TransformLabelAttributes);
|
|
Set(metricsInProgress, anObject, @"disabledLabelAttributes", VerifyIsLabelAttributes, TransformLabelAttributes);
|
|
|
|
return (NSDictionary *)[metricsInProgress copy];
|
|
});
|
|
|
|
info = [infoInProgress copy];
|
|
[self->_cache setObject:info forKey:aStyle.identifier];
|
|
}
|
|
else
|
|
os_trace_debug("Info is in cache");
|
|
}
|
|
}));
|
|
|
|
return info;
|
|
}
|
|
|
|
- (NSArray<NSString *> *)lookupPrefixesForStyle:(SRRecorderControlStyle *)aStyle
|
|
{
|
|
__block NSArray *lookupPrefixes = nil;
|
|
os_activity_initiate("-[SRRecorderControlStyleResourceLoader lookupPrefixesForStyle:]", OS_ACTIVITY_FLAG_DEFAULT, (^{
|
|
os_trace_debug_with_payload("Fetching lookup prefixes", ^(xpc_object_t d) {
|
|
xpc_dictionary_set_string(d, "identifier", aStyle.identifier.UTF8String);
|
|
});
|
|
|
|
@synchronized (self)
|
|
{
|
|
__auto_type key = [_SRRecorderControlStyleResourceLoaderCacheLookupPrefixesKey new];
|
|
key.identifier = [aStyle.identifier copy];
|
|
key.components = [aStyle.effectiveComponents copy];
|
|
|
|
lookupPrefixes = [self->_cache objectForKey:key];
|
|
|
|
if (!lookupPrefixes)
|
|
{
|
|
os_trace_debug("Lookup prefixes are not in cache");
|
|
SRRecorderControlStyleComponents *effectiveComponents = aStyle.effectiveComponents;
|
|
NSComparator cmp = ^NSComparisonResult(SRRecorderControlStyleComponents *a, SRRecorderControlStyleComponents *b) {
|
|
return [a compare:b relativeToComponents:effectiveComponents];
|
|
};
|
|
__auto_type supportedComponents = (NSArray<SRRecorderControlStyleComponents *> *)[self infoForStyle:aStyle][@"supportedComponents"];
|
|
supportedComponents = [supportedComponents sortedArrayWithOptions:NSSortStable usingComparator:cmp];
|
|
lookupPrefixes = [NSMutableArray arrayWithCapacity:supportedComponents.count];
|
|
|
|
for (SRRecorderControlStyleComponents *c in supportedComponents)
|
|
[(NSMutableArray *)lookupPrefixes addObject:[NSString stringWithFormat:@"%@%@", aStyle.identifier, c.stringRepresentation]];
|
|
|
|
lookupPrefixes = [lookupPrefixes copy];
|
|
[self->_cache setObject:lookupPrefixes forKey:key];
|
|
}
|
|
else
|
|
os_trace_debug("Lookup prefixes are in cache");
|
|
}
|
|
}));
|
|
|
|
return lookupPrefixes;
|
|
}
|
|
|
|
- (NSImage *)imageNamed:(NSString *)aName forStyle:(SRRecorderControlStyle *)aStyle
|
|
{
|
|
__block NSImage *image = nil;
|
|
os_activity_initiate("-[SRRecorderControlStyleResourceLoader imageNamed:forStyle:]", OS_ACTIVITY_FLAG_DEFAULT, (^{
|
|
os_trace_debug_with_payload("Fetching image name", ^(xpc_object_t d) {
|
|
xpc_dictionary_set_string(d, "identifier", aStyle.identifier.UTF8String);
|
|
xpc_dictionary_set_string(d, "image", aName.UTF8String);
|
|
});
|
|
|
|
@synchronized (self)
|
|
{
|
|
__auto_type key = [_SRRecorderControlStyleResourceLoaderCacheImageKey new];
|
|
key.identifier = [aStyle.identifier copy];
|
|
key.components = [aStyle.effectiveComponents copy];
|
|
key.name = [aName copy];
|
|
NSArray *imageNameCache = [self->_cache objectForKey:key];
|
|
|
|
if (!imageNameCache)
|
|
{
|
|
os_trace_debug("Image name is not in cache");
|
|
NSString *imageName = nil;
|
|
BOOL usesSRImage = YES;
|
|
|
|
for (NSString *p in [self lookupPrefixesForStyle:aStyle])
|
|
{
|
|
imageName = [NSString stringWithFormat:@"%@-%@", p, aName];
|
|
|
|
image = SRImage(imageName);
|
|
if (image)
|
|
{
|
|
usesSRImage = YES;
|
|
break;
|
|
}
|
|
|
|
image = [NSImage imageNamed:imageName];
|
|
if (image)
|
|
{
|
|
usesSRImage = NO;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!image)
|
|
[NSException raise:NSInternalInconsistencyException format:@"Missing image named %@", aName];
|
|
|
|
[self->_cache setObject:@[imageName, @(usesSRImage)] forKey:key];
|
|
}
|
|
else
|
|
{
|
|
os_trace_debug("Image name is in cache");
|
|
NSString *imageName = imageNameCache[0];
|
|
BOOL usesSRImage = [imageNameCache[1] boolValue];
|
|
|
|
if (usesSRImage)
|
|
image = SRImage(imageName);
|
|
else
|
|
image = [NSImage imageNamed:imageName];
|
|
}
|
|
}
|
|
}));
|
|
|
|
return image;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation SRRecorderControlStyle
|
|
{
|
|
NSArray<NSString *> *_currentLookupPrefixes;
|
|
|
|
NSLayoutConstraint *_backgroundTopConstraint;
|
|
NSLayoutConstraint *_backgroundLeftConstraint;
|
|
NSLayoutConstraint *_backgroundBottomConstraint;
|
|
NSLayoutConstraint *_backgroundRightConstraint;
|
|
|
|
NSLayoutConstraint *_alignmentSuggestedWidthConstraint;
|
|
NSLayoutConstraint *_alignmentWidthConstraint;
|
|
NSLayoutConstraint *_alignmentHeightConstraint;
|
|
NSLayoutConstraint *_alignmentToLabelConstraint;
|
|
|
|
NSLayoutConstraint *_labelToAlignmentConstraint;
|
|
NSLayoutConstraint *_labelToCancelConstraint;
|
|
NSLayoutConstraint *_cancelToAlignmentConstraint;
|
|
NSLayoutConstraint *_clearToAlignmentConstraint;
|
|
NSLayoutConstraint *_cancelButtonHeightConstraint;
|
|
NSLayoutConstraint *_cancelButtonWidthConstraint;
|
|
NSLayoutConstraint *_clearButtonHeightConstraint;
|
|
NSLayoutConstraint *_clearButtonWidthConstraint;
|
|
NSLayoutConstraint *_cancelToClearConstraint;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithIdentifier:nil components:nil];
|
|
}
|
|
|
|
- (instancetype)initWithIdentifier:(NSString *)anIdentifier components:(SRRecorderControlStyleComponents *)aComponents
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
if (anIdentifier)
|
|
_identifier = [anIdentifier copy];
|
|
else
|
|
{
|
|
if (@available(macOS 10.14, *))
|
|
_identifier = @"sr-mojave";
|
|
else
|
|
_identifier = @"sr-yosemite";
|
|
}
|
|
|
|
if (aComponents)
|
|
_preferredComponents = [aComponents copy];
|
|
else
|
|
_preferredComponents = [SRRecorderControlStyleComponents new];
|
|
|
|
_allowsVibrancy = NO;
|
|
_opaque = NO;
|
|
_labelDrawingFrameOpaque = YES;
|
|
_alwaysConstraints = @[];
|
|
_displayingConstraints = @[];
|
|
_recordingWithValueConstraints = @[];
|
|
_recordingWithNoValueConstraints = @[];
|
|
_alignmentGuide = [NSLayoutGuide new];
|
|
_backgroundDrawingGuide = [NSLayoutGuide new];
|
|
_labelDrawingGuide = [NSLayoutGuide new];
|
|
_cancelButtonDrawingGuide = [NSLayoutGuide new];
|
|
_clearButtonDrawingGuide = [NSLayoutGuide new];
|
|
_cancelButtonLayoutGuide = [NSLayoutGuide new];
|
|
_clearButtonLayoutGuide = [NSLayoutGuide new];
|
|
_intrinsicContentSize = NSMakeSize(NSViewNoIntrinsicMetric, NSViewNoIntrinsicMetric);
|
|
_effectiveComponents = SRRecorderControlStyleComponents.currentComponents;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
#pragma mark Properties
|
|
|
|
+ (SRRecorderControlStyleResourceLoader *)resourceLoader
|
|
{
|
|
static SRRecorderControlStyleResourceLoader *Loader = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
Loader = [SRRecorderControlStyleResourceLoader new];
|
|
});
|
|
return Loader;
|
|
}
|
|
|
|
#pragma mark Methods
|
|
|
|
- (void)addConstraints
|
|
{
|
|
__auto_type strongRecorderControl = self.recorderControl;
|
|
[strongRecorderControl addLayoutGuide:self.alignmentGuide];
|
|
[strongRecorderControl addLayoutGuide:self.backgroundDrawingGuide];
|
|
[strongRecorderControl addLayoutGuide:self.labelDrawingGuide];
|
|
[strongRecorderControl addLayoutGuide:self.cancelButtonDrawingGuide];
|
|
[strongRecorderControl addLayoutGuide:self.clearButtonDrawingGuide];
|
|
[strongRecorderControl addLayoutGuide:self.cancelButtonLayoutGuide];
|
|
[strongRecorderControl addLayoutGuide:self.clearButtonLayoutGuide];
|
|
|
|
__auto_type SetConstraint = ^(NSLayoutConstraint * __strong *var, NSLayoutConstraint *value) {
|
|
*var = value;
|
|
return value;
|
|
};
|
|
|
|
__auto_type MakeConstraint = ^(NSLayoutAnchor * _Nonnull firstItem,
|
|
NSLayoutAnchor * _Nullable secondItem,
|
|
CGFloat constant,
|
|
NSLayoutPriority priority,
|
|
NSLayoutRelation relation,
|
|
NSString *identifier)
|
|
{
|
|
NSLayoutConstraint *c = nil;
|
|
|
|
if (secondItem)
|
|
{
|
|
switch (relation)
|
|
{
|
|
case NSLayoutRelationEqual:
|
|
c = [firstItem constraintEqualToAnchor:secondItem constant:constant];
|
|
break;
|
|
case NSLayoutRelationGreaterThanOrEqual:
|
|
c = [firstItem constraintGreaterThanOrEqualToAnchor:secondItem constant:constant];
|
|
break;
|
|
case NSLayoutRelationLessThanOrEqual:
|
|
c = [firstItem constraintLessThanOrEqualToAnchor:secondItem constant:constant];
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSAssert([firstItem isKindOfClass:NSLayoutDimension.class],
|
|
@"Only dimensional anchors allow constant constraints.");
|
|
|
|
switch (relation)
|
|
{
|
|
case NSLayoutRelationEqual:
|
|
c = [(NSLayoutDimension *)firstItem constraintEqualToConstant:constant];
|
|
break;
|
|
case NSLayoutRelationGreaterThanOrEqual:
|
|
c = [(NSLayoutDimension *)firstItem constraintGreaterThanOrEqualToConstant:constant];
|
|
break;
|
|
case NSLayoutRelationLessThanOrEqual:
|
|
c = [(NSLayoutDimension *)firstItem constraintLessThanOrEqualToConstant:constant];
|
|
break;
|
|
}
|
|
}
|
|
|
|
c.priority = priority;
|
|
c.identifier = identifier;
|
|
return c;
|
|
};
|
|
|
|
__auto_type MakeEqConstraint = ^(NSLayoutAnchor * _Nonnull firstItem, NSLayoutAnchor * _Nullable secondItem, NSString *identifier) {
|
|
return MakeConstraint(firstItem, secondItem, 0.0, NSLayoutPriorityRequired, NSLayoutRelationEqual, identifier);
|
|
};
|
|
|
|
__auto_type MakeGteConstraint = ^(NSLayoutAnchor * _Nonnull firstItem, NSLayoutAnchor * _Nullable secondItem, NSString *identifier) {
|
|
return MakeConstraint(firstItem, secondItem, 0.0, NSLayoutPriorityRequired, NSLayoutRelationGreaterThanOrEqual, identifier);
|
|
};
|
|
|
|
_alwaysConstraints = @[
|
|
MakeEqConstraint(self.alignmentGuide.topAnchor,
|
|
strongRecorderControl.topAnchor,
|
|
@"SR_alignmentGuide_topToView"),
|
|
MakeEqConstraint(self.alignmentGuide.leftAnchor,
|
|
strongRecorderControl.leftAnchor,
|
|
@"SR_alignmentGuide_leftToView"),
|
|
MakeEqConstraint(self.alignmentGuide.rightAnchor,
|
|
strongRecorderControl.rightAnchor,
|
|
@"SR_alignmentGuide_rightToView"),
|
|
MakeConstraint(self.alignmentGuide.bottomAnchor,
|
|
strongRecorderControl.bottomAnchor,
|
|
0.0,
|
|
NSLayoutPriorityDefaultHigh,
|
|
NSLayoutRelationEqual,
|
|
@"SR_alignmentGuide_bottomToView"),
|
|
SetConstraint(&_alignmentHeightConstraint, MakeEqConstraint(self.alignmentGuide.heightAnchor,
|
|
nil,
|
|
@"SR_alignmentGuide_height")),
|
|
SetConstraint(&_alignmentWidthConstraint, MakeGteConstraint(self.alignmentGuide.widthAnchor,
|
|
nil,
|
|
@"SR_alignmentGuide_width")),
|
|
SetConstraint(&_alignmentSuggestedWidthConstraint, MakeConstraint(self.alignmentGuide.widthAnchor,
|
|
nil,
|
|
0.0,
|
|
NSLayoutPriorityDefaultLow,
|
|
NSLayoutRelationEqual,
|
|
@"SR_alignmentGuide_suggestedWidth")),
|
|
|
|
SetConstraint(&_backgroundTopConstraint, MakeEqConstraint(self.alignmentGuide.topAnchor,
|
|
self.backgroundDrawingGuide.topAnchor,
|
|
@"SR_backgroundDrawingGuide_topToAlignment")),
|
|
SetConstraint(&_backgroundLeftConstraint, MakeEqConstraint(self.alignmentGuide.leftAnchor,
|
|
self.backgroundDrawingGuide.leftAnchor,
|
|
@"SR_backgroundDrawingGuide_leftToAlignment")),
|
|
SetConstraint(&_backgroundBottomConstraint, MakeEqConstraint(self.backgroundDrawingGuide.bottomAnchor,
|
|
self.alignmentGuide.bottomAnchor,
|
|
@"SR_backgroundDrawingGuide_bottomToAlignment")),
|
|
SetConstraint(&_backgroundRightConstraint, MakeEqConstraint(self.backgroundDrawingGuide.rightAnchor,
|
|
self.alignmentGuide.rightAnchor,
|
|
@"SR_backgroundDrawingGuide_rightToAlignment")),
|
|
|
|
MakeEqConstraint(self.labelDrawingGuide.topAnchor,
|
|
self.alignmentGuide.topAnchor,
|
|
@"SR_labelDrawingGuide_topToAlignment"),
|
|
SetConstraint(&_alignmentToLabelConstraint, MakeGteConstraint(self.labelDrawingGuide.leadingAnchor,
|
|
self.alignmentGuide.leadingAnchor,
|
|
@"SR_labelDrawingGuide_leadingToAlignment")),
|
|
MakeEqConstraint(self.labelDrawingGuide.bottomAnchor,
|
|
self.alignmentGuide.bottomAnchor,
|
|
@"SR_labelDrawingGuide_bottomToAlignment"),
|
|
MakeConstraint(self.labelDrawingGuide.centerXAnchor,
|
|
self.alignmentGuide.centerXAnchor,
|
|
0.0,
|
|
SRRecorderControlLabelWidthPriority - 1,
|
|
NSLayoutRelationEqual,
|
|
@"SR_labelDrawingGuide_centerXToAlignment")
|
|
];
|
|
|
|
_displayingConstraints = @[
|
|
SetConstraint(&_labelToAlignmentConstraint, MakeGteConstraint(self.alignmentGuide.trailingAnchor,
|
|
self.labelDrawingGuide.trailingAnchor,
|
|
@"SR_labelDrawingGuide_trailingToAlignment")),
|
|
];
|
|
|
|
_recordingWithNoValueConstraints = @[
|
|
SetConstraint(&_labelToCancelConstraint, MakeGteConstraint(self.cancelButtonDrawingGuide.leadingAnchor,
|
|
self.labelDrawingGuide.trailingAnchor,
|
|
@"SR_labelDrawingGuide_trailingToCancel")),
|
|
|
|
SetConstraint(&_cancelToAlignmentConstraint, MakeEqConstraint(self.alignmentGuide.trailingAnchor,
|
|
self.cancelButtonDrawingGuide.trailingAnchor,
|
|
@"SR_cancelButtonDrawingGuide_trailingToAlignment")),
|
|
MakeEqConstraint(self.cancelButtonDrawingGuide.centerYAnchor,
|
|
self.alignmentGuide.centerYAnchor,
|
|
@"SR_cancelButtonDrawingGuide_centerYAlignment"),
|
|
SetConstraint(&_cancelButtonWidthConstraint, MakeEqConstraint(self.cancelButtonDrawingGuide.widthAnchor,
|
|
nil,
|
|
@"SR_cancelButtonDrawingGuide_width")),
|
|
SetConstraint(&_cancelButtonHeightConstraint, MakeEqConstraint(self.cancelButtonDrawingGuide.heightAnchor,
|
|
nil,
|
|
@"SR_cancelButtonDrawingGuide_height")),
|
|
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.topAnchor,
|
|
self.alignmentGuide.topAnchor,
|
|
@"SR_cancelButtonLayoutGuide_topToAlignment"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.leadingAnchor,
|
|
self.cancelButtonDrawingGuide.leadingAnchor,
|
|
@"SR_cancelButtonLayoutGuide_leadingToDrawing"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.bottomAnchor,
|
|
self.alignmentGuide.bottomAnchor,
|
|
@"SR_cancelButtonLayoutGuide_bottomToAlignment"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.trailingAnchor,
|
|
self.alignmentGuide.trailingAnchor,
|
|
@"SR_cancelButtonLayoutGuide_trailingToAlignment"),
|
|
];
|
|
|
|
_recordingWithValueConstraints = @[
|
|
_labelToCancelConstraint,
|
|
|
|
MakeEqConstraint(self.cancelButtonDrawingGuide.centerYAnchor,
|
|
self.alignmentGuide.centerYAnchor,
|
|
@"SR_cancelButtonDrawingGuide_centerYToAlignment"),
|
|
SetConstraint(&_cancelToClearConstraint, MakeEqConstraint(self.clearButtonDrawingGuide.leadingAnchor,
|
|
self.cancelButtonDrawingGuide.trailingAnchor,
|
|
@"SR_cancelButtonDrawingGuide_trailingToClear")),
|
|
_cancelButtonWidthConstraint,
|
|
_cancelButtonHeightConstraint,
|
|
|
|
MakeEqConstraint(self.clearButtonDrawingGuide.centerYAnchor,
|
|
self.alignmentGuide.centerYAnchor,
|
|
@"SR_clearButtonDrawingGuide_centerYToAlignment"),
|
|
SetConstraint(&_clearToAlignmentConstraint, MakeEqConstraint(self.alignmentGuide.trailingAnchor,
|
|
self.clearButtonDrawingGuide.trailingAnchor,
|
|
@"SR_clearButtonDrawingGuide_trailingToAlignment")),
|
|
SetConstraint(&_clearButtonWidthConstraint, MakeEqConstraint(self.clearButtonDrawingGuide.widthAnchor,
|
|
nil,
|
|
@"SR_clearButtonDrawingGuide_width")),
|
|
SetConstraint(&_clearButtonHeightConstraint, MakeEqConstraint(self.clearButtonDrawingGuide.heightAnchor,
|
|
nil,
|
|
@"SR_clearButtonDrawingGuide_height")),
|
|
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.topAnchor,
|
|
self.alignmentGuide.topAnchor,
|
|
@"SR_cancelButtonLayoutGuide_topToAlignment"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.leadingAnchor,
|
|
self.cancelButtonDrawingGuide.leadingAnchor,
|
|
@"SR_cancelButtonLayoutGuide_leadingToDrawing"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.bottomAnchor,
|
|
self.alignmentGuide.bottomAnchor,
|
|
@"SR_cancelButtonLayoutGuide_bottomToAlignment"),
|
|
MakeEqConstraint(self.cancelButtonLayoutGuide.trailingAnchor,
|
|
self.cancelButtonDrawingGuide.trailingAnchor,
|
|
@"SR_cancelButtonLayoutGuide_trailingToDrawing"),
|
|
|
|
MakeEqConstraint(self.clearButtonLayoutGuide.topAnchor,
|
|
self.alignmentGuide.topAnchor,
|
|
@"SR_clearButtonLayoutGuide_topToAlignment"),
|
|
MakeEqConstraint(self.clearButtonLayoutGuide.leadingAnchor,
|
|
self.clearButtonDrawingGuide.leadingAnchor,
|
|
@"SR_clearButtonLayoutGuide_leadingToDrawing"),
|
|
MakeEqConstraint(self.clearButtonLayoutGuide.bottomAnchor,
|
|
self.alignmentGuide.bottomAnchor,
|
|
@"SR_clearButtonLayoutGuide_bottomToAlignment"),
|
|
MakeEqConstraint(self.clearButtonLayoutGuide.trailingAnchor,
|
|
self.alignmentGuide.trailingAnchor,
|
|
@"SR_clearButtonLayoutGuide_trailingToAlignment"),
|
|
];
|
|
|
|
strongRecorderControl.needsUpdateConstraints = YES;
|
|
}
|
|
|
|
#pragma mark SRRecorderControlStyling
|
|
@synthesize identifier = _identifier;
|
|
@synthesize allowsVibrancy = _allowsVibrancy;
|
|
@synthesize opaque = _opaque;
|
|
@synthesize labelDrawingFrameOpaque = _labelDrawingFrameOpaque;
|
|
@synthesize normalLabelAttributes = _normalLabelAttributes;
|
|
@synthesize recordingLabelAttributes = _recordingLabelAttributes;
|
|
@synthesize disabledLabelAttributes = _disabledLabelAttributes;
|
|
@synthesize bezelNormalLeft = _bezelNormalLeft;
|
|
@synthesize bezelNormalCenter = _bezelNormalCenter;
|
|
@synthesize bezelNormalRight = _bezelNormalRight;
|
|
@synthesize bezelPressedLeft = _bezelPressedLeft;
|
|
@synthesize bezelPressedCenter = _bezelPressedCenter;
|
|
@synthesize bezelPressedRight = _bezelPressedRight;
|
|
@synthesize bezelRecordingLeft = _bezelRecordingLeft;
|
|
@synthesize bezelRecordingCenter = _bezelRecordingCenter;
|
|
@synthesize bezelRecordingRight = _bezelRecordingRight;
|
|
@synthesize bezelDisabledLeft = _bezelDisabledLeft;
|
|
@synthesize bezelDisabledCenter = _bezelDisabledCenter;
|
|
@synthesize bezelDisabledRight = _bezelDisabledRight;
|
|
@synthesize cancelButton = _cancelButton;
|
|
@synthesize cancelButtonPressed = _cancelButtonPressed;
|
|
@synthesize clearButton = _clearButton;
|
|
@synthesize clearButtonPressed = _clearButtonPressed;
|
|
@synthesize focusRingCornerRadius = _focusRingCornerRadius;
|
|
@synthesize focusRingInsets = _focusRingInsets;
|
|
@synthesize baselineLayoutOffsetFromBottom = _baselineLayoutOffsetFromBottom;
|
|
@synthesize baselineDrawingOffsetFromBottom = _baselineDrawingOffsetFromBottom;
|
|
@synthesize alignmentRectInsets = _alignmentRectInsets;
|
|
@synthesize intrinsicContentSize = _intrinsicContentSize;
|
|
@synthesize alignmentGuide = _alignmentGuide;
|
|
@synthesize backgroundDrawingGuide = _backgroundDrawingGuide;
|
|
@synthesize labelDrawingGuide = _labelDrawingGuide;
|
|
@synthesize cancelButtonDrawingGuide = _cancelButtonDrawingGuide;
|
|
@synthesize clearButtonDrawingGuide = _clearButtonDrawingGuide;
|
|
@synthesize cancelButtonLayoutGuide = _cancelButtonLayoutGuide;
|
|
@synthesize clearButtonLayoutGuide = _clearButtonLayoutGuide;
|
|
@synthesize alwaysConstraints = _alwaysConstraints;
|
|
@synthesize displayingConstraints = _displayingConstraints;
|
|
@synthesize recordingWithNoValueConstraints = _recordingWithNoValueConstraints;
|
|
@synthesize recordingWithValueConstraints = _recordingWithValueConstraints;
|
|
@synthesize preferredComponents = _preferredComponents;
|
|
|
|
- (NSString *)noValueNormalLabel
|
|
{
|
|
return SRLoc(@"Record Shortcut");
|
|
}
|
|
|
|
- (NSString *)noValueDisableLabel
|
|
{
|
|
return SRLoc(@"Record Shortcut");
|
|
}
|
|
|
|
- (NSString *)noValueRecordingLabel
|
|
{
|
|
return SRLoc(@"Type shortcut");
|
|
}
|
|
|
|
- (void)prepareForRecorderControl:(SRRecorderControl *)aControl
|
|
{
|
|
NSAssert(_recorderControl == nil, @"Style was not removed properly.");
|
|
|
|
[self willChangeValueForKey:@"recorderControl"];
|
|
_recorderControl = aControl;
|
|
[self didChangeValueForKey:@"recorderControl"];
|
|
|
|
if (!aControl)
|
|
return;
|
|
|
|
[self addConstraints];
|
|
[self recorderControlAppearanceDidChange:nil];
|
|
|
|
aControl.needsDisplay = YES;
|
|
}
|
|
|
|
- (void)prepareForRemoval
|
|
{
|
|
__auto_type strongRecorderControl = _recorderControl;
|
|
NSAssert(strongRecorderControl != nil, @"Style was not applied properly.");
|
|
|
|
[strongRecorderControl removeLayoutGuide:_alignmentGuide];
|
|
[strongRecorderControl removeLayoutGuide:_backgroundDrawingGuide];
|
|
[strongRecorderControl removeLayoutGuide:_labelDrawingGuide];
|
|
[strongRecorderControl removeLayoutGuide:_cancelButtonDrawingGuide];
|
|
[strongRecorderControl removeLayoutGuide:_clearButtonDrawingGuide];
|
|
[strongRecorderControl removeLayoutGuide:_cancelButtonLayoutGuide];
|
|
[strongRecorderControl removeLayoutGuide:_clearButtonLayoutGuide];
|
|
|
|
[self willChangeValueForKey:@"recorderControl"];
|
|
strongRecorderControl = nil;
|
|
[self didChangeValueForKey:@"recorderControl"];
|
|
}
|
|
|
|
- (void)recorderControlAppearanceDidChange:(nullable id)aReason
|
|
{
|
|
__auto_type UpdateEffectiveComponents = ^{
|
|
SRRecorderControlStyleComponents *newComponents = nil;
|
|
|
|
if (self->_preferredComponents.isSpecified)
|
|
newComponents = [self->_preferredComponents copy];
|
|
else
|
|
{
|
|
SRRecorderControlStyleComponents *current = [SRRecorderControlStyleComponents currentComponentsForView:self.recorderControl];
|
|
|
|
__auto_type appearance = self->_preferredComponents.appearance;
|
|
if (appearance == SRRecorderControlStyleComponentsAppearanceUnspecified)
|
|
appearance = current.appearance ? current.appearance : SRRecorderControlStyleComponentsAppearanceAqua;
|
|
|
|
__auto_type accessibility = self->_preferredComponents.accessibility;
|
|
if (!accessibility)
|
|
accessibility = current.accessibility ? current.accessibility : SRRecorderControlStyleComponentsAccessibilityNone;
|
|
|
|
__auto_type layoutDirection = self->_preferredComponents.layoutDirection;
|
|
if (!layoutDirection)
|
|
layoutDirection = current.layoutDirection ? current.layoutDirection : SRRecorderControlStyleComponentsLayoutDirectionLeftToRight;
|
|
|
|
__auto_type tint = self->_preferredComponents.tint;
|
|
if (!tint)
|
|
tint = current.tint ? current.tint : SRRecorderControlStyleComponentsTintBlue;
|
|
|
|
newComponents = [[SRRecorderControlStyleComponents alloc] initWithAppearance:appearance
|
|
accessibility:accessibility
|
|
layoutDirection:layoutDirection
|
|
tint:tint];
|
|
}
|
|
|
|
[self willChangeValueForKey:@"effectiveComponents"];
|
|
self->_effectiveComponents = newComponents;
|
|
[self didChangeValueForKey:@"effectiveComponents"];
|
|
};
|
|
|
|
UpdateEffectiveComponents();
|
|
|
|
__auto_type newLookupPrefixes = [self.class.resourceLoader lookupPrefixesForStyle:self];
|
|
if ([newLookupPrefixes isEqual:_currentLookupPrefixes])
|
|
return;
|
|
|
|
// Update image if needed using KVC for KVO notifications.
|
|
__auto_type UpdateImage = ^(NSString *imageName, SEL propName, NSRect frame) {
|
|
NSImage *newImage = [self.class.resourceLoader imageNamed:imageName forStyle:self];
|
|
NSString *propNameString = NSStringFromSelector(propName);
|
|
|
|
NSAssert(newImage != nil, @"Missing image for %@!", imageName);
|
|
|
|
if ([newImage isEqual:[self valueForKey:propNameString]])
|
|
return;
|
|
|
|
[self setValue:newImage forKey:propNameString];
|
|
|
|
if (!NSIsEmptyRect(frame))
|
|
[self.recorderControl setNeedsDisplayInRect:frame];
|
|
};
|
|
|
|
__auto_type strongRecorderControl = self.recorderControl;
|
|
NSRect controlBounds = strongRecorderControl.bounds;
|
|
|
|
UpdateImage(@"bezel-normal-left", @selector(bezelNormalLeft), controlBounds);
|
|
UpdateImage(@"bezel-normal-center", @selector(bezelNormalCenter), controlBounds);
|
|
UpdateImage(@"bezel-normal-right", @selector(bezelNormalRight), controlBounds);
|
|
|
|
UpdateImage(@"bezel-pressed-left", @selector(bezelPressedLeft), controlBounds);
|
|
UpdateImage(@"bezel-pressed-center", @selector(bezelPressedCenter), controlBounds);
|
|
UpdateImage(@"bezel-pressed-right", @selector(bezelPressedRight), controlBounds);
|
|
|
|
UpdateImage(@"bezel-recording-left", @selector(bezelRecordingLeft), controlBounds);
|
|
UpdateImage(@"bezel-recording-center", @selector(bezelRecordingCenter), controlBounds);
|
|
UpdateImage(@"bezel-recording-right", @selector(bezelRecordingRight), controlBounds);
|
|
|
|
UpdateImage(@"bezel-disabled-left", @selector(bezelDisabledLeft), controlBounds);
|
|
UpdateImage(@"bezel-disabled-center", @selector(bezelDisabledCenter), controlBounds);
|
|
UpdateImage(@"bezel-disabled-right", @selector(bezelDisabledRight), controlBounds);
|
|
|
|
UpdateImage(@"button-cancel-normal", @selector(cancelButton), self.cancelButtonDrawingGuide.frame);
|
|
UpdateImage(@"button-cancel-pressed", @selector(cancelButtonPressed), self.cancelButtonDrawingGuide.frame);
|
|
|
|
UpdateImage(@"button-clear-normal", @selector(clearButton), self.clearButtonDrawingGuide.frame);
|
|
UpdateImage(@"button-clear-pressed", @selector(clearButtonPressed), self.clearButtonDrawingGuide.frame);
|
|
|
|
_cancelButtonWidthConstraint.constant = self.cancelButton.size.width;
|
|
_cancelButtonHeightConstraint.constant = self.cancelButton.size.height;
|
|
_clearButtonWidthConstraint.constant = self.clearButton.size.width;
|
|
_clearButtonHeightConstraint.constant = self.clearButton.size.height;
|
|
|
|
if (!_currentLookupPrefixes)
|
|
{
|
|
__auto_type metrics = (NSDictionary *)[self.class.resourceLoader infoForStyle:self][@"metrics"];
|
|
|
|
_alignmentRectInsets = [metrics[@"alignmentInsets"] edgeInsetsValue];
|
|
_focusRingCornerRadius = [metrics[@"focusRingCornerRadius"] sizeValue];
|
|
_focusRingInsets = [metrics[@"focusRingInsets"] edgeInsetsValue];
|
|
_baselineLayoutOffsetFromBottom = [metrics[@"baselineLayoutOffsetFromBottom"] doubleValue];
|
|
_baselineDrawingOffsetFromBottom = [metrics[@"baselineDrawingOffsetFromBottom"] doubleValue];
|
|
_normalLabelAttributes = metrics[@"normalLabelAttributes"];
|
|
_recordingLabelAttributes = metrics[@"recordingLabelAttributes"];
|
|
_disabledLabelAttributes = metrics[@"disabledLabelAttributes"];
|
|
|
|
NSSize minSize = [metrics[@"minSize"] sizeValue];
|
|
_alignmentWidthConstraint.constant = fdim(minSize.width, _alignmentRectInsets.left + _alignmentRectInsets.right);
|
|
_alignmentHeightConstraint.constant = fdim(minSize.height, _alignmentRectInsets.top + _alignmentRectInsets.bottom);
|
|
|
|
_backgroundTopConstraint.constant = _alignmentRectInsets.top;
|
|
_backgroundLeftConstraint.constant = _alignmentRectInsets.left;
|
|
_backgroundBottomConstraint.constant = _alignmentRectInsets.bottom;
|
|
_backgroundRightConstraint.constant = _alignmentRectInsets.right;
|
|
|
|
_alignmentToLabelConstraint.constant = [metrics[@"alignmentToLabel"] doubleValue];
|
|
_labelToAlignmentConstraint.constant = [metrics[@"labelToAlignment"] doubleValue];
|
|
_labelToCancelConstraint.constant = [metrics[@"labelToCancel"] doubleValue];
|
|
_cancelToAlignmentConstraint.constant = [metrics[@"buttonToAlignment"] doubleValue];
|
|
_clearToAlignmentConstraint.constant = [metrics[@"buttonToAlignment"] doubleValue];
|
|
_cancelToClearConstraint.constant = [metrics[@"cancelToClear"] doubleValue];
|
|
|
|
CGFloat maxExpectedLeadingLabelOffset = _alignmentToLabelConstraint.constant;
|
|
|
|
CGFloat normalLabelWidth = ceil([self.noValueNormalLabel sizeWithAttributes:_normalLabelAttributes].width);
|
|
CGFloat disabledLabelWidth = ceil([self.noValueDisableLabel sizeWithAttributes:_disabledLabelAttributes].width);
|
|
CGFloat recordingLabelWidth = ceil([self.noValueRecordingLabel sizeWithAttributes:_recordingLabelAttributes].width);
|
|
CGFloat maxExpectedLabelWidth = MAX(MAX(normalLabelWidth, disabledLabelWidth), recordingLabelWidth);
|
|
|
|
CGFloat maxExpectedTrailingLabelOffset = MAX(_labelToAlignmentConstraint.constant,
|
|
_labelToCancelConstraint.constant +
|
|
_cancelButtonWidthConstraint.constant +
|
|
_cancelToClearConstraint.constant +
|
|
_clearButtonWidthConstraint.constant +
|
|
_clearToAlignmentConstraint.constant);
|
|
|
|
_alignmentSuggestedWidthConstraint.constant = maxExpectedLeadingLabelOffset + maxExpectedLabelWidth + maxExpectedTrailingLabelOffset;
|
|
|
|
_intrinsicContentSize = NSMakeSize(_alignmentSuggestedWidthConstraint.constant, _alignmentHeightConstraint.constant);
|
|
|
|
[strongRecorderControl noteFocusRingMaskChanged];
|
|
[strongRecorderControl invalidateIntrinsicContentSize];
|
|
strongRecorderControl.needsDisplay = YES;
|
|
}
|
|
|
|
_currentLookupPrefixes = newLookupPrefixes;
|
|
}
|
|
|
|
#pragma mark NSCopying
|
|
|
|
- (instancetype)copyWithZone:(NSZone *)aZone
|
|
{
|
|
return [[self.class alloc] initWithIdentifier:self.identifier components:self.preferredComponents];
|
|
}
|
|
|
|
@end
|