mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Fix linking
This commit is contained in:
parent
e6b96f6cd4
commit
461c278867
3
.bazelrc
3
.bazelrc
@ -6,4 +6,7 @@ build --swiftcopt='-Xcc'
|
||||
build --swiftcopt='-w'
|
||||
build --spawn_strategy=local
|
||||
build --strategy=SwiftCompile=local
|
||||
build --features=debug_prefix_map_pwd_is_dot
|
||||
build --features=swift.cacheable_swiftmodules
|
||||
build --features=swift.debug_prefix_map
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -14,3 +14,6 @@
|
||||
[submodule "submodules/TgVoip/libtgvoip"]
|
||||
path = submodules/TgVoip/libtgvoip
|
||||
url = https://github.com/telegramdesktop/libtgvoip.git
|
||||
[submodule "build-system/tulsi"]
|
||||
path = build-system/tulsi
|
||||
url = https://github.com/ali-fareed/tulsi.git
|
||||
|
@ -36,6 +36,17 @@ GEN_DIRECTORY="build-input/gen/project"
|
||||
rm -rf "$GEN_DIRECTORY"
|
||||
mkdir -p "$GEN_DIRECTORY"
|
||||
|
||||
pushd "build-system/tulsi"
|
||||
"$BAZEL" build //:tulsi --xcode_version=$(cat "build-system/xcode_version")
|
||||
popd
|
||||
|
||||
TULSI_DIRECTORY="build-input/gen/project"
|
||||
TULSI_APP="build-input/gen/project/Tulsi.app"
|
||||
TULSI="$TULSI_APP/Contents/MacOS/Tulsi"
|
||||
mkdir -p "$TULSI_DIRECTORY"
|
||||
|
||||
unzip -oq "build-system/tulsi/bazel-bin/tulsi.zip" -d "$TULSI_DIRECTORY"
|
||||
|
||||
CORE_COUNT=$(sysctl -n hw.logicalcpu)
|
||||
CORE_COUNT_MINUS_ONE=$(expr ${CORE_COUNT} \- 1)
|
||||
|
||||
@ -51,7 +62,7 @@ if [ "$BAZEL_CACHE_DIR" != "" ]; then
|
||||
BAZEL_OPTIONS=("${BAZEL_OPTIONS[@]}" --disk_cache="$(echo $BAZEL_CACHE_DIR | sed -e 's/[\/&]/\\&/g')")
|
||||
fi
|
||||
|
||||
$HOME/Applications/Tulsi.app/Contents/MacOS/Tulsi -- \
|
||||
"$TULSI" -- \
|
||||
--verbose \
|
||||
--create-tulsiproj Telegram \
|
||||
--workspaceroot ./ \
|
||||
@ -68,7 +79,7 @@ done
|
||||
|
||||
sed -i "" -e '1h;2,$H;$!d;g' -e 's/\("sourceFilters" : \[\n[ ]*\)"\.\/\.\.\."/\1"Telegram\/...", "submodules\/..."/' "$GEN_DIRECTORY/Telegram.tulsiproj/Configs/Telegram.tulsigen"
|
||||
|
||||
${HOME}/Applications/Tulsi.app/Contents/MacOS/Tulsi -- \
|
||||
"$TULSI" -- \
|
||||
--verbose \
|
||||
--genconfig "$GEN_DIRECTORY/Telegram.tulsiproj:Telegram" \
|
||||
--bazel "$BAZEL" \
|
||||
|
1
build-system/tulsi
Submodule
1
build-system/tulsi
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ee185c4c20ea4384bc3cbf8ccd8705c904154abb
|
1
build-system/xcode_version
Normal file
1
build-system/xcode_version
Normal file
@ -0,0 +1 @@
|
||||
11.3.1
|
@ -0,0 +1,21 @@
|
||||
//
|
||||
// ASAbstractLayoutController+FrameworkPrivate.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
//
|
||||
// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode.
|
||||
// These methods must never be called or overridden by other classes.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
|
||||
@interface ASAbstractLayoutController (FrameworkPrivate)
|
||||
|
||||
+ (std::vector<std::vector<ASRangeTuningParameters>>)defaultTuningParameters;
|
||||
|
||||
@end
|
186
submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm
Normal file
186
submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm
Normal file
@ -0,0 +1,186 @@
|
||||
//
|
||||
// ASAsciiArtBoxCreator.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <cmath>
|
||||
|
||||
static const NSUInteger kDebugBoxPadding = 2;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation)
|
||||
{
|
||||
PIDebugBoxPaddingLocationFront,
|
||||
PIDebugBoxPaddingLocationEnd,
|
||||
PIDebugBoxPaddingLocationBoth
|
||||
};
|
||||
|
||||
@interface NSString(PIDebugBox)
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSString(PIDebugBox)
|
||||
|
||||
+ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount NS_RETURNS_RETAINED
|
||||
{
|
||||
NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[stringToRepeat length] * repeatCount];
|
||||
for (NSUInteger index = 0; index < repeatCount; index++) {
|
||||
[string appendString:stringToRepeat];
|
||||
}
|
||||
return [string copy];
|
||||
}
|
||||
|
||||
- (NSString *)debugbox_stringByAddingPadding:(NSString *)padding count:(NSUInteger)count location:(PIDebugBoxPaddingLocation)location
|
||||
{
|
||||
NSString *paddingString = [NSString debugbox_stringWithString:padding repeatedCount:count];
|
||||
switch (location) {
|
||||
case PIDebugBoxPaddingLocationFront:
|
||||
return [NSString stringWithFormat:@"%@%@", paddingString, self];
|
||||
case PIDebugBoxPaddingLocationEnd:
|
||||
return [NSString stringWithFormat:@"%@%@", self, paddingString];
|
||||
case PIDebugBoxPaddingLocationBoth:
|
||||
return [NSString stringWithFormat:@"%@%@%@", paddingString, self, paddingString];
|
||||
}
|
||||
return [self copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASAsciiArtBoxCreator
|
||||
|
||||
+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent
|
||||
{
|
||||
if ([children count] == 0) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
NSMutableArray *childrenLines = [NSMutableArray array];
|
||||
|
||||
// split the children into lines
|
||||
NSUInteger lineCountPerChild = 0;
|
||||
for (NSString *child in children) {
|
||||
NSArray *lines = [child componentsSeparatedByString:@"\n"];
|
||||
lineCountPerChild = MAX(lineCountPerChild, [lines count]);
|
||||
}
|
||||
|
||||
for (NSString *child in children) {
|
||||
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
|
||||
NSUInteger topPadding = ceil((CGFloat)(lineCountPerChild - [lines count])/2.0);
|
||||
NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0;
|
||||
NSUInteger lineLength = [lines[0] length];
|
||||
|
||||
for (NSUInteger index = 0; index < topPadding; index++) {
|
||||
[lines insertObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength] atIndex:0];
|
||||
}
|
||||
for (NSUInteger index = 0; index < bottomPadding; index++) {
|
||||
[lines addObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength]];
|
||||
}
|
||||
[childrenLines addObject:lines];
|
||||
}
|
||||
|
||||
NSMutableArray *concatenatedLines = [NSMutableArray array];
|
||||
NSString *padding = [NSString debugbox_stringWithString:@" " repeatedCount:kDebugBoxPadding];
|
||||
for (NSUInteger index = 0; index < lineCountPerChild; index++) {
|
||||
NSMutableString *line = [[NSMutableString alloc] init];
|
||||
[line appendFormat:@"|%@",padding];
|
||||
for (NSArray *childLines in childrenLines) {
|
||||
[line appendFormat:@"%@%@", childLines[index], padding];
|
||||
}
|
||||
[line appendString:@"|"];
|
||||
[concatenatedLines addObject:line];
|
||||
}
|
||||
|
||||
// surround the lines in a box
|
||||
NSUInteger totalLineLength = [concatenatedLines[0] length];
|
||||
if (totalLineLength < [parent length]) {
|
||||
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength;
|
||||
NSUInteger leftPadding = ceil((CGFloat)difference/2.0);
|
||||
NSUInteger rightPadding = difference/2;
|
||||
|
||||
NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd];
|
||||
NSString *rightString = [@"|" debugbox_stringByAddingPadding:@" " count:rightPadding location:PIDebugBoxPaddingLocationFront];
|
||||
|
||||
NSMutableArray *paddedLines = [NSMutableArray array];
|
||||
for (NSString *line in concatenatedLines) {
|
||||
NSString *paddedLine = [line stringByReplacingOccurrencesOfString:@"|" withString:leftString options:NSCaseInsensitiveSearch range:NSMakeRange(0, 1)];
|
||||
paddedLine = [paddedLine stringByReplacingOccurrencesOfString:@"|" withString:rightString options:NSCaseInsensitiveSearch range:NSMakeRange([paddedLine length] - 1, 1)];
|
||||
[paddedLines addObject:paddedLine];
|
||||
}
|
||||
concatenatedLines = paddedLines;
|
||||
// totalLineLength += difference;
|
||||
}
|
||||
concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent];
|
||||
return [concatenatedLines componentsJoinedByString:@"\n"];
|
||||
|
||||
}
|
||||
|
||||
+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent
|
||||
{
|
||||
if ([children count] == 0) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
NSMutableArray *childrenLines = [NSMutableArray array];
|
||||
|
||||
NSUInteger maxChildLength = 0;
|
||||
for (NSString *child in children) {
|
||||
NSArray *lines = [child componentsSeparatedByString:@"\n"];
|
||||
maxChildLength = MAX(maxChildLength, [lines[0] length]);
|
||||
}
|
||||
|
||||
NSUInteger rightPadding = 0;
|
||||
NSUInteger leftPadding = 0;
|
||||
|
||||
if (maxChildLength < [parent length]) {
|
||||
NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength;
|
||||
leftPadding = ceil((CGFloat)difference/2.0);
|
||||
rightPadding = difference/2;
|
||||
}
|
||||
|
||||
NSString *rightPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightPadding + kDebugBoxPadding];
|
||||
NSString *leftPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftPadding + kDebugBoxPadding];
|
||||
|
||||
for (NSString *child in children) {
|
||||
NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy];
|
||||
|
||||
NSUInteger leftLinePadding = ceil((CGFloat)(maxChildLength - [lines[0] length])/2.0);
|
||||
NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0;
|
||||
|
||||
for (NSString *line in lines) {
|
||||
NSString *rightLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightLinePadding];
|
||||
rightLinePaddingString = [NSString stringWithFormat:@"%@%@|", rightLinePaddingString, rightPaddingString];
|
||||
|
||||
NSString *leftLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftLinePadding];
|
||||
leftLinePaddingString = [NSString stringWithFormat:@"|%@%@", leftLinePaddingString, leftPaddingString];
|
||||
|
||||
NSString *paddingLine = [NSString stringWithFormat:@"%@%@%@", leftLinePaddingString, line, rightLinePaddingString];
|
||||
[childrenLines addObject:paddingLine];
|
||||
}
|
||||
}
|
||||
|
||||
childrenLines = [self appendTopAndBottomToBoxString:childrenLines parent:parent];
|
||||
return [childrenLines componentsJoinedByString:@"\n"];
|
||||
}
|
||||
|
||||
+ (NSMutableArray *)appendTopAndBottomToBoxString:(NSMutableArray *)boxStrings parent:(NSString *)parent
|
||||
{
|
||||
NSUInteger totalLineLength = [boxStrings[0] length];
|
||||
[boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]];
|
||||
|
||||
NSUInteger leftPadding = ceil(((CGFloat)(totalLineLength - [parent length]))/2.0);
|
||||
NSUInteger rightPadding = (totalLineLength - [parent length])/2;
|
||||
|
||||
NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront];
|
||||
topLine = [topLine debugbox_stringByAddingPadding:@"-" count:rightPadding location:PIDebugBoxPaddingLocationEnd];
|
||||
[boxStrings insertObject:topLine atIndex:0];
|
||||
|
||||
return boxStrings;
|
||||
}
|
||||
|
||||
@end
|
58
submodules/AsyncDisplayKit/Source/ASAssert.mm
Normal file
58
submodules/AsyncDisplayKit/Source/ASAssert.mm
Normal file
@ -0,0 +1,58 @@
|
||||
//
|
||||
// ASAssert.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if AS_TLS_AVAILABLE
|
||||
|
||||
static _Thread_local int tls_mainThreadAssertionsDisabledCount;
|
||||
BOOL ASMainThreadAssertionsAreDisabled() {
|
||||
return tls_mainThreadAssertionsDisabledCount > 0;
|
||||
}
|
||||
|
||||
void ASPushMainThreadAssertionsDisabled() {
|
||||
tls_mainThreadAssertionsDisabledCount += 1;
|
||||
}
|
||||
|
||||
void ASPopMainThreadAssertionsDisabled() {
|
||||
tls_mainThreadAssertionsDisabledCount -= 1;
|
||||
ASDisplayNodeCAssert(tls_mainThreadAssertionsDisabledCount >= 0, @"Attempt to pop thread assertion-disabling without corresponding push.");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#import <dispatch/once.h>
|
||||
|
||||
static pthread_key_t ASMainThreadAssertionsDisabledKey() {
|
||||
static pthread_key_t k;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
pthread_key_create(&k, NULL);
|
||||
});
|
||||
return k;
|
||||
}
|
||||
|
||||
BOOL ASMainThreadAssertionsAreDisabled() {
|
||||
return (nullptr != pthread_getspecific(ASMainThreadAssertionsDisabledKey()));
|
||||
}
|
||||
|
||||
void ASPushMainThreadAssertionsDisabled() {
|
||||
const auto key = ASMainThreadAssertionsDisabledKey();
|
||||
const auto oldVal = (intptr_t)pthread_getspecific(key);
|
||||
pthread_setspecific(key, (void *)(oldVal + 1));
|
||||
}
|
||||
|
||||
void ASPopMainThreadAssertionsDisabled() {
|
||||
const auto key = ASMainThreadAssertionsDisabledKey();
|
||||
const auto oldVal = (intptr_t)pthread_getspecific(key);
|
||||
pthread_setspecific(key, (void *)(oldVal - 1));
|
||||
ASDisplayNodeCAssert(oldVal > 0, @"Attempt to pop thread assertion-disabling without corresponding push.");
|
||||
}
|
||||
|
||||
#endif // AS_TLS_AVAILABLE
|
28
submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h
Normal file
28
submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// ASCGImageBuffer.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <CoreGraphics/CGDataProvider.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASCGImageBuffer : NSObject
|
||||
|
||||
/// Init a zero-filled buffer with the given length.
|
||||
- (instancetype)initWithLength:(NSUInteger)length;
|
||||
|
||||
@property (readonly) void *mutableBytes NS_RETURNS_INNER_POINTER;
|
||||
|
||||
/// Don't do any drawing or call any methods after calling this.
|
||||
- (CGDataProviderRef)createDataProviderAndInvalidate CF_RETURNS_RETAINED;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
88
submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm
Normal file
88
submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm
Normal file
@ -0,0 +1,88 @@
|
||||
//
|
||||
// ASCGImageBuffer.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASCGImageBuffer.h"
|
||||
|
||||
#import <sys/mman.h>
|
||||
#import <mach/mach_init.h>
|
||||
#import <mach/vm_map.h>
|
||||
#import <mach/vm_statistics.h>
|
||||
|
||||
/**
|
||||
* The behavior of this class is modeled on the private function
|
||||
* _CGDataProviderCreateWithCopyOfData, which is the function used
|
||||
* by CGBitmapContextCreateImage.
|
||||
*
|
||||
* If the buffer is larger than a page, we use mmap and mark it as
|
||||
* read-only when they are finished drawing. Then we wrap the VM
|
||||
* in an NSData
|
||||
*/
|
||||
@implementation ASCGImageBuffer {
|
||||
BOOL _createdData;
|
||||
BOOL _isVM;
|
||||
NSUInteger _length;
|
||||
}
|
||||
|
||||
- (instancetype)initWithLength:(NSUInteger)length
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_length = length;
|
||||
_isVM = (length >= vm_page_size);
|
||||
if (_isVM) {
|
||||
_mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0);
|
||||
if (_mutableBytes == MAP_FAILED) {
|
||||
NSAssert(NO, @"Failed to map for CG image data.");
|
||||
_isVM = NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the VM flag again because we may have failed above.
|
||||
if (!_isVM) {
|
||||
_mutableBytes = calloc(1, length);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (!_createdData) {
|
||||
[ASCGImageBuffer deallocateBuffer:_mutableBytes length:_length isVM:_isVM];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGDataProviderRef)createDataProviderAndInvalidate
|
||||
{
|
||||
NSAssert(!_createdData, @"Should not create data provider from buffer multiple times.");
|
||||
_createdData = YES;
|
||||
|
||||
// Mark the pages as read-only.
|
||||
if (_isVM) {
|
||||
__unused kern_return_t result = vm_protect(mach_task_self(), (vm_address_t)_mutableBytes, _length, true, VM_PROT_READ);
|
||||
NSAssert(result == noErr, @"Error marking buffer as read-only: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]);
|
||||
}
|
||||
|
||||
// Wrap in an NSData
|
||||
BOOL isVM = _isVM;
|
||||
NSData *d = [[NSData alloc] initWithBytesNoCopy:_mutableBytes length:_length deallocator:^(void * _Nonnull bytes, NSUInteger length) {
|
||||
[ASCGImageBuffer deallocateBuffer:bytes length:length isVM:isVM];
|
||||
}];
|
||||
return CGDataProviderCreateWithCFData((__bridge CFDataRef)d);
|
||||
}
|
||||
|
||||
+ (void)deallocateBuffer:(void *)buf length:(NSUInteger)length isVM:(BOOL)isVM
|
||||
{
|
||||
if (isVM) {
|
||||
__unused kern_return_t result = vm_deallocate(mach_task_self(), (vm_address_t)buf, length);
|
||||
NSAssert(result == noErr, @"Failed to unmap cg image buffer: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]);
|
||||
} else {
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
61
submodules/AsyncDisplayKit/Source/ASCollections.mm
Normal file
61
submodules/AsyncDisplayKit/Source/ASCollections.mm
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// ASCollections.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
|
||||
/**
|
||||
* A private allocator that signals to our retain callback to skip the retain.
|
||||
* It behaves the same as the default allocator, but acts as a signal that we
|
||||
* are creating a transfer array so we should skip the retain.
|
||||
*/
|
||||
static CFAllocatorRef gTransferAllocator;
|
||||
|
||||
static const void *ASTransferRetain(CFAllocatorRef allocator, const void *val) {
|
||||
if (allocator == gTransferAllocator) {
|
||||
// Transfer allocator. Ignore retain and pass through.
|
||||
return val;
|
||||
} else {
|
||||
// Other allocator. Retain like normal.
|
||||
// This happens when they make a mutable copy.
|
||||
return (&kCFTypeArrayCallBacks)->retain(allocator, val);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation NSArray (ASCollections)
|
||||
|
||||
+ (NSArray *)arrayByTransferring:(__strong id *)pointers count:(NSUInteger)count NS_RETURNS_RETAINED
|
||||
{
|
||||
// Custom callbacks that point to our ASTransferRetain callback.
|
||||
static CFArrayCallBacks callbacks;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
callbacks = kCFTypeArrayCallBacks;
|
||||
callbacks.retain = ASTransferRetain;
|
||||
CFAllocatorContext ctx;
|
||||
CFAllocatorGetContext(NULL, &ctx);
|
||||
gTransferAllocator = CFAllocatorCreate(NULL, &ctx);
|
||||
});
|
||||
|
||||
// NSZeroArray fast path.
|
||||
if (count == 0) {
|
||||
return @[]; // Does not actually call +array when optimized.
|
||||
}
|
||||
|
||||
// NSSingleObjectArray fast path. Retain/release here is worth it.
|
||||
if (count == 1) {
|
||||
NSArray *result = [[NSArray alloc] initWithObjects:pointers count:1];
|
||||
pointers[0] = nil;
|
||||
return result;
|
||||
}
|
||||
|
||||
NSArray *result = (__bridge_transfer NSArray *)CFArrayCreate(gTransferAllocator, (const void **)(void *)pointers, count, &callbacks);
|
||||
memset(pointers, 0, count * sizeof(id));
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
64
submodules/AsyncDisplayKit/Source/ASConfiguration.mm
Normal file
64
submodules/AsyncDisplayKit/Source/ASConfiguration.mm
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// ASConfiguration.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
|
||||
/// Not too performance-sensitive here.
|
||||
|
||||
@implementation ASConfiguration
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
|
||||
{
|
||||
if (self = [super init]) {
|
||||
if (dictionary != nil) {
|
||||
const auto featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray);
|
||||
const auto version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue;
|
||||
if (version != ASConfigurationSchemaCurrentVersion) {
|
||||
NSLog(@"Texture warning: configuration schema is old version (%ld vs %ld)", (long)version, (long)ASConfigurationSchemaCurrentVersion);
|
||||
}
|
||||
self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings);
|
||||
} else {
|
||||
self.experimentalFeatures = kNilOptions;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
|
||||
config.experimentalFeatures = self.experimentalFeatures;
|
||||
config.delegate = self.delegate;
|
||||
return config;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }"
|
||||
|
||||
#ifdef AS_FIXED_CONFIG_JSON
|
||||
|
||||
@implementation ASConfiguration (UserProvided)
|
||||
|
||||
+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED
|
||||
{
|
||||
NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSError *error;
|
||||
NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
|
||||
if (!d) {
|
||||
NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error);
|
||||
return nil;
|
||||
} else {
|
||||
return [[ASConfiguration alloc] initWithDictionary:d];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif // AS_FIXED_CONFIG_JSON
|
111
submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm
Normal file
111
submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm
Normal file
@ -0,0 +1,111 @@
|
||||
//
|
||||
// ASConfigurationInternal.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASConfiguration.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationDelegate.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
static ASConfigurationManager *ASSharedConfigurationManager;
|
||||
static dispatch_once_t ASSharedConfigurationManagerOnceToken;
|
||||
|
||||
NS_INLINE ASConfigurationManager *ASConfigurationManagerGet() {
|
||||
dispatch_once(&ASSharedConfigurationManagerOnceToken, ^{
|
||||
ASSharedConfigurationManager = [[ASConfigurationManager alloc] init];
|
||||
});
|
||||
return ASSharedConfigurationManager;
|
||||
}
|
||||
|
||||
@implementation ASConfigurationManager {
|
||||
ASConfiguration *_config;
|
||||
dispatch_queue_t _delegateQueue;
|
||||
BOOL _frameworkInitialized;
|
||||
_Atomic(ASExperimentalFeatures) _activatedExperiments;
|
||||
}
|
||||
|
||||
+ (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED
|
||||
{
|
||||
ASConfiguration *config = [[ASConfiguration alloc] init];
|
||||
// TODO(wsdwsd0829): Fix #788 before enabling it.
|
||||
// config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing;
|
||||
return config;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_delegateQueue = dispatch_queue_create("org.TextureGroup.Texture.ConfigNotifyQueue", DISPATCH_QUEUE_SERIAL);
|
||||
if ([ASConfiguration respondsToSelector:@selector(textureConfiguration)]) {
|
||||
_config = [[ASConfiguration textureConfiguration] copy];
|
||||
} else {
|
||||
_config = [ASConfigurationManager defaultConfiguration];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)frameworkDidInitialize
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_frameworkInitialized) {
|
||||
ASDisplayNodeFailAssert(@"Framework initialized twice.");
|
||||
return;
|
||||
}
|
||||
_frameworkInitialized = YES;
|
||||
|
||||
const auto delegate = _config.delegate;
|
||||
if ([delegate respondsToSelector:@selector(textureDidInitialize)]) {
|
||||
[delegate textureDidInitialize];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested
|
||||
{
|
||||
if (_config == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSAssert(__builtin_popcountl(requested) == 1, @"Cannot activate multiple features at once with this method.");
|
||||
|
||||
// We need to call out, whether it's enabled or not.
|
||||
// A/B testing requires even "control" users to be activated.
|
||||
ASExperimentalFeatures enabled = requested & _config.experimentalFeatures;
|
||||
ASExperimentalFeatures prevTriggered = atomic_fetch_or(&_activatedExperiments, requested);
|
||||
ASExperimentalFeatures newlyTriggered = requested & ~prevTriggered;
|
||||
|
||||
// Notify delegate if needed.
|
||||
if (newlyTriggered != 0) {
|
||||
__unsafe_unretained id<ASConfigurationDelegate> del = _config.delegate;
|
||||
dispatch_async(_delegateQueue, ^{
|
||||
[del textureDidActivateExperimentalFeatures:newlyTriggered];
|
||||
});
|
||||
}
|
||||
|
||||
return (enabled != 0);
|
||||
}
|
||||
|
||||
// Define this even when !DEBUG, since we may run our tests in release mode.
|
||||
+ (void)test_resetWithConfiguration:(ASConfiguration *)configuration
|
||||
{
|
||||
ASConfigurationManager *inst = ASConfigurationManagerGet();
|
||||
inst->_config = configuration ?: [self defaultConfiguration];
|
||||
atomic_store(&inst->_activatedExperiments, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures feature)
|
||||
{
|
||||
return [ASConfigurationManagerGet() activateExperimentalFeature:feature];
|
||||
}
|
||||
|
||||
void ASNotifyInitialized()
|
||||
{
|
||||
[ASConfigurationManagerGet() frameworkDidInitialize];
|
||||
}
|
18
submodules/AsyncDisplayKit/Source/ASControlNode+Private.h
Normal file
18
submodules/AsyncDisplayKit/Source/ASControlNode+Private.h
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// ASControlNode+Private.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
@interface ASControlNode (Private)
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)_pressDown;
|
||||
#endif
|
||||
|
||||
@end
|
499
submodules/AsyncDisplayKit/Source/ASControlNode.mm
Normal file
499
submodules/AsyncDisplayKit/Source/ASControlNode.mm
Normal file
@ -0,0 +1,499 @@
|
||||
//
|
||||
// ASControlNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASControlNode.h>
|
||||
#import "ASControlNode+Private.h"
|
||||
#import <AsyncDisplayKit/ASControlNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASControlTargetAction.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
// UIControl allows dragging some distance outside of the control itself during
|
||||
// tracking. This value depends on the device idiom (25 or 70 points), so
|
||||
// so replicate that effect with the same values here for our own controls.
|
||||
#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f)
|
||||
|
||||
// Initial capacities for dispatch tables.
|
||||
#define kASControlNodeEventDispatchTableInitialCapacity 4
|
||||
#define kASControlNodeActionDispatchTableInitialCapacity 4
|
||||
|
||||
@interface ASControlNode ()
|
||||
{
|
||||
@private
|
||||
// Control Attributes
|
||||
BOOL _enabled;
|
||||
BOOL _highlighted;
|
||||
|
||||
// Tracking
|
||||
BOOL _tracking;
|
||||
BOOL _touchInside;
|
||||
|
||||
// Target action pairs stored in an array for each event type
|
||||
// ASControlEvent -> [ASTargetAction0, ASTargetAction1]
|
||||
NSMutableDictionary<id<NSCopying>, NSMutableArray<ASControlTargetAction *> *> *_controlEventDispatchTable;
|
||||
}
|
||||
|
||||
// Read-write overrides.
|
||||
@property (getter=isTracking) BOOL tracking;
|
||||
@property (getter=isTouchInside) BOOL touchInside;
|
||||
|
||||
/**
|
||||
@abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event.
|
||||
@param controlEvent A control event.
|
||||
@result A key for use in _controlEventDispatchTable.
|
||||
*/
|
||||
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent);
|
||||
|
||||
/**
|
||||
@abstract Enumerates the ASControlNode events included mask, invoking the block for each event.
|
||||
@param mask An ASControlNodeEvent mask.
|
||||
@param block The block to be invoked for each ASControlNodeEvent included in mask.
|
||||
*/
|
||||
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent));
|
||||
|
||||
/**
|
||||
@abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking.
|
||||
@param controlNode A control node.
|
||||
@result The expanded bounds of the node.
|
||||
*/
|
||||
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode);
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASControlNode
|
||||
{
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_enabled = YES;
|
||||
|
||||
// As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on.
|
||||
self.userInteractionEnabled = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
// On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them.
|
||||
// Here we add our own internal tap gesture to handle this behaviour.
|
||||
self.userInteractionEnabled = YES;
|
||||
UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)];
|
||||
tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)];
|
||||
[self.view addGestureRecognizer:tapGestureRec];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
|
||||
{
|
||||
[super setUserInteractionEnabled:userInteractionEnabled];
|
||||
self.isAccessibilityElement = userInteractionEnabled;
|
||||
}
|
||||
|
||||
- (void)__exitHierarchy
|
||||
{
|
||||
[super __exitHierarchy];
|
||||
|
||||
// If a control node is exit the hierarchy and is tracking we have to cancel it
|
||||
if (self.tracking) {
|
||||
[self _cancelTrackingWithEvent:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
||||
|
||||
#pragma mark - ASDisplayNode Overrides
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the tracking should start
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
if (![self beginTrackingWithTouch:theTouch withEvent:event]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get more than one touch down on us, cancel.
|
||||
// Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation.
|
||||
if (touches.count > 1 || self.tracking) {
|
||||
[self _cancelTrackingWithEvent:event];
|
||||
} else {
|
||||
// Otherwise, begin tracking.
|
||||
self.tracking = YES;
|
||||
|
||||
// No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds.
|
||||
self.touchInside = YES;
|
||||
self.highlighted = YES;
|
||||
|
||||
// Send the appropriate touch-down control event depending on how many times we've been tapped.
|
||||
ASControlNodeEvent controlEventMask = (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat;
|
||||
[self sendActionsForControlEvents:controlEventMask withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSParameterAssert(touches.count == 1);
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
|
||||
// Check if tracking should continue
|
||||
if (!self.tracking || ![self continueTrackingWithTouch:theTouch withEvent:event]) {
|
||||
self.tracking = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
CGPoint touchLocation = [theTouch locationInView:self.view];
|
||||
|
||||
// Update our touchInside state.
|
||||
BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil];
|
||||
|
||||
// Update our highlighted state.
|
||||
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
|
||||
BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
|
||||
self.touchInside = dragIsInsideExpandedBounds;
|
||||
self.highlighted = dragIsInsideExpandedBounds;
|
||||
|
||||
[self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside)
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that we've cancelled tracking.
|
||||
[self _cancelTrackingWithEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent:
|
||||
// twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to
|
||||
// trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking
|
||||
// state in order to have a correct behavior.
|
||||
// It might be related to that issue: http://www.openradar.me/22910171
|
||||
if (!self.tracking) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSParameterAssert([touches count] == 1);
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
CGPoint touchLocation = [theTouch locationInView:self.view];
|
||||
|
||||
// Update state.
|
||||
self.tracking = NO;
|
||||
self.touchInside = NO;
|
||||
self.highlighted = NO;
|
||||
|
||||
// Note that we've ended tracking.
|
||||
[self endTrackingWithTouch:theTouch withEvent:event];
|
||||
|
||||
// Send the appropriate touch-up control event.
|
||||
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
|
||||
BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
|
||||
|
||||
[self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside)
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)_cancelTrackingWithEvent:(UIEvent *)event
|
||||
{
|
||||
// We're no longer tracking and there is no touch to be inside.
|
||||
self.tracking = NO;
|
||||
self.touchInside = NO;
|
||||
self.highlighted = NO;
|
||||
|
||||
// Send the cancel event.
|
||||
[self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// If not enabled we should not care about receving touches
|
||||
if (! self.enabled) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
// If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir.
|
||||
if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) {
|
||||
UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer;
|
||||
// Allow double-tap gestures
|
||||
return tapRecognizer.numberOfTapsRequired != 1;
|
||||
}
|
||||
|
||||
// Otherwise, go ahead. :]
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return super.supportsLayerBacking && !self.userInteractionEnabled;
|
||||
}
|
||||
|
||||
#pragma mark - Action Messages
|
||||
|
||||
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
|
||||
{
|
||||
NSParameterAssert(action);
|
||||
NSParameterAssert(controlEventMask != 0);
|
||||
|
||||
// ASControlNode cannot be layer backed if adding a target
|
||||
ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair.");
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
if (!_controlEventDispatchTable) {
|
||||
_controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
|
||||
}
|
||||
|
||||
// Create new target action pair
|
||||
ASControlTargetAction *targetAction = [[ASControlTargetAction alloc] init];
|
||||
targetAction.action = action;
|
||||
targetAction.target = target;
|
||||
|
||||
// Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Do we already have an event table for this control event?
|
||||
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
|
||||
|
||||
if (!eventTargetActionArray) {
|
||||
eventTargetActionArray = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
// Remove any prior target-action pair for this event, as UIKit does.
|
||||
[eventTargetActionArray removeObject:targetAction];
|
||||
|
||||
// Register the new target-action as the last one to be sent.
|
||||
[eventTargetActionArray addObject:targetAction];
|
||||
|
||||
if (eventKey) {
|
||||
[_controlEventDispatchTable setObject:eventTargetActionArray forKey:eventKey];
|
||||
}
|
||||
});
|
||||
|
||||
self.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent
|
||||
{
|
||||
NSParameterAssert(target);
|
||||
NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents);
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Grab the event target action array for this event.
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)];
|
||||
if (!eventTargetActionArray) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray *actions = [[NSMutableArray alloc] init];
|
||||
|
||||
// Collect all actions for this target.
|
||||
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
|
||||
if ((target == nil && targetAction.createdWithNoTarget) || (target != nil && target == targetAction.target)) {
|
||||
[actions addObject:NSStringFromSelector(targetAction.action)];
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
- (NSSet *)allTargets
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
NSMutableSet *targets = [[NSMutableSet alloc] init];
|
||||
|
||||
// Look at each event...
|
||||
for (NSMutableArray *eventTargetActionArray in [_controlEventDispatchTable objectEnumerator]) {
|
||||
// and each event's targets...
|
||||
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
|
||||
[targets addObject:targetAction.target];
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
|
||||
{
|
||||
NSParameterAssert(controlEventMask != 0);
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask.
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Grab the dispatch table for this event (if we have it).
|
||||
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
|
||||
if (!eventTargetActionArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(ASControlTargetAction *_Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
|
||||
if (!target || evaluatedObject.target == target) {
|
||||
if (!action) {
|
||||
return NO;
|
||||
} else if (evaluatedObject.action == action) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
[eventTargetActionArray filterUsingPredicate:filterPredicate];
|
||||
|
||||
if (eventTargetActionArray.count == 0) {
|
||||
// If there are no targets for this event anymore, remove it.
|
||||
[_controlEventDispatchTable removeObjectForKey:eventKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread(); //We access self.view below, it's not safe to call this off of main.
|
||||
NSParameterAssert(controlEvents != 0);
|
||||
|
||||
NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray<ASControlTargetAction *> alloc] init];
|
||||
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Enumerate the events in the mask, invoking the target-action pairs for each.
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Iterate on each target action pair
|
||||
for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) {
|
||||
ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init];
|
||||
resolvedTargetAction.action = targetAction.action;
|
||||
resolvedTargetAction.target = targetAction.target;
|
||||
|
||||
// NSNull means that a nil target was set, so start at self and travel the responder chain
|
||||
if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) {
|
||||
// if the target cannot perform the action, travel the responder chain to try to find something that does
|
||||
resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self];
|
||||
}
|
||||
|
||||
if (resolvedTargetAction.target) {
|
||||
[resolvedEventTargetActionArray addObject:resolvedTargetAction];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
for (ASControlTargetAction *targetAction in resolvedEventTargetActionArray) {
|
||||
[targetAction.target performSelector:targetAction.action withObject:self withObject:event];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
#pragma mark - Convenience
|
||||
|
||||
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
return @(controlEvent);
|
||||
}
|
||||
|
||||
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent))
|
||||
{
|
||||
if (block == nil) {
|
||||
return;
|
||||
}
|
||||
// Start with our first event (touch down) and work our way up to the last event (PrimaryActionTriggered)
|
||||
for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventPrimaryActionTriggered; thisEvent <<= 1) {
|
||||
// If it's included in the mask, invoke the block.
|
||||
if ((mask & thisEvent) == thisEvent)
|
||||
block(thisEvent);
|
||||
}
|
||||
}
|
||||
|
||||
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) {
|
||||
return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset);
|
||||
}
|
||||
|
||||
#pragma mark - For Subclasses
|
||||
|
||||
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
// Subclass hook
|
||||
}
|
||||
|
||||
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
// Subclass hook
|
||||
}
|
||||
|
||||
#pragma mark - Debug
|
||||
- (ASDisplayNode *)debugHighlightOverlay
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
@end
|
65
submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm
Normal file
65
submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// ASControlTargetAction.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASControlTargetAction.h>
|
||||
|
||||
@implementation ASControlTargetAction
|
||||
{
|
||||
__weak id _target;
|
||||
BOOL _createdWithNoTarget;
|
||||
}
|
||||
|
||||
- (void)setTarget:(id)target {
|
||||
_target = target;
|
||||
|
||||
if (!target) {
|
||||
_createdWithNoTarget = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)target {
|
||||
return _target;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (![object isKindOfClass:[ASControlTargetAction class]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
ASControlTargetAction *otherObject = (ASControlTargetAction *)object;
|
||||
|
||||
BOOL areTargetsEqual;
|
||||
|
||||
if (self.target != nil && otherObject.target != nil && self.target == otherObject.target) {
|
||||
areTargetsEqual = YES;
|
||||
}
|
||||
else if (self.target == nil && otherObject.target == nil && self.createdWithNoTarget && otherObject.createdWithNoTarget) {
|
||||
areTargetsEqual = YES;
|
||||
}
|
||||
else {
|
||||
areTargetsEqual = NO;
|
||||
}
|
||||
|
||||
if (!areTargetsEqual) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (self.action && otherObject.action && self.action == otherObject.action) {
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [self.target hash];
|
||||
}
|
||||
|
||||
@end
|
125
submodules/AsyncDisplayKit/Source/ASDimension.mm
Normal file
125
submodules/AsyncDisplayKit/Source/ASDimension.mm
Normal file
@ -0,0 +1,125 @@
|
||||
//
|
||||
// ASDimension.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#pragma mark - ASDimension
|
||||
|
||||
ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0};
|
||||
|
||||
ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension)
|
||||
{
|
||||
if (dimension.length > 0) {
|
||||
|
||||
// Handle points
|
||||
if ([dimension hasSuffix:@"pt"]) {
|
||||
return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromString(dimension));
|
||||
}
|
||||
|
||||
// Handle auto
|
||||
if ([dimension isEqualToString:@"auto"]) {
|
||||
return ASDimensionAuto;
|
||||
}
|
||||
|
||||
// Handle percent
|
||||
if ([dimension hasSuffix:@"%"]) {
|
||||
return ASDimensionMake(ASDimensionUnitFraction, (ASCGFloatFromString(dimension) / 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
return ASDimensionAuto;
|
||||
}
|
||||
|
||||
NSString *NSStringFromASDimension(ASDimension dimension)
|
||||
{
|
||||
switch (dimension.unit) {
|
||||
case ASDimensionUnitPoints:
|
||||
return [NSString stringWithFormat:@"%.0fpt", dimension.value];
|
||||
case ASDimensionUnitFraction:
|
||||
return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0];
|
||||
case ASDimensionUnitAuto:
|
||||
return @"Auto";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ASLayoutSize
|
||||
|
||||
ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto};
|
||||
|
||||
#pragma mark - ASSizeRange
|
||||
|
||||
ASSizeRange const ASSizeRangeZero = {};
|
||||
|
||||
ASSizeRange const ASSizeRangeUnconstrained = { {0, 0}, { INFINITY, INFINITY }};
|
||||
|
||||
struct _Range {
|
||||
CGFloat min;
|
||||
CGFloat max;
|
||||
|
||||
/**
|
||||
Intersects another dimension range. If the other range does not overlap, this size range "wins" by returning a
|
||||
single point within its own range that is closest to the non-overlapping range.
|
||||
*/
|
||||
_Range intersect(const _Range &other) const
|
||||
{
|
||||
CGFloat newMin = MAX(min, other.min);
|
||||
CGFloat newMax = MIN(max, other.max);
|
||||
if (newMin <= newMax) {
|
||||
return {newMin, newMax};
|
||||
} else {
|
||||
// No intersection. If we're before the other range, return our max; otherwise our min.
|
||||
if (min < other.min) {
|
||||
return {max, max};
|
||||
} else {
|
||||
return {min, min};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRange)
|
||||
{
|
||||
const auto w = _Range({sizeRange.min.width, sizeRange.max.width}).intersect({otherSizeRange.min.width, otherSizeRange.max.width});
|
||||
const auto h = _Range({sizeRange.min.height, sizeRange.max.height}).intersect({otherSizeRange.min.height, otherSizeRange.max.height});
|
||||
return {{w.min, h.min}, {w.max, h.max}};
|
||||
}
|
||||
|
||||
NSString *NSStringFromASSizeRange(ASSizeRange sizeRange)
|
||||
{
|
||||
// 17 field length copied from iOS 10.3 impl of NSStringFromCGSize.
|
||||
if (CGSizeEqualToSize(sizeRange.min, sizeRange.max)) {
|
||||
return [NSString stringWithFormat:@"{{%.*g, %.*g}}",
|
||||
17, sizeRange.min.width,
|
||||
17, sizeRange.min.height];
|
||||
}
|
||||
return [NSString stringWithFormat:@"{{%.*g, %.*g}, {%.*g, %.*g}}",
|
||||
17, sizeRange.min.width,
|
||||
17, sizeRange.min.height,
|
||||
17, sizeRange.max.width,
|
||||
17, sizeRange.max.height];
|
||||
}
|
||||
|
||||
#if YOGA
|
||||
#pragma mark - Yoga - ASEdgeInsets
|
||||
ASEdgeInsets const ASEdgeInsetsZero = {};
|
||||
|
||||
ASEdgeInsets ASEdgeInsetsMake(UIEdgeInsets edgeInsets)
|
||||
{
|
||||
ASEdgeInsets asEdgeInsets = ASEdgeInsetsZero;
|
||||
asEdgeInsets.top = ASDimensionMake(edgeInsets.top);
|
||||
asEdgeInsets.left = ASDimensionMake(edgeInsets.left);
|
||||
asEdgeInsets.bottom = ASDimensionMake(edgeInsets.bottom);
|
||||
asEdgeInsets.right = ASDimensionMake(edgeInsets.right);
|
||||
return asEdgeInsets;
|
||||
}
|
||||
#endif
|
65
submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm
Normal file
65
submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// ASDimensionInternal.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDimensionInternal.h>
|
||||
|
||||
#pragma mark - ASLayoutElementSize
|
||||
|
||||
NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size)
|
||||
{
|
||||
return [NSString stringWithFormat:
|
||||
@"<ASLayoutElementSize: exact=%@, min=%@, max=%@>",
|
||||
NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)),
|
||||
NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)),
|
||||
NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))];
|
||||
}
|
||||
|
||||
ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax)
|
||||
{
|
||||
NSCAssert(!isnan(minVal), @"minVal must not be NaN");
|
||||
NSCAssert(!isnan(maxVal), @"maxVal must not be NaN");
|
||||
// Avoid use of min/max primitives since they're harder to reason
|
||||
// about in the presence of NaN (in exactVal)
|
||||
// Follow CSS: min overrides max overrides exact.
|
||||
|
||||
// Begin with the min/max range
|
||||
*outMin = minVal;
|
||||
*outMax = maxVal;
|
||||
if (maxVal <= minVal) {
|
||||
// min overrides max and exactVal is irrelevant
|
||||
*outMax = minVal;
|
||||
return;
|
||||
}
|
||||
if (isnan(exactVal)) {
|
||||
// no exact value, so leave as a min/max range
|
||||
return;
|
||||
}
|
||||
if (exactVal > maxVal) {
|
||||
// clip to max value
|
||||
*outMin = maxVal;
|
||||
} else if (exactVal < minVal) {
|
||||
// clip to min value
|
||||
*outMax = minVal;
|
||||
} else {
|
||||
// use exact value
|
||||
*outMin = *outMax = exactVal;
|
||||
}
|
||||
}
|
||||
|
||||
ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange)
|
||||
{
|
||||
CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN});
|
||||
CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min);
|
||||
CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max);
|
||||
|
||||
CGSize rangeMin, rangeMax;
|
||||
ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width);
|
||||
ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height);
|
||||
return {rangeMin, rangeMax};
|
||||
}
|
27
submodules/AsyncDisplayKit/Source/ASDispatch.h
Normal file
27
submodules/AsyncDisplayKit/Source/ASDispatch.h
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// ASDispatch.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
/**
|
||||
* Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs.
|
||||
*
|
||||
* Note: The actual number of threads may be lower than threadCount, if libdispatch
|
||||
* decides the system can't handle it. In reality this rarely happens.
|
||||
*/
|
||||
AS_EXTERN void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i));
|
||||
|
||||
/**
|
||||
* Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs.
|
||||
*
|
||||
* Note: The actual number of threads may be lower than threadCount, if libdispatch
|
||||
* decides the system can't handle it. In reality this rarely happens.
|
||||
*/
|
||||
AS_EXTERN void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i));
|
63
submodules/AsyncDisplayKit/Source/ASDispatch.mm
Normal file
63
submodules/AsyncDisplayKit/Source/ASDispatch.mm
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// ASDispatch.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASDispatch.h"
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
|
||||
|
||||
// Prefer C atomics in this file because ObjC blocks can't capture C++ atomics well.
|
||||
#import <stdatomic.h>
|
||||
|
||||
/**
|
||||
* Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs.
|
||||
*
|
||||
* Note: The actual number of threads may be lower than threadCount, if libdispatch
|
||||
* decides the system can't handle it. In reality this rarely happens.
|
||||
*/
|
||||
void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) {
|
||||
if (threadCount == 0) {
|
||||
if (ASActivateExperimentalFeature(ASExperimentalDispatchApply)) {
|
||||
dispatch_apply(iterationCount, queue, work);
|
||||
return;
|
||||
}
|
||||
threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2;
|
||||
}
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
__block atomic_size_t counter = ATOMIC_VAR_INIT(0);
|
||||
for (NSUInteger t = 0; t < threadCount; t++) {
|
||||
dispatch_group_async(group, queue, ^{
|
||||
size_t i;
|
||||
while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) {
|
||||
work(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
};
|
||||
|
||||
/**
|
||||
* Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs.
|
||||
*
|
||||
* Note: The actual number of threads may be lower than threadCount, if libdispatch
|
||||
* decides the system can't handle it. In reality this rarely happens.
|
||||
*/
|
||||
void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) {
|
||||
if (threadCount == 0) {
|
||||
threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2;
|
||||
}
|
||||
__block atomic_size_t counter = ATOMIC_VAR_INIT(0);
|
||||
for (NSUInteger t = 0; t < threadCount; t++) {
|
||||
dispatch_async(queue, ^{
|
||||
size_t i;
|
||||
while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) {
|
||||
work(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
90
submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm
Normal file
90
submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm
Normal file
@ -0,0 +1,90 @@
|
||||
//
|
||||
// ASDisplayNode+Ancestry.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASNodeAncestryEnumerator : NSEnumerator
|
||||
@end
|
||||
|
||||
@implementation ASNodeAncestryEnumerator {
|
||||
ASDisplayNode *_lastNode; // This needs to be strong because enumeration will not retain the current batch of objects
|
||||
BOOL _initialState;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_initialState = YES;
|
||||
_lastNode = node;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)nextObject
|
||||
{
|
||||
if (_initialState) {
|
||||
_initialState = NO;
|
||||
return _lastNode;
|
||||
}
|
||||
|
||||
ASDisplayNode *nextNode = _lastNode.supernode;
|
||||
if (nextNode == nil && ASDisplayNodeThreadIsMain()) {
|
||||
CALayer *layer = _lastNode.nodeLoaded ? _lastNode.layer.superlayer : nil;
|
||||
while (layer != nil) {
|
||||
nextNode = ASLayerToDisplayNode(layer);
|
||||
if (nextNode != nil) {
|
||||
break;
|
||||
}
|
||||
layer = layer.superlayer;
|
||||
}
|
||||
}
|
||||
_lastNode = nextNode;
|
||||
return nextNode;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (Ancestry)
|
||||
|
||||
- (id<NSFastEnumeration>)supernodes
|
||||
{
|
||||
NSEnumerator *result = [[ASNodeAncestryEnumerator alloc] initWithNode:self];
|
||||
[result nextObject]; // discard first object (self)
|
||||
return result;
|
||||
}
|
||||
|
||||
- (id<NSFastEnumeration>)supernodesIncludingSelf
|
||||
{
|
||||
return [[ASNodeAncestryEnumerator alloc] initWithNode:self];
|
||||
}
|
||||
|
||||
- (nullable __kindof ASDisplayNode *)supernodeOfClass:(Class)supernodeClass includingSelf:(BOOL)includeSelf
|
||||
{
|
||||
id<NSFastEnumeration> chain = includeSelf ? self.supernodesIncludingSelf : self.supernodes;
|
||||
for (ASDisplayNode *ancestor in chain) {
|
||||
if ([ancestor isKindOfClass:supernodeClass]) {
|
||||
return ancestor;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)ancestryDescription
|
||||
{
|
||||
NSMutableArray *strings = [NSMutableArray array];
|
||||
for (ASDisplayNode *node in self.supernodes) {
|
||||
[strings addObject:ASObjectDescriptionMakeTiny(node)];
|
||||
}
|
||||
return strings.description;
|
||||
}
|
||||
|
||||
@end
|
493
submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm
Normal file
493
submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm
Normal file
@ -0,0 +1,493 @@
|
||||
//
|
||||
// ASDisplayNode+AsyncDisplay.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
|
||||
#import <AsyncDisplayKit/_ASAsyncTransaction.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import "ASSignpost.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
using AS::MutexLocker;
|
||||
|
||||
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (AsyncDisplay)
|
||||
|
||||
#if ASDISPLAYNODE_DELAY_DISPLAY
|
||||
#define ASDN_DELAY_FOR_DISPLAY() usleep( (long)(0.1 * USEC_PER_SEC) )
|
||||
#else
|
||||
#define ASDN_DELAY_FOR_DISPLAY()
|
||||
#endif
|
||||
|
||||
#define CHECK_CANCELLED_AND_RETURN_NIL(expr) if (isCancelledBlock()) { \
|
||||
expr; \
|
||||
return nil; \
|
||||
} \
|
||||
|
||||
- (NSObject *)drawParameters
|
||||
{
|
||||
__instanceLock__.lock();
|
||||
BOOL implementsDrawParameters = _flags.implementsDrawParameters;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (implementsDrawParameters) {
|
||||
return [self drawParametersForAsyncLayer:self.asyncLayer];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock displayBlocks:(NSMutableArray *)displayBlocks
|
||||
{
|
||||
// Skip subtrees that are hidden or zero alpha.
|
||||
if (self.isHidden || self.alpha <= 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
__instanceLock__.lock();
|
||||
BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized);
|
||||
__instanceLock__.unlock();
|
||||
|
||||
// if super node is rasterizing descendants, subnodes will not have had layout calls because they don't have layers
|
||||
if (rasterizingFromAscendent) {
|
||||
[self __layout];
|
||||
}
|
||||
|
||||
// Capture these outside the display block so they are retained.
|
||||
UIColor *backgroundColor = self.backgroundColor;
|
||||
CGRect bounds = self.bounds;
|
||||
CGFloat cornerRadius = self.cornerRadius;
|
||||
BOOL clipsToBounds = self.clipsToBounds;
|
||||
|
||||
CGRect frame;
|
||||
|
||||
// If this is the root container node, use a frame with a zero origin to draw into. If not, calculate the correct frame using the node's position, transform and anchorPoint.
|
||||
if (self.rasterizesSubtree) {
|
||||
frame = CGRectMake(0.0f, 0.0f, bounds.size.width, bounds.size.height);
|
||||
} else {
|
||||
CGPoint position = self.position;
|
||||
CGPoint anchorPoint = self.anchorPoint;
|
||||
|
||||
// Pretty hacky since full 3D transforms aren't actually supported, but attempt to compute the transformed frame of this node so that we can composite it into approximately the right spot.
|
||||
CGAffineTransform transform = CATransform3DGetAffineTransform(self.transform);
|
||||
CGSize scaledBoundsSize = CGSizeApplyAffineTransform(bounds.size, transform);
|
||||
CGPoint origin = CGPointMake(position.x - scaledBoundsSize.width * anchorPoint.x,
|
||||
position.y - scaledBoundsSize.height * anchorPoint.y);
|
||||
frame = CGRectMake(origin.x, origin.y, bounds.size.width, bounds.size.height);
|
||||
}
|
||||
|
||||
// Get the display block for this node.
|
||||
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:NO isCancelledBlock:isCancelledBlock rasterizing:YES];
|
||||
|
||||
// We'll display something if there is a display block, clipping, translation and/or a background color.
|
||||
BOOL shouldDisplay = displayBlock || backgroundColor || CGPointEqualToPoint(CGPointZero, frame.origin) == NO || clipsToBounds;
|
||||
|
||||
// If we should display, then push a transform, draw the background color, and draw the contents.
|
||||
// The transform is popped in a block added after the recursion into subnodes.
|
||||
if (shouldDisplay) {
|
||||
dispatch_block_t pushAndDisplayBlock = ^{
|
||||
// Push transform relative to parent.
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGContextTranslateCTM(context, frame.origin.x, frame.origin.y);
|
||||
|
||||
//support cornerRadius
|
||||
if (rasterizingFromAscendent && clipsToBounds) {
|
||||
if (cornerRadius) {
|
||||
[[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip];
|
||||
} else {
|
||||
CGContextClipToRect(context, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill background if any.
|
||||
CGColorRef backgroundCGColor = backgroundColor.CGColor;
|
||||
if (backgroundColor && CGColorGetAlpha(backgroundCGColor) > 0.0) {
|
||||
CGContextSetFillColorWithColor(context, backgroundCGColor);
|
||||
CGContextFillRect(context, bounds);
|
||||
}
|
||||
|
||||
// If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store).
|
||||
if (displayBlock) {
|
||||
UIImage *image = (UIImage *)displayBlock();
|
||||
if (image) {
|
||||
BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage));
|
||||
CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal;
|
||||
[image drawInRect:bounds blendMode:blendMode alpha:1];
|
||||
}
|
||||
}
|
||||
};
|
||||
[displayBlocks addObject:pushAndDisplayBlock];
|
||||
}
|
||||
|
||||
// Recursively capture displayBlocks for all descendants.
|
||||
for (ASDisplayNode *subnode in self.subnodes) {
|
||||
[subnode _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
|
||||
}
|
||||
|
||||
// If we pushed a transform, pop it by adding a display block that does nothing other than that.
|
||||
if (shouldDisplay) {
|
||||
// Since this block is pure, we can store it statically.
|
||||
static dispatch_block_t popBlock;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
popBlock = ^{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextRestoreGState(context);
|
||||
};
|
||||
});
|
||||
[displayBlocks addObject:popBlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchronous:(BOOL)asynchronous
|
||||
isCancelledBlock:(asdisplaynode_iscancelled_block_t)isCancelledBlock
|
||||
rasterizing:(BOOL)rasterizing
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil;
|
||||
ASDisplayNodeFlags flags;
|
||||
|
||||
__instanceLock__.lock();
|
||||
|
||||
flags = _flags;
|
||||
|
||||
// We always create a graphics context, unless a -display method is used, OR if we are a subnode drawing into a rasterized parent.
|
||||
BOOL shouldCreateGraphicsContext = (flags.implementsImageDisplay == NO && rasterizing == NO);
|
||||
BOOL shouldBeginRasterizing = (rasterizing == NO && flags.rasterizesSubtree);
|
||||
BOOL usesImageDisplay = flags.implementsImageDisplay;
|
||||
BOOL usesDrawRect = flags.implementsDrawRect;
|
||||
|
||||
if (usesImageDisplay == NO && usesDrawRect == NO && shouldBeginRasterizing == NO) {
|
||||
// Early exit before requesting more expensive properties like bounds and opaque from the layer.
|
||||
__instanceLock__.unlock();
|
||||
return nil;
|
||||
}
|
||||
|
||||
BOOL opaque = self.opaque;
|
||||
CGRect bounds = self.bounds;
|
||||
UIColor *backgroundColor = self.backgroundColor;
|
||||
CGColorRef borderColor = self.borderColor;
|
||||
CGFloat borderWidth = self.borderWidth;
|
||||
CGFloat contentsScaleForDisplay = _contentsScaleForDisplay;
|
||||
|
||||
__instanceLock__.unlock();
|
||||
|
||||
// Capture drawParameters from delegate on main thread, if this node is displaying itself rather than recursively rasterizing.
|
||||
id drawParameters = (shouldBeginRasterizing == NO ? [self drawParameters] : nil);
|
||||
|
||||
// Only the -display methods should be called if we can't size the graphics buffer to use.
|
||||
if (CGRectIsEmpty(bounds) && (shouldBeginRasterizing || shouldCreateGraphicsContext)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(contentsScaleForDisplay != 0.0, @"Invalid contents scale");
|
||||
ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized),
|
||||
@"Rasterized descendants should never display unless being drawn into the rasterized container.");
|
||||
|
||||
if (shouldBeginRasterizing) {
|
||||
// Collect displayBlocks for all descendants.
|
||||
NSMutableArray *displayBlocks = [[NSMutableArray alloc] init];
|
||||
[self _recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:isCancelledBlock displayBlocks:displayBlocks];
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
// If [UIColor clearColor] or another semitransparent background color is used, include alpha channel when rasterizing.
|
||||
// Unlike CALayer drawing, we include the backgroundColor as a base during rasterization.
|
||||
opaque = opaque && CGColorGetAlpha(backgroundColor.CGColor) == 1.0f;
|
||||
|
||||
displayBlock = ^id{
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
|
||||
for (dispatch_block_t block in displayBlocks) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext());
|
||||
block();
|
||||
}
|
||||
|
||||
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
return image;
|
||||
};
|
||||
} else {
|
||||
displayBlock = ^id{
|
||||
CHECK_CANCELLED_AND_RETURN_NIL();
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay);
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
}
|
||||
|
||||
CGContextRef currentContext = UIGraphicsGetCurrentContext();
|
||||
UIImage *image = nil;
|
||||
|
||||
if (shouldCreateGraphicsContext && !currentContext) {
|
||||
//ASDisplayNodeAssert(NO, @"Failed to create a CGContext (size: %@)", NSStringFromCGSize(bounds.size));
|
||||
return nil;
|
||||
}
|
||||
|
||||
// For -display methods, we don't have a context, and thus will not call the _willDisplayNodeContentWithRenderingContext or
|
||||
// _didDisplayNodeContentWithRenderingContext blocks. It's up to the implementation of -display... to do what it needs.
|
||||
[self __willDisplayNodeContentWithRenderingContext:currentContext drawParameters:drawParameters];
|
||||
|
||||
if (usesImageDisplay) { // If we are using a display method, we'll get an image back directly.
|
||||
image = [self.class displayWithParameters:drawParameters isCancelled:isCancelledBlock];
|
||||
} else if (usesDrawRect) { // If we're using a draw method, this will operate on the currentContext.
|
||||
[self.class drawRect:bounds withParameters:drawParameters isCancelled:isCancelledBlock isRasterizing:rasterizing];
|
||||
}
|
||||
|
||||
[self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor];
|
||||
|
||||
if (shouldCreateGraphicsContext) {
|
||||
CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); );
|
||||
image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
|
||||
ASDN_DELAY_FOR_DISPLAY();
|
||||
return image;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
If we're profiling, wrap the display block with signpost start and end.
|
||||
Color the interval red if cancelled, green otherwise.
|
||||
*/
|
||||
#if AS_KDEBUG_ENABLE
|
||||
__unsafe_unretained id ptrSelf = self;
|
||||
displayBlock = ^{
|
||||
ASSignpostStartCustom(ASSignpostLayerDisplay, ptrSelf, 0);
|
||||
id result = displayBlock();
|
||||
ASSignpostEndCustom(ASSignpostLayerDisplay, ptrSelf, 0, isCancelledBlock() ? ASSignpostColorRed : ASSignpostColorGreen);
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
|
||||
return displayBlock;
|
||||
}
|
||||
|
||||
- (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context drawParameters:(id _Nullable)drawParameters
|
||||
{
|
||||
if (context) {
|
||||
__instanceLock__.lock();
|
||||
ASCornerRoundingType cornerRoundingType = _cornerRoundingType;
|
||||
CGFloat cornerRadius = _cornerRadius;
|
||||
ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0) {
|
||||
ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self);
|
||||
// TODO: This clip path should be removed if we are rasterizing.
|
||||
CGRect boundingBox = CGContextGetClipBoundingBox(context);
|
||||
[[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip];
|
||||
}
|
||||
|
||||
if (willDisplayNodeContentWithRenderingContext) {
|
||||
willDisplayNodeContentWithRenderingContext(context, drawParameters);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
- (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image:(UIImage **)image drawParameters:(id _Nullable)drawParameters backgroundColor:(UIColor *)backgroundColor borderWidth:(CGFloat)borderWidth borderColor:(CGColorRef)borderColor
|
||||
{
|
||||
if (context == NULL && *image == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
__instanceLock__.lock();
|
||||
ASCornerRoundingType cornerRoundingType = _cornerRoundingType;
|
||||
CGFloat cornerRadius = _cornerRadius;
|
||||
CGFloat contentsScale = _contentsScaleForDisplay;
|
||||
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
if (context != NULL) {
|
||||
if (didDisplayNodeContentWithRenderingContext) {
|
||||
didDisplayNodeContentWithRenderingContext(context, drawParameters);
|
||||
}
|
||||
}
|
||||
|
||||
if (cornerRoundingType == ASCornerRoundingTypePrecomposited && cornerRadius > 0.0f) {
|
||||
CGRect bounds = CGRectZero;
|
||||
if (context == NULL) {
|
||||
bounds = self.threadSafeBounds;
|
||||
bounds.size.width *= contentsScale;
|
||||
bounds.size.height *= contentsScale;
|
||||
CGFloat white = 0.0f, alpha = 0.0f;
|
||||
[backgroundColor getWhite:&white alpha:&alpha];
|
||||
ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale);
|
||||
[*image drawInRect:bounds];
|
||||
} else {
|
||||
bounds = CGContextGetClipBoundingBox(context);
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self);
|
||||
|
||||
UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds];
|
||||
[roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]];
|
||||
roundedHole.usesEvenOddFillRule = YES;
|
||||
|
||||
UIBezierPath *roundedPath = nil;
|
||||
if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0
|
||||
CGFloat strokeThickness = borderWidth * contentsScale;
|
||||
CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f;
|
||||
roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset)
|
||||
cornerRadius:_cornerRadius * contentsScale];
|
||||
roundedPath.lineWidth = strokeThickness;
|
||||
[[UIColor colorWithCGColor:borderColor] setStroke];
|
||||
}
|
||||
|
||||
// Punch out the corners by copying the backgroundColor over them.
|
||||
// This works for everything from clearColor to opaque colors.
|
||||
[backgroundColor setFill];
|
||||
[roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f];
|
||||
|
||||
[roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil.
|
||||
|
||||
if (*image) {
|
||||
*image = ASGraphicsGetImageAndEndCurrentContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
__instanceLock__.lock();
|
||||
|
||||
if (_hierarchyState & ASHierarchyStateRasterized) {
|
||||
__instanceLock__.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
CALayer *layer = _layer;
|
||||
BOOL rasterizesSubtree = _flags.rasterizesSubtree;
|
||||
|
||||
__instanceLock__.unlock();
|
||||
|
||||
// for async display, capture the current displaySentinel value to bail early when the job is executed if another is
|
||||
// enqueued
|
||||
// for sync display, do not support cancellation
|
||||
|
||||
// FIXME: what about the degenerate case where we are calling setNeedsDisplay faster than the jobs are dequeuing
|
||||
// from the displayQueue? Need to not cancel early fails from displaySentinel changes.
|
||||
asdisplaynode_iscancelled_block_t isCancelledBlock = nil;
|
||||
if (asynchronously) {
|
||||
uint displaySentinelValue = ++_displaySentinel;
|
||||
__weak ASDisplayNode *weakSelf = self;
|
||||
isCancelledBlock = ^BOOL{
|
||||
__strong ASDisplayNode *self = weakSelf;
|
||||
return self == nil || (displaySentinelValue != self->_displaySentinel.load());
|
||||
};
|
||||
} else {
|
||||
isCancelledBlock = ^BOOL{
|
||||
return NO;
|
||||
};
|
||||
}
|
||||
|
||||
// Set up displayBlock to call either display or draw on the delegate and return a UIImage contents
|
||||
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];
|
||||
|
||||
if (!displayBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(layer, @"Expect _layer to be not nil");
|
||||
|
||||
// This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously
|
||||
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
|
||||
ASDisplayNodeCAssertMainThread();
|
||||
if (!canceled && !isCancelledBlock()) {
|
||||
UIImage *image = (UIImage *)value;
|
||||
BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero));
|
||||
if (stretchable) {
|
||||
ASDisplayNodeSetResizableContents(layer, image);
|
||||
} else {
|
||||
layer.contentsScale = self.contentsScale;
|
||||
layer.contents = (id)image.CGImage;
|
||||
}
|
||||
[self didDisplayAsyncLayer:self.asyncLayer];
|
||||
|
||||
if (rasterizesSubtree) {
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node didDisplayAsyncLayer:node.asyncLayer];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Call willDisplay immediately in either case
|
||||
[self willDisplayAsyncLayer:self.asyncLayer asynchronously:asynchronously];
|
||||
|
||||
if (rasterizesSubtree) {
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node willDisplayAsyncLayer:node.asyncLayer asynchronously:asynchronously];
|
||||
});
|
||||
}
|
||||
|
||||
if (asynchronously) {
|
||||
// Async rendering operations are contained by a transaction, which allows them to proceed and concurrently
|
||||
// while synchronizing the final application of the results to the layer's contents property (completionBlock).
|
||||
|
||||
// First, look to see if we are expected to join a parent's transaction container.
|
||||
CALayer *containerLayer = layer.asyncdisplaykit_parentTransactionContainer ? : layer;
|
||||
|
||||
// In the case that a transaction does not yet exist (such as for an individual node outside of a container),
|
||||
// this call will allocate the transaction and add it to _ASAsyncTransactionGroup.
|
||||
// It will automatically commit the transaction at the end of the runloop.
|
||||
_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
|
||||
|
||||
// Adding this displayBlock operation to the transaction will start it IMMEDIATELY.
|
||||
// The only function of the transaction commit is to gate the calling of the completionBlock.
|
||||
[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
|
||||
} else {
|
||||
UIImage *contents = (UIImage *)displayBlock();
|
||||
completionBlock(contents, NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer
|
||||
{
|
||||
_displaySentinel.fetch_add(1);
|
||||
}
|
||||
|
||||
- (ASDisplayNodeContextModifier)willDisplayNodeContentWithRenderingContext
|
||||
{
|
||||
MutexLocker l(__instanceLock__);
|
||||
return _willDisplayNodeContentWithRenderingContext;
|
||||
}
|
||||
|
||||
- (ASDisplayNodeContextModifier)didDisplayNodeContentWithRenderingContext
|
||||
{
|
||||
MutexLocker l(__instanceLock__);
|
||||
return _didDisplayNodeContentWithRenderingContext;
|
||||
}
|
||||
|
||||
- (void)setWillDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier
|
||||
{
|
||||
MutexLocker l(__instanceLock__);
|
||||
_willDisplayNodeContentWithRenderingContext = contextModifier;
|
||||
}
|
||||
|
||||
- (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier;
|
||||
{
|
||||
MutexLocker l(__instanceLock__);
|
||||
_didDisplayNodeContentWithRenderingContext = contextModifier;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,40 @@
|
||||
//
|
||||
// ASDisplayNode+Convenience.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Convenience.h>
|
||||
|
||||
#import <UIKit/UIViewController.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import "ASResponderChainEnumerator.h"
|
||||
|
||||
@implementation ASDisplayNode (Convenience)
|
||||
|
||||
- (__kindof UIViewController *)closestViewController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// Careful not to trigger node loading here.
|
||||
if (!self.nodeLoaded) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get the closest view.
|
||||
UIView *view = ASFindClosestViewOfLayer(self.layer);
|
||||
// Travel up the responder chain to find a view controller.
|
||||
for (UIResponder *responder in [view asdk_responderChainEnumerator]) {
|
||||
UIViewController *vc = ASDynamicCast(responder, UIViewController);
|
||||
if (vc != nil) {
|
||||
return vc;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
142
submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h
Normal file
142
submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h
Normal file
@ -0,0 +1,142 @@
|
||||
//
|
||||
// ASDisplayNode+Deprecated.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
@interface ASDisplayNode (Deprecated)
|
||||
|
||||
/**
|
||||
* @abstract The name of this node, which will be displayed in `description`. The default value is nil.
|
||||
*
|
||||
* @deprecated Deprecated in version 2.0: Use .debugName instead. This value will display in
|
||||
* results of the -asciiArtString method (@see ASLayoutElementAsciiArtProtocol).
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSString *name ASDISPLAYNODE_DEPRECATED_MSG("Use .debugName instead.");
|
||||
|
||||
/**
|
||||
* @abstract Provides a default intrinsic content size for calculateSizeThatFits:. This is useful when laying out
|
||||
* a node that either has no intrinsic content size or should be laid out at a different size than its intrinsic content
|
||||
* size. For example, this property could be set on an ASImageNode to display at a size different from the underlying
|
||||
* image size.
|
||||
*
|
||||
* @return Try to create a CGSize for preferredFrameSize of this node from the width and height property of this node. It will return CGSizeZero if width and height dimensions are not of type ASDimensionUnitPoints.
|
||||
*
|
||||
* @deprecated Deprecated in version 2.0: Just calls through to set the height and width property of the node. Convert to use sizing properties instead: height, minHeight, maxHeight, width, minWidth, maxWidth.
|
||||
*/
|
||||
@property (nonatomic, assign, readwrite) CGSize preferredFrameSize ASDISPLAYNODE_DEPRECATED_MSG("Use .style.preferredSize instead OR set individual values with .style.height and .style.width.");
|
||||
|
||||
/**
|
||||
* @abstract Asks the node to measure and return the size that best fits its subnodes.
|
||||
*
|
||||
* @param constrainedSize The maximum size the receiver should fit in.
|
||||
*
|
||||
* @return A new size that fits the receiver's subviews.
|
||||
*
|
||||
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
|
||||
* constraint and the result.
|
||||
*
|
||||
* @warning Subclasses must not override this; it calls -measureWithSizeRange: with zero min size.
|
||||
* -measureWithSizeRange: caches results from -calculateLayoutThatFits:. Calling this method may
|
||||
* be expensive if result is not cached.
|
||||
*
|
||||
* @see measureWithSizeRange:
|
||||
* @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:]
|
||||
*
|
||||
* @deprecated Deprecated in version 2.0: Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout
|
||||
*/
|
||||
- (CGSize)measure:(CGSize)constrainedSize/* ASDISPLAYNODE_DEPRECATED_MSG("Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout.")*/;
|
||||
|
||||
ASLayoutElementStyleForwardingDeclaration
|
||||
|
||||
/**
|
||||
* @abstract Called whenever the visiblity of the node changed.
|
||||
*
|
||||
* @discussion Subclasses may use this to monitor when they become visible.
|
||||
*
|
||||
* @deprecated @see didEnterVisibleState @see didExitVisibleState
|
||||
*/
|
||||
- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead.");
|
||||
|
||||
/**
|
||||
* @abstract Called whenever the visiblity of the node changed.
|
||||
*
|
||||
* @discussion Subclasses may use this to monitor when they become visible.
|
||||
*
|
||||
* @deprecated @see didEnterVisibleState @see didExitVisibleState
|
||||
*/
|
||||
- (void)visibleStateDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead.");
|
||||
|
||||
/**
|
||||
* @abstract Called whenever the the node has entered or exited the display state.
|
||||
*
|
||||
* @discussion Subclasses may use this to monitor when a node should be rendering its content.
|
||||
*
|
||||
* @note This method can be called from any thread and should therefore be thread safe.
|
||||
*
|
||||
* @deprecated @see didEnterDisplayState @see didExitDisplayState
|
||||
*/
|
||||
- (void)displayStateDidChange:(BOOL)inDisplayState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterDisplayState / -didExitDisplayState instead.");
|
||||
|
||||
/**
|
||||
* @abstract Called whenever the the node has entered or left the load state.
|
||||
*
|
||||
* @discussion Subclasses may use this to monitor data for a node should be loaded, either from a local or remote source.
|
||||
*
|
||||
* @note This method can be called from any thread and should therefore be thread safe.
|
||||
*
|
||||
* @deprecated @see didEnterPreloadState @see didExitPreloadState
|
||||
*/
|
||||
- (void)loadStateDidChange:(BOOL)inLoadState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState / -didExitPreloadState instead.");
|
||||
|
||||
/**
|
||||
* @abstract Cancels all performing layout transitions. Can be called on any thread.
|
||||
*
|
||||
* @deprecated Deprecated in version 2.0: Use cancelLayoutTransition
|
||||
*/
|
||||
- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED_MSG("Use -cancelLayoutTransition instead.");
|
||||
|
||||
/**
|
||||
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
|
||||
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
|
||||
*
|
||||
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
|
||||
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
|
||||
*
|
||||
* @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead.");
|
||||
|
||||
/**
|
||||
* @abstract Indicates that the node should fetch any external data, such as images.
|
||||
*
|
||||
* @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching
|
||||
* should be done asynchronously. The node is also responsible for managing the memory of any data.
|
||||
* The data may be remote and accessed via the network, but could also be a local database query.
|
||||
*/
|
||||
- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead.");
|
||||
|
||||
/**
|
||||
* Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node.
|
||||
*
|
||||
* @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or
|
||||
* selectively clear fetched data.
|
||||
*/
|
||||
- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead.");
|
||||
|
||||
@end
|
1036
submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm
Normal file
1036
submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm
Normal file
File diff suppressed because it is too large
Load Diff
145
submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm
Normal file
145
submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm
Normal file
@ -0,0 +1,145 @@
|
||||
//
|
||||
// ASDisplayNode+LayoutSpec.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#import "_ASScopeTimer.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import "ASLayoutSpec+Subclasses.h"
|
||||
#import "ASLayoutSpecPrivate.h"
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutSpec)
|
||||
|
||||
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
|
||||
{
|
||||
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together.
|
||||
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits),
|
||||
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:");
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
_layoutSpecBlock = layoutSpecBlock;
|
||||
}
|
||||
|
||||
- (ASLayoutSpecBlock)layoutSpecBlock
|
||||
{
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
return _layoutSpecBlock;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize
|
||||
{
|
||||
AS::UniqueLock l(__instanceLock__);
|
||||
|
||||
// Manual size calculation via calculateSizeThatFits:
|
||||
if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) {
|
||||
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
|
||||
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size));
|
||||
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
|
||||
}
|
||||
|
||||
// Size calcualtion with layout elements
|
||||
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
|
||||
if (measureLayoutSpec) {
|
||||
_layoutSpecNumberOfPasses++;
|
||||
}
|
||||
|
||||
// Get layout element from the node
|
||||
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize];
|
||||
#if ASEnableVerboseLogging
|
||||
for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) {
|
||||
as_log_verbose(ASLayoutLog(), "%@", asciiLine);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Certain properties are necessary to set on an element of type ASLayoutSpec
|
||||
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) {
|
||||
ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement;
|
||||
|
||||
#if AS_DEDUPE_LAYOUT_SPEC_TREE
|
||||
NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree];
|
||||
if (duplicateElements.count > 0) {
|
||||
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements);
|
||||
// Use an empty layout spec to avoid crashes
|
||||
layoutSpec = [[ASLayoutSpec alloc] init];
|
||||
}
|
||||
#endif
|
||||
|
||||
ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec);
|
||||
|
||||
layoutSpec.isMutable = NO;
|
||||
}
|
||||
|
||||
// Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection
|
||||
{
|
||||
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
|
||||
ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection);
|
||||
}
|
||||
|
||||
BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation;
|
||||
if (measureLayoutComputation) {
|
||||
_layoutComputationNumberOfPasses++;
|
||||
}
|
||||
|
||||
// Layout element layout creation
|
||||
ASLayout *layout = ({
|
||||
AS::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
|
||||
[layoutElement layoutThatFits:constrainedSize];
|
||||
});
|
||||
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout);
|
||||
|
||||
// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct.
|
||||
BOOL isFinalLayoutElement = (layout.layoutElement != self);
|
||||
if (isFinalLayoutElement) {
|
||||
layout.position = CGPointZero;
|
||||
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]];
|
||||
}
|
||||
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);
|
||||
|
||||
// PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver
|
||||
if ([ASDisplayNode shouldStoreUnflattenedLayouts]) {
|
||||
_unflattenedLayout = layout;
|
||||
}
|
||||
layout = [layout filteredNodeLayoutTree];
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
|
||||
|
||||
if (_layoutSpecBlock != NULL) {
|
||||
return ({
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
|
||||
_layoutSpecBlock(self, constrainedSize);
|
||||
});
|
||||
} else {
|
||||
return ({
|
||||
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
|
||||
[self layoutSpecThatFits:constrainedSize];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
__ASDisplayNodeCheckForLayoutMethodOverrides;
|
||||
|
||||
ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported.");
|
||||
return [[ASLayoutSpec alloc] init];
|
||||
}
|
||||
|
||||
@end
|
1325
submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm
Normal file
1325
submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm
Normal file
File diff suppressed because it is too large
Load Diff
3803
submodules/AsyncDisplayKit/Source/ASDisplayNode.mm
Normal file
3803
submodules/AsyncDisplayKit/Source/ASDisplayNode.mm
Normal file
File diff suppressed because it is too large
Load Diff
339
submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm
Normal file
339
submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm
Normal file
@ -0,0 +1,339 @@
|
||||
//
|
||||
// ASDisplayNodeExtras.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
#import <queue>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
|
||||
void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr) {
|
||||
/**
|
||||
* UIKit components must be deallocated on the main thread. We use this shared
|
||||
* run loop queue to gradually deallocate them across many turns of the main run loop.
|
||||
*/
|
||||
static ASRunLoopQueue *queue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil];
|
||||
queue.batchSize = 10;
|
||||
});
|
||||
|
||||
if (objectPtr != NULL && *objectPtr != nil) {
|
||||
// TODO: If ASRunLoopQueue supported an "unsafe_unretained" mode, we could
|
||||
// transfer the caller's +1 into it and save the retain/release pair.
|
||||
|
||||
// Lock queue while enqueuing and releasing, so that there's no risk
|
||||
// that the queue will release before we get a chance to release.
|
||||
[queue lock];
|
||||
[queue enqueue:*objectPtr]; // Retain, +1
|
||||
*objectPtr = nil; // Release, +0
|
||||
[queue unlock]; // (After queue drains), release, -1
|
||||
}
|
||||
}
|
||||
|
||||
void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...)
|
||||
{
|
||||
NSString *owningClassName = NSStringFromClass(owningClass);
|
||||
NSArray *nameArray = [names componentsSeparatedByString:@", "];
|
||||
va_list args;
|
||||
va_start(args, object);
|
||||
NSInteger i = 0;
|
||||
for (ASDisplayNode *node = object; node != nil; node = va_arg(args, id), i++) {
|
||||
NSMutableString *symbolName = [nameArray[i] mutableCopy];
|
||||
// Remove any `self.` or `_` prefix
|
||||
[symbolName replaceOccurrencesOfString:@"self." withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
|
||||
[symbolName replaceOccurrencesOfString:@"_" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
|
||||
node.debugName = [NSString stringWithFormat:@"%@.%@", owningClassName, symbolName];
|
||||
}
|
||||
ASDisplayNodeCAssert(nameArray.count == i, @"Malformed call to ASSetDebugNames: %@", names);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
|
||||
{
|
||||
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
|
||||
if (displayNode && [displayNode supportsRangeManagedInterfaceState]) {
|
||||
// Directly clear the visible bit if we are not in a window. This means that the interface state is,
|
||||
// if not already, about to be set to invisible as it is not possible for an element to be visible
|
||||
// while outside of a window.
|
||||
ASInterfaceState interfaceState = displayNode.pendingInterfaceState;
|
||||
return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState);
|
||||
} else {
|
||||
// For not range managed nodes we might be on our own to try to guess if we're visible.
|
||||
return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay));
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
|
||||
{
|
||||
return layer.asyncdisplaykit_node;
|
||||
}
|
||||
|
||||
ASDisplayNode *ASViewToDisplayNode(UIView *view)
|
||||
{
|
||||
return view.asyncdisplaykit_node;
|
||||
}
|
||||
|
||||
void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
if (!node) {
|
||||
ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer");
|
||||
ASDisplayNodeCAssertMainThread();
|
||||
node = ASLayerToDisplayNode(layer);
|
||||
}
|
||||
|
||||
if (node) {
|
||||
block(node);
|
||||
}
|
||||
if (traverseSublayers && !layer && [node isNodeLoaded] && ASDisplayNodeThreadIsMain()) {
|
||||
layer = node.layer;
|
||||
}
|
||||
|
||||
if (traverseSublayers && layer && node.rasterizesSubtree == NO) {
|
||||
/// NOTE: The docs say `sublayers` returns a copy, but it does not.
|
||||
/// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated
|
||||
for (CALayer *sublayer in [[layer sublayers] copy]) {
|
||||
ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, traverseSublayers, block);
|
||||
}
|
||||
} else if (node) {
|
||||
for (ASDisplayNode *subnode in [node subnodes]) {
|
||||
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, traverseSublayers, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
|
||||
std::queue<ASDisplayNode *> queue;
|
||||
queue.push(node);
|
||||
|
||||
while (!queue.empty()) {
|
||||
node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
block(node);
|
||||
|
||||
// Add all subnodes to process in next step
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
queue.push(subnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, YES, block);
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
// This function has historically started with `self` but the name suggests
|
||||
// that it wouldn't. Perhaps we should change the behavior.
|
||||
for (ASDisplayNode *ancestor in node.supernodesIncludingSelf) {
|
||||
if (block(ancestor)) {
|
||||
return ancestor;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
|
||||
{
|
||||
// This function has historically started with `self` but the name suggests
|
||||
// that it wouldn't. Perhaps we should change the behavior.
|
||||
return [start supernodeOfClass:c includingSelf:YES];
|
||||
}
|
||||
|
||||
static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer)
|
||||
{
|
||||
ASDisplayNode *node = ASLayerToDisplayNode(layer);
|
||||
|
||||
if (nil != node) {
|
||||
[array addObject:node];
|
||||
}
|
||||
|
||||
for (CALayer *sublayer in layer.sublayers)
|
||||
_ASCollectDisplayNodes(array, sublayer);
|
||||
}
|
||||
|
||||
NSArray<ASDisplayNode *> *ASCollectDisplayNodes(ASDisplayNode *node)
|
||||
{
|
||||
NSMutableArray *list = [[NSMutableArray alloc] init];
|
||||
for (CALayer *sublayer in node.layer.sublayers) {
|
||||
_ASCollectDisplayNodes(list, sublayer);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
#pragma mark - Find all subnodes
|
||||
|
||||
static void _ASDisplayNodeFindAllSubnodes(NSMutableArray *array, ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
if (block(subnode)) {
|
||||
[array addObject:subnode];
|
||||
}
|
||||
|
||||
_ASDisplayNodeFindAllSubnodes(array, subnode, block);
|
||||
}
|
||||
}
|
||||
|
||||
NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodes(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
NSMutableArray *list = [[NSMutableArray alloc] init];
|
||||
_ASDisplayNodeFindAllSubnodes(list, start, block);
|
||||
return list;
|
||||
}
|
||||
|
||||
NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c)
|
||||
{
|
||||
return ASDisplayNodeFindAllSubnodes(start, ^(ASDisplayNode *n) {
|
||||
return [n isKindOfClass:c];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark - Find first subnode
|
||||
|
||||
static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
for (ASDisplayNode *subnode in startNode.subnodes) {
|
||||
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block);
|
||||
if (foundNode) {
|
||||
return foundNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeStartNode && block(startNode))
|
||||
return startNode;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
__kindof ASDisplayNode *ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
return _ASDisplayNodeFindFirstNode(startNode, YES, block);
|
||||
}
|
||||
|
||||
__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
|
||||
{
|
||||
return _ASDisplayNodeFindFirstNode(startNode, NO, block);
|
||||
}
|
||||
|
||||
__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)
|
||||
{
|
||||
return ASDisplayNodeFindFirstSubnode(start, ^(ASDisplayNode *n) {
|
||||
return [n isKindOfClass:c];
|
||||
});
|
||||
}
|
||||
|
||||
static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendant)
|
||||
{
|
||||
ASDisplayNode *supernode = possibleDescendant;
|
||||
while (supernode) {
|
||||
if (supernode == possibleAncestor) {
|
||||
return YES;
|
||||
}
|
||||
supernode = supernode.supernode;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer)
|
||||
{
|
||||
UIView *view = ASFindClosestViewOfLayer(layer);
|
||||
if (UIWindow *window = ASDynamicCast(view, UIWindow)) {
|
||||
return window;
|
||||
} else {
|
||||
return view.window;
|
||||
}
|
||||
}
|
||||
|
||||
UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer)
|
||||
{
|
||||
while (layer != nil) {
|
||||
if (UIView *view = ASDynamicCast(layer.delegate, UIView)) {
|
||||
return view;
|
||||
}
|
||||
layer = layer.superlayer;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2)
|
||||
{
|
||||
ASDisplayNode *possibleAncestor = node1;
|
||||
while (possibleAncestor) {
|
||||
if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) {
|
||||
break;
|
||||
}
|
||||
possibleAncestor = possibleAncestor.supernode;
|
||||
}
|
||||
|
||||
ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2);
|
||||
return possibleAncestor;
|
||||
}
|
||||
|
||||
ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node)
|
||||
{
|
||||
// node <- supernode on each loop
|
||||
// previous <- node on each loop where node is not nil
|
||||
// previous is the final non-nil value of supernode, i.e. the root node
|
||||
ASDisplayNode *previousNode = node;
|
||||
while ((node = [node supernode])) {
|
||||
previousNode = node;
|
||||
}
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
#pragma mark - Placeholders
|
||||
|
||||
UIColor *ASDisplayNodeDefaultPlaceholderColor()
|
||||
{
|
||||
static UIColor *defaultPlaceholderColor;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
defaultPlaceholderColor = [UIColor colorWithWhite:0.95 alpha:1.0];
|
||||
});
|
||||
return defaultPlaceholderColor;
|
||||
}
|
||||
|
||||
UIColor *ASDisplayNodeDefaultTintColor()
|
||||
{
|
||||
static UIColor *defaultTintColor;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0];
|
||||
});
|
||||
return defaultTintColor;
|
||||
}
|
||||
|
||||
#pragma mark - Hierarchy Notifications
|
||||
|
||||
void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node)
|
||||
{
|
||||
[node __incrementVisibilityNotificationsDisabled];
|
||||
}
|
||||
|
||||
void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node)
|
||||
{
|
||||
[node __decrementVisibilityNotificationsDisabled];
|
||||
}
|
407
submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h
Normal file
407
submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h
Normal file
@ -0,0 +1,407 @@
|
||||
//
|
||||
// ASDisplayNodeInternal.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
//
|
||||
// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode.
|
||||
// These methods must never be called or overridden by other classes.
|
||||
//
|
||||
|
||||
#import <atomic>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
#import "ASLayoutTransition.h"
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/_ASTransitionContext.h>
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol _ASDisplayLayerDelegate;
|
||||
@class _ASDisplayLayer;
|
||||
@class _ASPendingState;
|
||||
@class ASNodeController;
|
||||
struct ASDisplayNodeFlags;
|
||||
|
||||
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
|
||||
BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLayerBacked);
|
||||
|
||||
/// Get the pending view state for the node, creating one if needed.
|
||||
_ASPendingState * ASDisplayNodeGetPendingState(ASDisplayNode * node);
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
{
|
||||
ASDisplayNodeMethodOverrideNone = 0,
|
||||
ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0,
|
||||
ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1,
|
||||
ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2,
|
||||
ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3,
|
||||
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4,
|
||||
ASDisplayNodeMethodOverrideCalcLayoutThatFits = 1 << 5,
|
||||
ASDisplayNodeMethodOverrideCalcSizeThatFits = 1 << 6,
|
||||
ASDisplayNodeMethodOverrideCanBecomeFirstResponder= 1 << 7,
|
||||
ASDisplayNodeMethodOverrideBecomeFirstResponder = 1 << 8,
|
||||
ASDisplayNodeMethodOverrideCanResignFirstResponder= 1 << 9,
|
||||
ASDisplayNodeMethodOverrideResignFirstResponder = 1 << 10,
|
||||
ASDisplayNodeMethodOverrideIsFirstResponder = 1 << 11,
|
||||
};
|
||||
|
||||
typedef NS_OPTIONS(uint_least32_t, ASDisplayNodeAtomicFlags)
|
||||
{
|
||||
Synchronous = 1 << 0,
|
||||
YogaLayoutInProgress = 1 << 1,
|
||||
};
|
||||
|
||||
// Can be called without the node's lock. Client is responsible for thread safety.
|
||||
#define _loaded(node) (node->_layer != nil)
|
||||
|
||||
#define checkFlag(flag) ((_atomicFlags.load() & flag) != 0)
|
||||
// Returns the old value of the flag as a BOOL.
|
||||
#define setFlag(flag, x) (((x ? _atomicFlags.fetch_or(flag) \
|
||||
: _atomicFlags.fetch_and(~flag)) & flag) != 0)
|
||||
|
||||
AS_EXTERN NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification;
|
||||
AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp;
|
||||
|
||||
// Allow 2^n increments of begin disabling hierarchy notifications
|
||||
#define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4
|
||||
|
||||
#define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE)
|
||||
|
||||
#define NUM_CLIP_CORNER_LAYERS 4
|
||||
|
||||
@interface ASDisplayNode () <_ASTransitionContextCompletionDelegate>
|
||||
{
|
||||
@package
|
||||
AS::RecursiveMutex __instanceLock__;
|
||||
|
||||
_ASPendingState *_pendingViewState;
|
||||
ASInterfaceState _pendingInterfaceState;
|
||||
ASInterfaceState _preExitingInterfaceState;
|
||||
|
||||
UIView *_view;
|
||||
CALayer *_layer;
|
||||
|
||||
std::atomic<ASDisplayNodeAtomicFlags> _atomicFlags;
|
||||
|
||||
struct ASDisplayNodeFlags {
|
||||
// public properties
|
||||
unsigned viewEverHadAGestureRecognizerAttached:1;
|
||||
unsigned layerBacked:1;
|
||||
unsigned displaysAsynchronously:1;
|
||||
unsigned rasterizesSubtree:1;
|
||||
unsigned shouldBypassEnsureDisplay:1;
|
||||
unsigned displaySuspended:1;
|
||||
unsigned shouldAnimateSizeChanges:1;
|
||||
|
||||
// Wrapped view handling
|
||||
|
||||
// The layer contents should not be cleared in case the node is wrapping a UIImageView.UIImageView is specifically
|
||||
// optimized for performance and does not use the usual way to provide the contents of the CALayer via the
|
||||
// CALayerDelegate method that backs the UIImageView.
|
||||
unsigned canClearContentsOfLayer:1;
|
||||
|
||||
// Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer
|
||||
// triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView
|
||||
// it goes through the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately
|
||||
// UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the
|
||||
// methods at all instead it throws away the contents of the layer and nothing will show up.
|
||||
unsigned canCallSetNeedsDisplayOfLayer:1;
|
||||
|
||||
unsigned implementsDrawRect:1;
|
||||
unsigned implementsImageDisplay:1;
|
||||
unsigned implementsDrawParameters:1;
|
||||
|
||||
// internal state
|
||||
unsigned isEnteringHierarchy:1;
|
||||
unsigned isExitingHierarchy:1;
|
||||
unsigned isInHierarchy:1;
|
||||
unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS;
|
||||
unsigned isDeallocating:1;
|
||||
} _flags;
|
||||
|
||||
@protected
|
||||
ASDisplayNode * __weak _supernode;
|
||||
NSMutableArray<ASDisplayNode *> *_subnodes;
|
||||
|
||||
ASNodeController *_strongNodeController;
|
||||
__weak ASNodeController *_weakNodeController;
|
||||
|
||||
// Set this to nil whenever you modify _subnodes
|
||||
NSArray<ASDisplayNode *> *_cachedSubnodes;
|
||||
|
||||
std::atomic_uint _displaySentinel;
|
||||
|
||||
// This is the desired contentsScale, not the scale at which the layer's contents should be displayed
|
||||
CGFloat _contentsScaleForDisplay;
|
||||
ASDisplayNodeMethodOverrides _methodOverrides;
|
||||
|
||||
UIEdgeInsets _hitTestSlop;
|
||||
|
||||
#if ASEVENTLOG_ENABLE
|
||||
ASEventLog *_eventLog;
|
||||
#endif
|
||||
|
||||
|
||||
// Layout support
|
||||
ASLayoutElementStyle *_style;
|
||||
std::atomic<ASPrimitiveTraitCollection> _primitiveTraitCollection;
|
||||
|
||||
// Layout Spec
|
||||
ASLayoutSpecBlock _layoutSpecBlock;
|
||||
NSString *_debugName;
|
||||
|
||||
#if YOGA
|
||||
// Only ASDisplayNodes are supported in _yogaChildren currently. This means that it is necessary to
|
||||
// create ASDisplayNodes to make a stack layout when using Yoga.
|
||||
// However, the implementation is mostly ready for id <ASLayoutElement>, with a few areas requiring updates.
|
||||
NSMutableArray<ASDisplayNode *> *_yogaChildren;
|
||||
__weak ASDisplayNode *_yogaParent;
|
||||
ASLayout *_yogaCalculatedLayout;
|
||||
#endif
|
||||
|
||||
// Automatically manages subnodes
|
||||
BOOL _automaticallyManagesSubnodes; // Main thread only
|
||||
|
||||
// Layout Transition
|
||||
_ASTransitionContext *_pendingLayoutTransitionContext;
|
||||
NSTimeInterval _defaultLayoutTransitionDuration;
|
||||
NSTimeInterval _defaultLayoutTransitionDelay;
|
||||
UIViewAnimationOptions _defaultLayoutTransitionOptions;
|
||||
|
||||
std::atomic<int32_t> _transitionID;
|
||||
std::atomic<int32_t> _pendingTransitionID;
|
||||
ASLayoutTransition *_pendingLayoutTransition;
|
||||
ASDisplayNodeLayout _calculatedDisplayNodeLayout;
|
||||
ASDisplayNodeLayout _pendingDisplayNodeLayout;
|
||||
|
||||
/// Sentinel for layout data. Incremented when we get -setNeedsLayout / -invalidateCalculatedLayout.
|
||||
/// Starts at 1.
|
||||
std::atomic<NSUInteger> _layoutVersion;
|
||||
|
||||
|
||||
// Layout Spec performance measurement
|
||||
ASDisplayNodePerformanceMeasurementOptions _measurementOptions;
|
||||
NSTimeInterval _layoutSpecTotalTime;
|
||||
NSInteger _layoutSpecNumberOfPasses;
|
||||
NSTimeInterval _layoutComputationTotalTime;
|
||||
NSInteger _layoutComputationNumberOfPasses;
|
||||
|
||||
|
||||
// View Loading
|
||||
ASDisplayNodeViewBlock _viewBlock;
|
||||
ASDisplayNodeLayerBlock _layerBlock;
|
||||
NSMutableArray<ASDisplayNodeDidLoadBlock> *_onDidLoadBlocks;
|
||||
Class _viewClass; // nil -> _ASDisplayView
|
||||
Class _layerClass; // nil -> _ASDisplayLayer
|
||||
|
||||
|
||||
// Placeholder support
|
||||
UIImage *_placeholderImage;
|
||||
BOOL _placeholderEnabled;
|
||||
CALayer *_placeholderLayer;
|
||||
|
||||
// keeps track of nodes/subnodes that have not finished display, used with placeholders
|
||||
ASWeakSet *_pendingDisplayNodes;
|
||||
|
||||
|
||||
// Corner Radius support
|
||||
CGFloat _cornerRadius;
|
||||
ASCornerRoundingType _cornerRoundingType;
|
||||
CALayer *_clipCornerLayers[NUM_CLIP_CORNER_LAYERS];
|
||||
|
||||
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
|
||||
// Accessibility support
|
||||
BOOL _isAccessibilityElement;
|
||||
NSString *_accessibilityLabel;
|
||||
NSAttributedString *_accessibilityAttributedLabel;
|
||||
NSString *_accessibilityHint;
|
||||
NSAttributedString *_accessibilityAttributedHint;
|
||||
NSString *_accessibilityValue;
|
||||
NSAttributedString *_accessibilityAttributedValue;
|
||||
UIAccessibilityTraits _accessibilityTraits;
|
||||
CGRect _accessibilityFrame;
|
||||
NSString *_accessibilityLanguage;
|
||||
BOOL _accessibilityElementsHidden;
|
||||
BOOL _accessibilityViewIsModal;
|
||||
BOOL _shouldGroupAccessibilityChildren;
|
||||
NSString *_accessibilityIdentifier;
|
||||
UIAccessibilityNavigationStyle _accessibilityNavigationStyle;
|
||||
NSArray *_accessibilityHeaderElements;
|
||||
CGPoint _accessibilityActivationPoint;
|
||||
UIBezierPath *_accessibilityPath;
|
||||
BOOL _isAccessibilityContainer;
|
||||
|
||||
|
||||
// Safe Area support
|
||||
// These properties are used on iOS 10 and lower, where safe area is not supported by UIKit.
|
||||
UIEdgeInsets _fallbackSafeAreaInsets;
|
||||
BOOL _fallbackInsetsLayoutMarginsFromSafeArea;
|
||||
|
||||
BOOL _automaticallyRelayoutOnSafeAreaChanges;
|
||||
BOOL _automaticallyRelayoutOnLayoutMarginsChanges;
|
||||
|
||||
BOOL _isViewControllerRoot;
|
||||
|
||||
|
||||
#pragma mark - ASDisplayNode (Debugging)
|
||||
ASLayout *_unflattenedLayout;
|
||||
|
||||
#if TIME_DISPLAYNODE_OPS
|
||||
@public
|
||||
NSTimeInterval _debugTimeToCreateView;
|
||||
NSTimeInterval _debugTimeToApplyPendingState;
|
||||
NSTimeInterval _debugTimeToAddSubnodeViews;
|
||||
NSTimeInterval _debugTimeForDidLoad;
|
||||
#endif
|
||||
|
||||
/// Fast path: tells whether we've ever had an interface state delegate before.
|
||||
BOOL _hasHadInterfaceStateDelegates;
|
||||
__weak id<ASInterfaceStateDelegate> _interfaceStateDelegates[AS_MAX_INTERFACE_STATE_DELEGATES];
|
||||
}
|
||||
|
||||
+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node;
|
||||
|
||||
/// The _ASDisplayLayer backing the node, if any.
|
||||
@property (nullable, nonatomic, readonly) _ASDisplayLayer *asyncLayer;
|
||||
|
||||
/// Bitmask to check which methods an object overrides.
|
||||
- (ASDisplayNodeMethodOverrides)methodOverrides;
|
||||
|
||||
/**
|
||||
* Invoked before a call to setNeedsLayout to the underlying view
|
||||
*/
|
||||
- (void)__setNeedsLayout;
|
||||
|
||||
/**
|
||||
* Invoked after a call to setNeedsDisplay to the underlying view
|
||||
*/
|
||||
- (void)__setNeedsDisplay;
|
||||
|
||||
/**
|
||||
* Called whenever the node needs to layout its subnodes and, if it's already loaded, its subviews. Executes the layout pass for the node
|
||||
*
|
||||
* This method is thread-safe but asserts thread affinity.
|
||||
*/
|
||||
- (void)__layout;
|
||||
|
||||
/**
|
||||
* Internal method to add / replace / insert subnode and remove from supernode without checking if
|
||||
* node has automaticallyManagesSubnodes set to YES.
|
||||
*/
|
||||
- (void)_addSubnode:(ASDisplayNode *)subnode;
|
||||
- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode;
|
||||
- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below;
|
||||
- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above;
|
||||
- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx;
|
||||
- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode;
|
||||
- (void)_removeFromSupernode;
|
||||
|
||||
// Private API for helper functions / unit tests. Use ASDisplayNodeDisableHierarchyNotifications() to control this.
|
||||
- (BOOL)__visibilityNotificationsDisabled;
|
||||
- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled;
|
||||
- (void)__incrementVisibilityNotificationsDisabled;
|
||||
- (void)__decrementVisibilityNotificationsDisabled;
|
||||
|
||||
// Helper methods for UIResponder forwarding
|
||||
- (BOOL)__canBecomeFirstResponder;
|
||||
- (BOOL)__becomeFirstResponder;
|
||||
- (BOOL)__canResignFirstResponder;
|
||||
- (BOOL)__resignFirstResponder;
|
||||
- (BOOL)__isFirstResponder;
|
||||
|
||||
/// Helper method to summarize whether or not the node run through the display process
|
||||
- (BOOL)_implementsDisplay;
|
||||
|
||||
/// Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated.
|
||||
- (void)displayImmediately;
|
||||
|
||||
/// Refreshes any precomposited or drawn clip corners, setting up state as required to transition radius or rounding type.
|
||||
- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius;
|
||||
|
||||
/// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses.
|
||||
- (instancetype)initWithViewClass:(Class)viewClass;
|
||||
|
||||
/// Alternative initialiser for backing with a custom layer class. Supports asynchronous display with _ASDisplayLayer subclasses.
|
||||
- (instancetype)initWithLayerClass:(Class)layerClass;
|
||||
|
||||
@property (nonatomic) CGFloat contentsScaleForDisplay;
|
||||
|
||||
- (void)applyPendingViewState;
|
||||
|
||||
/**
|
||||
* Makes a local copy of the interface state delegates then calls the block on each.
|
||||
*
|
||||
* Lock is not held during block invocation. Method must not be called with the lock held.
|
||||
*/
|
||||
- (void)enumerateInterfaceStateDelegates:(void(NS_NOESCAPE ^)(id<ASInterfaceStateDelegate> delegate))block;
|
||||
|
||||
/**
|
||||
* // TODO: NOT YET IMPLEMENTED
|
||||
*
|
||||
* @abstract Prevents interface state changes from affecting the node, until disabled.
|
||||
*
|
||||
* @discussion Useful to avoid flashing after removing a node from the hierarchy and re-adding it.
|
||||
* Removing a node from the hierarchy will cause it to exit the Display state, clearing its contents.
|
||||
* For some animations, it's desirable to be able to remove a node without causing it to re-display.
|
||||
* Once re-enabled, the interface state will be updated to the same value it would have been.
|
||||
*
|
||||
* @see ASInterfaceState
|
||||
*/
|
||||
@property (nonatomic) BOOL interfaceStateSuspended;
|
||||
|
||||
/**
|
||||
* This method has proven helpful in a few rare scenarios, similar to a category extension on UIView,
|
||||
* but it's considered private API for now and its use should not be encouraged.
|
||||
* @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode.
|
||||
* If YES, this method must be called on the main thread and the node must not be layer-backed.
|
||||
*/
|
||||
- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy;
|
||||
|
||||
/**
|
||||
* Whether this node rasterizes its descendants. See -enableSubtreeRasterization.
|
||||
*/
|
||||
@property (readonly) BOOL rasterizesSubtree;
|
||||
|
||||
/**
|
||||
* Called if a gesture recognizer was attached to an _ASDisplayView
|
||||
*/
|
||||
- (void)nodeViewDidAddGestureRecognizer;
|
||||
|
||||
// Recalculates fallbackSafeAreaInsets for the subnodes
|
||||
- (void)_fallbackUpdateSafeAreaOnChildren;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (InternalPropertyBridge)
|
||||
|
||||
@property (nonatomic) CGFloat layerCornerRadius;
|
||||
|
||||
- (BOOL)_locked_insetsLayoutMarginsFromSafeArea;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayoutElementPrivate)
|
||||
|
||||
/**
|
||||
* Returns the internal style object or creates a new if no exists. Need to be called with lock held.
|
||||
*/
|
||||
- (ASLayoutElementStyle *)_locked_style;
|
||||
|
||||
/**
|
||||
* Returns the current layout element. Need to be called with lock held.
|
||||
*/
|
||||
- (id<ASLayoutElement>)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
59
submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h
Normal file
59
submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// ASDisplayNodeLayout.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
@class ASLayout;
|
||||
|
||||
/*
|
||||
* Represents a connection between an ASLayout and a ASDisplayNode
|
||||
* ASDisplayNode uses this to store additional information that are necessary besides the layout
|
||||
*/
|
||||
struct ASDisplayNodeLayout {
|
||||
ASLayout *layout;
|
||||
ASSizeRange constrainedSize;
|
||||
CGSize parentSize;
|
||||
BOOL requestedLayoutFromAbove;
|
||||
NSUInteger version;
|
||||
|
||||
/*
|
||||
* Create a new display node layout with
|
||||
* @param layout The layout to associate, usually returned from a call to -layoutThatFits:parentSize:
|
||||
* @param constrainedSize Constrained size used to create the layout
|
||||
* @param parentSize Parent size used to create the layout
|
||||
* @param version The version of the source layout data – see ASDisplayNode's _layoutVersion.
|
||||
*/
|
||||
ASDisplayNodeLayout(ASLayout *layout, ASSizeRange constrainedSize, CGSize parentSize, NSUInteger version)
|
||||
: layout(layout), constrainedSize(constrainedSize), parentSize(parentSize), requestedLayoutFromAbove(NO), version(version) {};
|
||||
|
||||
/*
|
||||
* Creates a layout without any layout associated. By default this display node layout is dirty.
|
||||
*/
|
||||
ASDisplayNodeLayout()
|
||||
: layout(nil), constrainedSize({{0, 0}, {0, 0}}), parentSize({0, 0}), requestedLayoutFromAbove(NO), version(0) {};
|
||||
|
||||
/**
|
||||
* Returns whether this is valid for a given version
|
||||
*/
|
||||
BOOL isValid(NSUInteger versionArg) {
|
||||
return layout != nil && version >= versionArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is valid for a given constrained size, parent size, and version
|
||||
*/
|
||||
BOOL isValid(ASSizeRange theConstrainedSize, CGSize theParentSize, NSUInteger versionArg) {
|
||||
return isValid(versionArg)
|
||||
&& CGSizeEqualToSize(parentSize, theParentSize)
|
||||
&& ASSizeRangeEqualToSizeRange(constrainedSize, theConstrainedSize);
|
||||
}
|
||||
};
|
1172
submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm
Normal file
1172
submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm
Normal file
File diff suppressed because it is too large
Load Diff
52
submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm
Normal file
52
submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm
Normal file
@ -0,0 +1,52 @@
|
||||
//
|
||||
// ASExperimentalFeatures.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASExperimentalFeatures.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
|
||||
NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags)
|
||||
{
|
||||
NSArray *allNames = ASCreateOnce((@[@"exp_graphics_contexts",
|
||||
@"exp_text_node",
|
||||
@"exp_interface_state_coalesce",
|
||||
@"exp_unfair_lock",
|
||||
@"exp_infer_layer_defaults",
|
||||
@"exp_collection_teardown",
|
||||
@"exp_framesetter_cache",
|
||||
@"exp_skip_clear_data",
|
||||
@"exp_did_enter_preload_skip_asm_layout",
|
||||
@"exp_disable_a11y_cache",
|
||||
@"exp_dispatch_apply",
|
||||
@"exp_image_downloader_priority",
|
||||
@"exp_text_drawing"]));
|
||||
if (flags == ASExperimentalFeatureAll) {
|
||||
return allNames;
|
||||
}
|
||||
|
||||
// Go through all names, testing each bit.
|
||||
NSUInteger i = 0;
|
||||
return ASArrayByFlatMapping(allNames, NSString *name, ({
|
||||
(flags & (1 << i++)) ? name : nil;
|
||||
}));
|
||||
}
|
||||
|
||||
// O(N^2) but with counts this small, it's probably faster
|
||||
// than hashing the strings.
|
||||
ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray<NSString *> *array)
|
||||
{
|
||||
NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll);
|
||||
ASExperimentalFeatures result = 0;
|
||||
for (NSString *str in array) {
|
||||
NSUInteger i = [allNames indexOfObject:str];
|
||||
if (i != NSNotFound) {
|
||||
result |= (1 << i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
167
submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm
Normal file
167
submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm
Normal file
@ -0,0 +1,167 @@
|
||||
//
|
||||
// ASGraphicsContext.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import "ASCGImageBuffer.h"
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <UIKit/UIGraphics.h>
|
||||
#import <UIKit/UIImage.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
/**
|
||||
* Our version of the private CGBitmapGetAlignedBytesPerRow function.
|
||||
*
|
||||
* In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32
|
||||
* in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that
|
||||
* the bytes-per-row for a 1x1 context from the system is 32.
|
||||
*/
|
||||
static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) {
|
||||
// Add 31 then zero out low 5 bits.
|
||||
return (baseValue + 31) & ~0x1F;
|
||||
}
|
||||
|
||||
/**
|
||||
* A key used to associate CGContextRef -> NSMutableData, nonatomic retain.
|
||||
*
|
||||
* That way the data will be released when the context dies. If they pull an image,
|
||||
* we will retain the data object (in a CGDataProvider) before releasing the context.
|
||||
*/
|
||||
static UInt8 __contextDataAssociationKey;
|
||||
|
||||
#pragma mark - Graphics Contexts
|
||||
|
||||
void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
|
||||
return;
|
||||
}
|
||||
|
||||
// We use "reference contexts" to get device-specific options that UIKit
|
||||
// uses.
|
||||
static dispatch_once_t onceToken;
|
||||
static CGContextRef refCtxOpaque;
|
||||
static CGContextRef refCtxTransparent;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1);
|
||||
refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext());
|
||||
ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?");
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
// Make transparent ref context.
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1);
|
||||
refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext());
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
|
||||
// These options are taken from UIGraphicsBeginImageContext.
|
||||
CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent;
|
||||
CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx);
|
||||
|
||||
if (scale == 0) {
|
||||
scale = ASScreenScale();
|
||||
}
|
||||
size_t intWidth = (size_t)ceil(size.width * scale);
|
||||
size_t intHeight = (size_t)ceil(size.height * scale);
|
||||
size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx);
|
||||
size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8;
|
||||
bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow);
|
||||
size_t bufferSize = bytesPerRow * intHeight;
|
||||
CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx);
|
||||
|
||||
// We create our own buffer, and wrap the context around that. This way we can prevent
|
||||
// the copy that usually gets made when you form a CGImage from the context.
|
||||
ASCGImageBuffer *buffer = [[ASCGImageBuffer alloc] initWithLength:bufferSize];
|
||||
|
||||
CGContextRef context = CGBitmapContextCreate(buffer.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo);
|
||||
|
||||
// Transfer ownership of the data to the context. So that if the context
|
||||
// is destroyed before we create an image from it, the data will be released.
|
||||
objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, buffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
|
||||
// Set the CTM to account for iOS orientation & specified scale.
|
||||
// If only we could use CGContextSetBaseCTM. It doesn't
|
||||
// seem like there are any consequences for our use case
|
||||
// but we'll be on the look out. The internet hinted that it
|
||||
// affects shadowing but I tested and shadowing works.
|
||||
CGContextTranslateCTM(context, 0, intHeight);
|
||||
CGContextScaleCTM(context, scale, -scale);
|
||||
|
||||
// Save the state so we can restore it and recover our scale in GetImageAndEnd
|
||||
CGContextSaveGState(context);
|
||||
|
||||
// Transfer context ownership to the UIKit stack.
|
||||
UIGraphicsPushContext(context);
|
||||
CGContextRelease(context);
|
||||
}
|
||||
|
||||
UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
||||
// Pop the context and make sure we have one.
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (context == NULL) {
|
||||
ASDisplayNodeCFailAssert(@"Can't end image context without having begun one.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Read the device-specific ICC-based color space to use for the image.
|
||||
// For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage
|
||||
// generates an image in a device-specific color space (for wide color support).
|
||||
// We replicate that behavior, even though at this time CA does not
|
||||
// require the image to be in this space. Plain DeviceRGB images seem
|
||||
// to be treated exactly the same, but better safe than sorry.
|
||||
static CGColorSpaceRef imageColorSpace;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
|
||||
UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage));
|
||||
ASDisplayNodeCAssertNotNil(imageColorSpace, nil);
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
|
||||
// Retrieve our buffer and create a CGDataProvider from it.
|
||||
ASCGImageBuffer *buffer = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey);
|
||||
ASDisplayNodeCAssertNotNil(buffer, nil);
|
||||
CGDataProviderRef provider = [buffer createDataProviderAndInvalidate];
|
||||
|
||||
// Create the CGImage. Options taken from CGBitmapContextCreateImage.
|
||||
CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault);
|
||||
CGDataProviderRelease(provider);
|
||||
|
||||
// We saved our GState right after setting the CTM so that we could restore it
|
||||
// here and get the original scale back.
|
||||
CGContextRestoreGState(context);
|
||||
CGFloat scale = CGContextGetCTM(context).a;
|
||||
|
||||
// Note: popping from the UIKit stack will probably destroy the context.
|
||||
context = NULL;
|
||||
UIGraphicsPopContext();
|
||||
|
||||
UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp];
|
||||
CGImageRelease(cgImg);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ASGraphicsEndImageContext()
|
||||
{
|
||||
if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) {
|
||||
UIGraphicsEndImageContext();
|
||||
return;
|
||||
}
|
||||
|
||||
UIGraphicsPopContext();
|
||||
}
|
38
submodules/AsyncDisplayKit/Source/ASHashing.mm
Normal file
38
submodules/AsyncDisplayKit/Source/ASHashing.mm
Normal file
@ -0,0 +1,38 @@
|
||||
//
|
||||
// ASHashing.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASHashing.h>
|
||||
|
||||
#define ELF_STEP(B) T1 = (H << 4) + B; T2 = T1 & 0xF0000000; if (T2) T1 ^= (T2 >> 24); T1 &= (~T2); H = T1;
|
||||
|
||||
/**
|
||||
* The hashing algorithm copied from CoreFoundation CFHashBytes function.
|
||||
* https://opensource.apple.com/source/CF/CF-1153.18/CFUtilities.c.auto.html
|
||||
*/
|
||||
NSUInteger ASHashBytes(void *bytesarg, size_t length) {
|
||||
/* The ELF hash algorithm, used in the ELF object file format */
|
||||
uint8_t *bytes = (uint8_t *)bytesarg;
|
||||
UInt32 H = 0, T1, T2;
|
||||
SInt32 rem = (SInt32)length;
|
||||
while (3 < rem) {
|
||||
ELF_STEP(bytes[length - rem]);
|
||||
ELF_STEP(bytes[length - rem + 1]);
|
||||
ELF_STEP(bytes[length - rem + 2]);
|
||||
ELF_STEP(bytes[length - rem + 3]);
|
||||
rem -= 4;
|
||||
}
|
||||
switch (rem) {
|
||||
case 3: ELF_STEP(bytes[length - 3]);
|
||||
case 2: ELF_STEP(bytes[length - 2]);
|
||||
case 1: ELF_STEP(bytes[length - 1]);
|
||||
case 0: ;
|
||||
}
|
||||
return H;
|
||||
}
|
||||
|
||||
#undef ELF_STEP
|
233
submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm
Normal file
233
submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm
Normal file
@ -0,0 +1,233 @@
|
||||
//
|
||||
// ASInternalHelpers.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <cmath>
|
||||
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
static NSNumber *allowsGroupOpacityFromUIKitOrNil;
|
||||
static NSNumber *allowsEdgeAntialiasingFromUIKitOrNil;
|
||||
|
||||
BOOL ASDefaultAllowsGroupOpacity()
|
||||
{
|
||||
static BOOL groupOpacity;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSNumber *groupOpacityObj = allowsGroupOpacityFromUIKitOrNil ?: [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIViewGroupOpacity"];
|
||||
groupOpacity = groupOpacityObj ? groupOpacityObj.boolValue : YES;
|
||||
});
|
||||
return groupOpacity;
|
||||
}
|
||||
|
||||
BOOL ASDefaultAllowsEdgeAntialiasing()
|
||||
{
|
||||
static BOOL edgeAntialiasing;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSNumber *antialiasingObj = allowsEdgeAntialiasingFromUIKitOrNil ?: [NSBundle.mainBundle objectForInfoDictionaryKey:@"UIViewEdgeAntialiasing"];
|
||||
edgeAntialiasing = antialiasingObj ? antialiasingObj.boolValue : NO;
|
||||
});
|
||||
return edgeAntialiasing;
|
||||
}
|
||||
|
||||
void ASInitializeFrameworkMainThread(void)
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
ASDisplayNodeCAssertMainThread();
|
||||
// Ensure these values are cached on the main thread before needed in the background.
|
||||
if (ASActivateExperimentalFeature(ASExperimentalLayerDefaults)) {
|
||||
// Nop. We will gather default values on-demand in ASDefaultAllowsGroupOpacity and ASDefaultAllowsEdgeAntialiasing
|
||||
} else {
|
||||
CALayer *layer = [[[UIView alloc] init] layer];
|
||||
allowsGroupOpacityFromUIKitOrNil = @(layer.allowsGroupOpacity);
|
||||
allowsEdgeAntialiasingFromUIKitOrNil = @(layer.allowsEdgeAntialiasing);
|
||||
}
|
||||
ASNotifyInitialized();
|
||||
});
|
||||
}
|
||||
|
||||
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector)
|
||||
{
|
||||
if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself.
|
||||
Method superclassMethod = class_getInstanceMethod(superclass, selector);
|
||||
Method subclassMethod = class_getInstanceMethod(subclass, selector);
|
||||
return (superclassMethod != subclassMethod);
|
||||
}
|
||||
|
||||
BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector)
|
||||
{
|
||||
if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself.
|
||||
Method superclassMethod = class_getClassMethod(superclass, selector);
|
||||
Method subclassMethod = class_getClassMethod(subclass, selector);
|
||||
return (superclassMethod != subclassMethod);
|
||||
}
|
||||
|
||||
IMP ASReplaceMethodWithBlock(Class c, SEL origSEL, id block)
|
||||
{
|
||||
NSCParameterAssert(block);
|
||||
|
||||
// Get original method
|
||||
Method origMethod = class_getInstanceMethod(c, origSEL);
|
||||
NSCParameterAssert(origMethod);
|
||||
|
||||
// Convert block to IMP trampoline and replace method implementation
|
||||
IMP newIMP = imp_implementationWithBlock(block);
|
||||
|
||||
// Try adding the method if not yet in the current class
|
||||
if (!class_addMethod(c, origSEL, newIMP, method_getTypeEncoding(origMethod))) {
|
||||
return method_setImplementation(origMethod, newIMP);
|
||||
} else {
|
||||
return method_getImplementation(origMethod);
|
||||
}
|
||||
}
|
||||
|
||||
void ASPerformBlockOnMainThread(void (^block)(void))
|
||||
{
|
||||
if (block == nil){
|
||||
return;
|
||||
}
|
||||
if (ASDisplayNodeThreadIsMain()) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
|
||||
void ASPerformBlockOnBackgroundThread(void (^block)(void))
|
||||
{
|
||||
if (block == nil){
|
||||
return;
|
||||
}
|
||||
if (ASDisplayNodeThreadIsMain()) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object)
|
||||
{
|
||||
[[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object];
|
||||
}
|
||||
|
||||
Class _Nullable ASGetClassFromType(const char * _Nullable type)
|
||||
{
|
||||
// Class types all start with @"
|
||||
if (type == NULL || strncmp(type, "@\"", 2) != 0) {
|
||||
return Nil;
|
||||
}
|
||||
|
||||
// Ensure length >= 3
|
||||
size_t typeLength = strlen(type);
|
||||
if (typeLength < 3) {
|
||||
ASDisplayNodeCFailAssert(@"Got invalid type-encoding: %s", type);
|
||||
return Nil;
|
||||
}
|
||||
|
||||
// Copy type[2..(end-1)]. So @"UIImage" -> UIImage
|
||||
size_t resultLength = typeLength - 3;
|
||||
char className[resultLength + 1];
|
||||
strncpy(className, type + 2, resultLength);
|
||||
className[resultLength] = '\0';
|
||||
return objc_getClass(className);
|
||||
}
|
||||
|
||||
CGFloat ASScreenScale()
|
||||
{
|
||||
static CGFloat __scale = 0.0;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
|
||||
__scale = CGContextGetCTM(UIGraphicsGetCurrentContext()).a;
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
return __scale;
|
||||
}
|
||||
|
||||
CGSize ASFloorSizeValues(CGSize s)
|
||||
{
|
||||
return CGSizeMake(ASFloorPixelValue(s.width), ASFloorPixelValue(s.height));
|
||||
}
|
||||
|
||||
// See ASCeilPixelValue for a more thoroguh explanation of (f + FLT_EPSILON),
|
||||
// but here is some quick math:
|
||||
//
|
||||
// Imagine a layout that comes back with a height of 100.66666666663
|
||||
// for a 3x deice:
|
||||
// 100.66666666663 * 3 = 301.99999999988995
|
||||
// floor(301.99999999988995) = 301
|
||||
// 301 / 3 = 100.333333333
|
||||
//
|
||||
// If we add FLT_EPSILON to normalize the garbage at the end we get:
|
||||
// po (100.66666666663 + FLT_EPSILON) * 3 = 302.00000035751782
|
||||
// floor(302.00000035751782) = 302
|
||||
// 302/3 = 100.66666666
|
||||
CGFloat ASFloorPixelValue(CGFloat f)
|
||||
{
|
||||
CGFloat scale = ASScreenScale();
|
||||
return floor((f + FLT_EPSILON) * scale) / scale;
|
||||
}
|
||||
|
||||
CGPoint ASCeilPointValues(CGPoint p)
|
||||
{
|
||||
return CGPointMake(ASCeilPixelValue(p.x), ASCeilPixelValue(p.y));
|
||||
}
|
||||
|
||||
CGSize ASCeilSizeValues(CGSize s)
|
||||
{
|
||||
return CGSizeMake(ASCeilPixelValue(s.width), ASCeilPixelValue(s.height));
|
||||
}
|
||||
|
||||
// With 3x devices layouts will often to compute to pixel bounds but
|
||||
// include garbage values beyond the precision of a float/double.
|
||||
// This garbage can result in a pixel value being rounded up when it isn't
|
||||
// necessary.
|
||||
//
|
||||
// For example, imagine a layout that comes back with a height of 100.666666666669
|
||||
// for a 3x device:
|
||||
// 100.666666666669 * 3 = 302.00000000000699
|
||||
// ceil(302.00000000000699) = 303
|
||||
// 303/3 = 101
|
||||
//
|
||||
// If we use FLT_EPSILON to get rid of the garbage at the end of the value,
|
||||
// things work as expected:
|
||||
// (100.666666666669 - FLT_EPSILON) * 3 = 301.99999964237912
|
||||
// ceil(301.99999964237912) = 302
|
||||
// 302/3 = 100.666666666
|
||||
//
|
||||
// For even more conversation around this, see:
|
||||
// https://github.com/TextureGroup/Texture/issues/838
|
||||
CGFloat ASCeilPixelValue(CGFloat f)
|
||||
{
|
||||
CGFloat scale = ASScreenScale();
|
||||
return ceil((f - FLT_EPSILON) * scale) / scale;
|
||||
}
|
||||
|
||||
CGFloat ASRoundPixelValue(CGFloat f)
|
||||
{
|
||||
CGFloat scale = ASScreenScale();
|
||||
return round(f * scale) / scale;
|
||||
}
|
||||
|
||||
@implementation NSIndexPath (ASInverseComparison)
|
||||
|
||||
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath
|
||||
{
|
||||
return [otherIndexPath compare:self];
|
||||
}
|
||||
|
||||
@end
|
378
submodules/AsyncDisplayKit/Source/ASLayout.mm
Normal file
378
submodules/AsyncDisplayKit/Source/ASLayout.mm
Normal file
@ -0,0 +1,378 @@
|
||||
//
|
||||
// ASLayout.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
#import <atomic>
|
||||
#import <queue>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import "ASLayoutSpecUtilities.h"
|
||||
#import "ASLayoutSpec+Subclasses.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
NSString *const ASThreadDictMaxConstraintSizeKey = @"kASThreadDictMaxConstraintSizeKey";
|
||||
|
||||
CGPoint const ASPointNull = {NAN, NAN};
|
||||
|
||||
BOOL ASPointIsNull(CGPoint point)
|
||||
{
|
||||
return isnan(point.x) && isnan(point.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an defined number of " |" indent blocks for the recursive description.
|
||||
*/
|
||||
ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString * descriptionIndents(NSUInteger indents)
|
||||
{
|
||||
NSMutableString *description = [NSMutableString string];
|
||||
for (NSUInteger i = 0; i < indents; i++) {
|
||||
[description appendString:@" |"];
|
||||
}
|
||||
if (indents > 0) {
|
||||
[description appendString:@" "];
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutIsDisplayNodeType(ASLayout *layout)
|
||||
{
|
||||
return layout.type == ASLayoutElementTypeDisplayNode;
|
||||
}
|
||||
|
||||
@interface ASLayout () <ASDescriptionProvider>
|
||||
{
|
||||
ASLayoutElementType _layoutElementType;
|
||||
std::atomic_bool _retainSublayoutElements;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation ASLayout
|
||||
|
||||
@dynamic frame, type;
|
||||
|
||||
static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(NO);
|
||||
|
||||
+ (void)setShouldRetainSublayoutLayoutElements:(BOOL)shouldRetain
|
||||
{
|
||||
static_retainsSublayoutLayoutElements.store(shouldRetain);
|
||||
}
|
||||
|
||||
+ (BOOL)shouldRetainSublayoutLayoutElements
|
||||
{
|
||||
return static_retainsSublayoutLayoutElements.load();
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
size:(CGSize)size
|
||||
position:(CGPoint)position
|
||||
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts
|
||||
{
|
||||
NSParameterAssert(layoutElement);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
|
||||
for (ASLayout *sublayout in sublayouts) {
|
||||
ASDisplayNodeAssert(ASPointIsNull(sublayout.position) == NO, @"Invalid position is not allowed in sublayout.");
|
||||
}
|
||||
#endif
|
||||
|
||||
_layoutElement = layoutElement;
|
||||
|
||||
// Read this now to avoid @c weak overhead later.
|
||||
_layoutElementType = layoutElement.layoutElementType;
|
||||
|
||||
if (!ASIsCGSizeValidForSize(size)) {
|
||||
//ASDisplayNodeFailAssert(@"layoutSize is invalid and unsafe to provide to Core Animation! Release configurations will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutElement);
|
||||
size = CGSizeZero;
|
||||
} else {
|
||||
size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height));
|
||||
}
|
||||
_size = size;
|
||||
|
||||
if (ASPointIsNull(position) == NO) {
|
||||
_position = ASCeilPointValues(position);
|
||||
} else {
|
||||
_position = position;
|
||||
}
|
||||
|
||||
_sublayouts = [sublayouts copy] ?: @[];
|
||||
|
||||
if ([ASLayout shouldRetainSublayoutLayoutElements]) {
|
||||
[self retainSublayoutElements];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Class Constructors
|
||||
|
||||
+ (instancetype)layoutWithLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
size:(CGSize)size
|
||||
position:(CGPoint)position
|
||||
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[self alloc] initWithLayoutElement:layoutElement
|
||||
size:size
|
||||
position:position
|
||||
sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
+ (instancetype)layoutWithLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
size:(CGSize)size
|
||||
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts NS_RETURNS_RETAINED
|
||||
{
|
||||
return [self layoutWithLayoutElement:layoutElement
|
||||
size:size
|
||||
position:ASPointNull
|
||||
sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
+ (instancetype)layoutWithLayoutElement:(id<ASLayoutElement>)layoutElement size:(CGSize)size NS_RETURNS_RETAINED
|
||||
{
|
||||
return [self layoutWithLayoutElement:layoutElement
|
||||
size:size
|
||||
position:ASPointNull
|
||||
sublayouts:nil];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_retainSublayoutElements.load()) {
|
||||
for (ASLayout *sublayout in _sublayouts) {
|
||||
// We retained this, so there's no risk of it deallocating on us.
|
||||
if (CFTypeRef cfElement = (__bridge CFTypeRef)sublayout->_layoutElement) {
|
||||
CFRelease(cfElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Sublayout Elements Caching
|
||||
|
||||
- (void)retainSublayoutElements
|
||||
{
|
||||
if (_retainSublayoutElements.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in _sublayouts) {
|
||||
// CFBridgingRetain atomically casts and retains. We need the atomicity.
|
||||
CFBridgingRetain(sublayout->_layoutElement);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Layout Flattening
|
||||
|
||||
- (BOOL)isFlattened
|
||||
{
|
||||
// A layout is flattened if its position is null, and all of its sublayouts are of type displaynode with no sublayouts.
|
||||
if (!ASPointIsNull(_position)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in _sublayouts) {
|
||||
if (ASLayoutIsDisplayNodeType(sublayout) == NO || sublayout->_sublayouts.count > 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED
|
||||
{
|
||||
if ([self isFlattened]) {
|
||||
// All flattened layouts must retain sublayout elements until they are applied.
|
||||
[self retainSublayoutElements];
|
||||
return self;
|
||||
}
|
||||
|
||||
struct Context {
|
||||
unowned ASLayout *layout;
|
||||
CGPoint absolutePosition;
|
||||
};
|
||||
|
||||
// Queue used to keep track of sublayouts while traversing this layout in a DFS fashion.
|
||||
std::deque<Context> queue;
|
||||
for (ASLayout *sublayout in _sublayouts) {
|
||||
queue.push_back({sublayout, sublayout.position});
|
||||
}
|
||||
|
||||
std::vector<ASLayout *> flattenedSublayouts;
|
||||
|
||||
while (!queue.empty()) {
|
||||
const Context context = std::move(queue.front());
|
||||
queue.pop_front();
|
||||
|
||||
unowned ASLayout *layout = context.layout;
|
||||
// Direct ivar access to avoid retain/release, use existing +1.
|
||||
const NSUInteger sublayoutsCount = layout->_sublayouts.count;
|
||||
const CGPoint absolutePosition = context.absolutePosition;
|
||||
|
||||
if (ASLayoutIsDisplayNodeType(layout)) {
|
||||
if (sublayoutsCount > 0 || CGPointEqualToPoint(ASCeilPointValues(absolutePosition), layout.position) == NO) {
|
||||
// Only create a new layout if the existing one can't be reused, which means it has either some sublayouts or an invalid absolute position.
|
||||
const auto newLayout = [ASLayout layoutWithLayoutElement:layout->_layoutElement
|
||||
size:layout.size
|
||||
position:absolutePosition
|
||||
sublayouts:@[]];
|
||||
flattenedSublayouts.push_back(newLayout);
|
||||
} else {
|
||||
flattenedSublayouts.push_back(layout);
|
||||
}
|
||||
} else if (sublayoutsCount > 0) {
|
||||
// Fast-reverse-enumerate the sublayouts array by copying it into a C-array and push_front'ing each into the queue.
|
||||
unowned ASLayout *rawSublayouts[sublayoutsCount];
|
||||
[layout->_sublayouts getObjects:rawSublayouts range:NSMakeRange(0, sublayoutsCount)];
|
||||
for (NSInteger i = sublayoutsCount - 1; i >= 0; i--) {
|
||||
queue.push_front({rawSublayouts[i], absolutePosition + rawSublayouts[i].position});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *array = [NSArray arrayByTransferring:flattenedSublayouts.data() count:flattenedSublayouts.size()];
|
||||
// flattenedSublayouts is now all nils.
|
||||
|
||||
ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:array];
|
||||
// All flattened layouts must retain sublayout elements until they are applied.
|
||||
[layout retainSublayoutElements];
|
||||
return layout;
|
||||
}
|
||||
|
||||
#pragma mark - Equality Checking
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) return YES;
|
||||
|
||||
ASLayout *layout = ASDynamicCast(object, ASLayout);
|
||||
if (layout == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!CGSizeEqualToSize(_size, layout.size)) return NO;
|
||||
|
||||
if (!((ASPointIsNull(self.position) && ASPointIsNull(layout.position))
|
||||
|| CGPointEqualToPoint(self.position, layout.position))) return NO;
|
||||
if (_layoutElement != layout.layoutElement) return NO;
|
||||
|
||||
if (!ASObjectIsEqual(_sublayouts, layout.sublayouts)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Accessors
|
||||
|
||||
- (ASLayoutElementType)type
|
||||
{
|
||||
return _layoutElementType;
|
||||
}
|
||||
|
||||
- (CGRect)frameForElement:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
for (ASLayout *l in _sublayouts) {
|
||||
if (l->_layoutElement == layoutElement) {
|
||||
return l.frame;
|
||||
}
|
||||
}
|
||||
return CGRectNull;
|
||||
}
|
||||
|
||||
- (CGRect)frame
|
||||
{
|
||||
CGRect subnodeFrame = CGRectZero;
|
||||
CGPoint adjustedOrigin = _position;
|
||||
if (isfinite(adjustedOrigin.x) == NO) {
|
||||
ASDisplayNodeAssert(0, @"Layout has an invalid position");
|
||||
adjustedOrigin.x = 0;
|
||||
}
|
||||
if (isfinite(adjustedOrigin.y) == NO) {
|
||||
ASDisplayNodeAssert(0, @"Layout has an invalid position");
|
||||
adjustedOrigin.y = 0;
|
||||
}
|
||||
subnodeFrame.origin = adjustedOrigin;
|
||||
|
||||
CGSize adjustedSize = _size;
|
||||
if (isfinite(adjustedSize.width) == NO) {
|
||||
ASDisplayNodeAssert(0, @"Layout has an invalid size");
|
||||
adjustedSize.width = 0;
|
||||
}
|
||||
if (isfinite(adjustedSize.height) == NO) {
|
||||
ASDisplayNodeAssert(0, @"Layout has an invalid position");
|
||||
adjustedSize.height = 0;
|
||||
}
|
||||
subnodeFrame.size = adjustedSize;
|
||||
|
||||
return subnodeFrame;
|
||||
}
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSMutableArray <NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
[result addObject:@{ @"size" : [NSValue valueWithCGSize:self.size] }];
|
||||
|
||||
if (id<ASLayoutElement> layoutElement = self.layoutElement) {
|
||||
[result addObject:@{ @"layoutElement" : layoutElement }];
|
||||
}
|
||||
|
||||
const auto pos = self.position;
|
||||
if (!ASPointIsNull(pos)) {
|
||||
[result addObject:@{ @"position" : [NSValue valueWithCGPoint:pos] }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, [self propertiesForDescription]);
|
||||
}
|
||||
|
||||
- (NSString *)recursiveDescription
|
||||
{
|
||||
return [self _recursiveDescriptionForLayout:self level:0];
|
||||
}
|
||||
|
||||
- (NSString *)_recursiveDescriptionForLayout:(ASLayout *)layout level:(NSUInteger)level
|
||||
{
|
||||
NSMutableString *description = [NSMutableString string];
|
||||
[description appendString:descriptionIndents(level)];
|
||||
[description appendString:[layout description]];
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
[description appendString:@"\n"];
|
||||
[description appendString:[self _recursiveDescriptionForLayout:sublayout level:level + 1]];
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
ASLayout *ASCalculateLayout(id<ASLayoutElement> layoutElement, const ASSizeRange sizeRange, const CGSize parentSize)
|
||||
{
|
||||
NSCParameterAssert(layoutElement != nil);
|
||||
|
||||
return [layoutElement layoutThatFits:sizeRange parentSize:parentSize];
|
||||
}
|
||||
|
||||
ASLayout *ASCalculateRootLayout(id<ASLayoutElement> rootLayoutElement, const ASSizeRange sizeRange)
|
||||
{
|
||||
ASLayout *layout = ASCalculateLayout(rootLayoutElement, sizeRange, sizeRange.max);
|
||||
// Here could specific verfication happen
|
||||
return layout;
|
||||
}
|
843
submodules/AsyncDisplayKit/Source/ASLayoutElement.mm
Normal file
843
submodules/AsyncDisplayKit/Source/ASLayoutElement.mm
Normal file
@ -0,0 +1,843 @@
|
||||
//
|
||||
// ASLayoutElement.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#import <atomic>
|
||||
#include <pthread.h>
|
||||
|
||||
using AS::MutexLocker;
|
||||
|
||||
#if YOGA
|
||||
#import YOGA_HEADER_PATH
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#endif
|
||||
|
||||
#pragma mark - ASLayoutElementContext
|
||||
|
||||
@implementation ASLayoutElementContext
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_transitionID = ASLayoutElementContextDefaultTransitionID;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
CGFloat const ASLayoutElementParentDimensionUndefined = NAN;
|
||||
CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined};
|
||||
|
||||
int32_t const ASLayoutElementContextInvalidTransitionID = 0;
|
||||
int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1;
|
||||
|
||||
#if AS_TLS_AVAILABLE
|
||||
|
||||
static _Thread_local __unsafe_unretained ASLayoutElementContext *tls_context;
|
||||
|
||||
void ASLayoutElementPushContext(ASLayoutElementContext *context)
|
||||
{
|
||||
// NOTE: It would be easy to support nested contexts – just use an NSMutableArray here.
|
||||
ASDisplayNodeCAssertNil(tls_context, @"Nested ASLayoutElementContexts aren't supported.");
|
||||
|
||||
tls_context = (__bridge ASLayoutElementContext *)(__bridge_retained CFTypeRef)context;
|
||||
}
|
||||
|
||||
ASLayoutElementContext *ASLayoutElementGetCurrentContext()
|
||||
{
|
||||
// Don't retain here. Caller will retain if it wants to!
|
||||
return tls_context;
|
||||
}
|
||||
|
||||
void ASLayoutElementPopContext()
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(tls_context, @"Attempt to pop context when there wasn't a context!");
|
||||
CFRelease((__bridge CFTypeRef)tls_context);
|
||||
tls_context = nil;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static pthread_key_t ASLayoutElementContextKey() {
|
||||
static pthread_key_t k;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
pthread_key_create(&k, NULL);
|
||||
});
|
||||
return k;
|
||||
}
|
||||
void ASLayoutElementPushContext(ASLayoutElementContext *context)
|
||||
{
|
||||
// NOTE: It would be easy to support nested contexts – just use an NSMutableArray here.
|
||||
ASDisplayNodeCAssertNil(pthread_getspecific(ASLayoutElementContextKey()), @"Nested ASLayoutElementContexts aren't supported.");
|
||||
|
||||
const auto cfCtx = (__bridge_retained CFTypeRef)context;
|
||||
pthread_setspecific(ASLayoutElementContextKey(), cfCtx);
|
||||
}
|
||||
|
||||
ASLayoutElementContext *ASLayoutElementGetCurrentContext()
|
||||
{
|
||||
// Don't retain here. Caller will retain if it wants to!
|
||||
const auto ctxPtr = pthread_getspecific(ASLayoutElementContextKey());
|
||||
return (__bridge ASLayoutElementContext *)ctxPtr;
|
||||
}
|
||||
|
||||
void ASLayoutElementPopContext()
|
||||
{
|
||||
const auto ctx = (CFTypeRef)pthread_getspecific(ASLayoutElementContextKey());
|
||||
ASDisplayNodeCAssertNotNil(ctx, @"Attempt to pop context when there wasn't a context!");
|
||||
CFRelease(ctx);
|
||||
pthread_setspecific(ASLayoutElementContextKey(), NULL);
|
||||
}
|
||||
|
||||
#endif // AS_TLS_AVAILABLE
|
||||
|
||||
#pragma mark - ASLayoutElementStyle
|
||||
|
||||
NSString * const ASLayoutElementStyleWidthProperty = @"ASLayoutElementStyleWidthProperty";
|
||||
NSString * const ASLayoutElementStyleMinWidthProperty = @"ASLayoutElementStyleMinWidthProperty";
|
||||
NSString * const ASLayoutElementStyleMaxWidthProperty = @"ASLayoutElementStyleMaxWidthProperty";
|
||||
|
||||
NSString * const ASLayoutElementStyleHeightProperty = @"ASLayoutElementStyleHeightProperty";
|
||||
NSString * const ASLayoutElementStyleMinHeightProperty = @"ASLayoutElementStyleMinHeightProperty";
|
||||
NSString * const ASLayoutElementStyleMaxHeightProperty = @"ASLayoutElementStyleMaxHeightProperty";
|
||||
|
||||
NSString * const ASLayoutElementStyleSpacingBeforeProperty = @"ASLayoutElementStyleSpacingBeforeProperty";
|
||||
NSString * const ASLayoutElementStyleSpacingAfterProperty = @"ASLayoutElementStyleSpacingAfterProperty";
|
||||
NSString * const ASLayoutElementStyleFlexGrowProperty = @"ASLayoutElementStyleFlexGrowProperty";
|
||||
NSString * const ASLayoutElementStyleFlexShrinkProperty = @"ASLayoutElementStyleFlexShrinkProperty";
|
||||
NSString * const ASLayoutElementStyleFlexBasisProperty = @"ASLayoutElementStyleFlexBasisProperty";
|
||||
NSString * const ASLayoutElementStyleAlignSelfProperty = @"ASLayoutElementStyleAlignSelfProperty";
|
||||
NSString * const ASLayoutElementStyleAscenderProperty = @"ASLayoutElementStyleAscenderProperty";
|
||||
NSString * const ASLayoutElementStyleDescenderProperty = @"ASLayoutElementStyleDescenderProperty";
|
||||
|
||||
NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty";
|
||||
|
||||
#if YOGA
|
||||
NSString * const ASYogaFlexWrapProperty = @"ASLayoutElementStyleLayoutFlexWrapProperty";
|
||||
NSString * const ASYogaFlexDirectionProperty = @"ASYogaFlexDirectionProperty";
|
||||
NSString * const ASYogaDirectionProperty = @"ASYogaDirectionProperty";
|
||||
NSString * const ASYogaSpacingProperty = @"ASYogaSpacingProperty";
|
||||
NSString * const ASYogaJustifyContentProperty = @"ASYogaJustifyContentProperty";
|
||||
NSString * const ASYogaAlignItemsProperty = @"ASYogaAlignItemsProperty";
|
||||
NSString * const ASYogaPositionTypeProperty = @"ASYogaPositionTypeProperty";
|
||||
NSString * const ASYogaPositionProperty = @"ASYogaPositionProperty";
|
||||
NSString * const ASYogaMarginProperty = @"ASYogaMarginProperty";
|
||||
NSString * const ASYogaPaddingProperty = @"ASYogaPaddingProperty";
|
||||
NSString * const ASYogaBorderProperty = @"ASYogaBorderProperty";
|
||||
NSString * const ASYogaAspectRatioProperty = @"ASYogaAspectRatioProperty";
|
||||
#endif
|
||||
|
||||
#define ASLayoutElementStyleSetSizeWithScope(x) \
|
||||
__instanceLock__.lock(); \
|
||||
ASLayoutElementSize newSize = _size.load(); \
|
||||
{ x } \
|
||||
_size.store(newSize); \
|
||||
__instanceLock__.unlock();
|
||||
|
||||
#define ASLayoutElementStyleCallDelegate(propertyName)\
|
||||
do {\
|
||||
[self propertyDidChange:propertyName];\
|
||||
[_delegate style:self propertyDidChange:propertyName];\
|
||||
} while(0)
|
||||
|
||||
@implementation ASLayoutElementStyle {
|
||||
AS::RecursiveMutex __instanceLock__;
|
||||
ASLayoutElementStyleExtensions _extensions;
|
||||
|
||||
std::atomic<ASLayoutElementSize> _size;
|
||||
std::atomic<CGFloat> _spacingBefore;
|
||||
std::atomic<CGFloat> _spacingAfter;
|
||||
std::atomic<CGFloat> _flexGrow;
|
||||
std::atomic<CGFloat> _flexShrink;
|
||||
std::atomic<ASDimension> _flexBasis;
|
||||
std::atomic<ASStackLayoutAlignSelf> _alignSelf;
|
||||
std::atomic<CGFloat> _ascender;
|
||||
std::atomic<CGFloat> _descender;
|
||||
std::atomic<CGPoint> _layoutPosition;
|
||||
|
||||
#if YOGA
|
||||
YGNodeRef _yogaNode;
|
||||
std::atomic<YGWrap> _flexWrap;
|
||||
std::atomic<ASStackLayoutDirection> _flexDirection;
|
||||
std::atomic<YGDirection> _direction;
|
||||
std::atomic<ASStackLayoutJustifyContent> _justifyContent;
|
||||
std::atomic<ASStackLayoutAlignItems> _alignItems;
|
||||
std::atomic<YGPositionType> _positionType;
|
||||
std::atomic<ASEdgeInsets> _position;
|
||||
std::atomic<ASEdgeInsets> _margin;
|
||||
std::atomic<ASEdgeInsets> _padding;
|
||||
std::atomic<ASEdgeInsets> _border;
|
||||
std::atomic<CGFloat> _aspectRatio;
|
||||
ASStackLayoutAlignItems _parentAlignStyle;
|
||||
#endif
|
||||
}
|
||||
|
||||
@dynamic width, height, minWidth, maxWidth, minHeight, maxHeight;
|
||||
@dynamic preferredSize, minSize, maxSize, preferredLayoutSize, minLayoutSize, maxLayoutSize;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithDelegate:(id<ASLayoutElementStyleDelegate>)delegate
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_delegate = delegate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_size = ASLayoutElementSizeMake();
|
||||
#if YOGA
|
||||
_parentAlignStyle = ASStackLayoutAlignItemsNotSet;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
ASSynthesizeLockingMethodsWithMutex(__instanceLock__)
|
||||
|
||||
#pragma mark - ASLayoutElementStyleSize
|
||||
|
||||
- (ASLayoutElementSize)size
|
||||
{
|
||||
return _size.load();
|
||||
}
|
||||
|
||||
- (void)setSize:(ASLayoutElementSize)size
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize = size;
|
||||
});
|
||||
// No CallDelegate method as ASLayoutElementSize is currently internal.
|
||||
}
|
||||
|
||||
#pragma mark - ASLayoutElementStyleSizeForwarding
|
||||
|
||||
- (ASDimension)width
|
||||
{
|
||||
return _size.load().width;
|
||||
}
|
||||
|
||||
- (void)setWidth:(ASDimension)width
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.width = width;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)height
|
||||
{
|
||||
return _size.load().height;
|
||||
}
|
||||
|
||||
- (void)setHeight:(ASDimension)height
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.height = height;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)minWidth
|
||||
{
|
||||
return _size.load().minWidth;
|
||||
}
|
||||
|
||||
- (void)setMinWidth:(ASDimension)minWidth
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.minWidth = minWidth;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)maxWidth
|
||||
{
|
||||
return _size.load().maxWidth;
|
||||
}
|
||||
|
||||
- (void)setMaxWidth:(ASDimension)maxWidth
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.maxWidth = maxWidth;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)minHeight
|
||||
{
|
||||
return _size.load().minHeight;
|
||||
}
|
||||
|
||||
- (void)setMinHeight:(ASDimension)minHeight
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.minHeight = minHeight;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)maxHeight
|
||||
{
|
||||
return _size.load().maxHeight;
|
||||
}
|
||||
|
||||
- (void)setMaxHeight:(ASDimension)maxHeight
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.maxHeight = maxHeight;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - ASLayoutElementStyleSizeHelpers
|
||||
|
||||
- (void)setPreferredSize:(CGSize)preferredSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.width = ASDimensionMakeWithPoints(preferredSize.width);
|
||||
newSize.height = ASDimensionMakeWithPoints(preferredSize.height);
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty);
|
||||
}
|
||||
|
||||
- (CGSize)preferredSize
|
||||
{
|
||||
ASLayoutElementSize size = _size.load();
|
||||
if (size.width.unit == ASDimensionUnitFraction) {
|
||||
NSCAssert(NO, @"Cannot get preferredSize of element with fractional width. Width: %@.", NSStringFromASDimension(size.width));
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
if (size.height.unit == ASDimensionUnitFraction) {
|
||||
NSCAssert(NO, @"Cannot get preferredSize of element with fractional height. Height: %@.", NSStringFromASDimension(size.height));
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
return CGSizeMake(size.width.value, size.height.value);
|
||||
}
|
||||
|
||||
- (void)setMinSize:(CGSize)minSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.minWidth = ASDimensionMakeWithPoints(minSize.width);
|
||||
newSize.minHeight = ASDimensionMakeWithPoints(minSize.height);
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty);
|
||||
}
|
||||
|
||||
- (void)setMaxSize:(CGSize)maxSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.maxWidth = ASDimensionMakeWithPoints(maxSize.width);
|
||||
newSize.maxHeight = ASDimensionMakeWithPoints(maxSize.height);
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty);
|
||||
}
|
||||
|
||||
- (ASLayoutSize)preferredLayoutSize
|
||||
{
|
||||
ASLayoutElementSize size = _size.load();
|
||||
return ASLayoutSizeMake(size.width, size.height);
|
||||
}
|
||||
|
||||
- (void)setPreferredLayoutSize:(ASLayoutSize)preferredLayoutSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.width = preferredLayoutSize.width;
|
||||
newSize.height = preferredLayoutSize.height;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty);
|
||||
}
|
||||
|
||||
- (ASLayoutSize)minLayoutSize
|
||||
{
|
||||
ASLayoutElementSize size = _size.load();
|
||||
return ASLayoutSizeMake(size.minWidth, size.minHeight);
|
||||
}
|
||||
|
||||
- (void)setMinLayoutSize:(ASLayoutSize)minLayoutSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.minWidth = minLayoutSize.width;
|
||||
newSize.minHeight = minLayoutSize.height;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty);
|
||||
}
|
||||
|
||||
- (ASLayoutSize)maxLayoutSize
|
||||
{
|
||||
ASLayoutElementSize size = _size.load();
|
||||
return ASLayoutSizeMake(size.maxWidth, size.maxHeight);
|
||||
}
|
||||
|
||||
- (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize
|
||||
{
|
||||
ASLayoutElementStyleSetSizeWithScope({
|
||||
newSize.maxWidth = maxLayoutSize.width;
|
||||
newSize.maxHeight = maxLayoutSize.height;
|
||||
});
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty);
|
||||
}
|
||||
|
||||
#pragma mark - ASStackLayoutElement
|
||||
|
||||
- (void)setSpacingBefore:(CGFloat)spacingBefore
|
||||
{
|
||||
_spacingBefore.store(spacingBefore);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingBeforeProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)spacingBefore
|
||||
{
|
||||
return _spacingBefore.load();
|
||||
}
|
||||
|
||||
- (void)setSpacingAfter:(CGFloat)spacingAfter
|
||||
{
|
||||
_spacingAfter.store(spacingAfter);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingAfterProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)spacingAfter
|
||||
{
|
||||
return _spacingAfter.load();
|
||||
}
|
||||
|
||||
- (void)setFlexGrow:(CGFloat)flexGrow
|
||||
{
|
||||
_flexGrow.store(flexGrow);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexGrowProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)flexGrow
|
||||
{
|
||||
return _flexGrow.load();
|
||||
}
|
||||
|
||||
- (void)setFlexShrink:(CGFloat)flexShrink
|
||||
{
|
||||
_flexShrink.store(flexShrink);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexShrinkProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)flexShrink
|
||||
{
|
||||
return _flexShrink.load();
|
||||
}
|
||||
|
||||
- (void)setFlexBasis:(ASDimension)flexBasis
|
||||
{
|
||||
_flexBasis.store(flexBasis);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexBasisProperty);
|
||||
}
|
||||
|
||||
- (ASDimension)flexBasis
|
||||
{
|
||||
return _flexBasis.load();
|
||||
}
|
||||
|
||||
- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf
|
||||
{
|
||||
_alignSelf.store(alignSelf);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAlignSelfProperty);
|
||||
}
|
||||
|
||||
- (ASStackLayoutAlignSelf)alignSelf
|
||||
{
|
||||
return _alignSelf.load();
|
||||
}
|
||||
|
||||
- (void)setAscender:(CGFloat)ascender
|
||||
{
|
||||
_ascender.store(ascender);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAscenderProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)ascender
|
||||
{
|
||||
return _ascender.load();
|
||||
}
|
||||
|
||||
- (void)setDescender:(CGFloat)descender
|
||||
{
|
||||
_descender.store(descender);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleDescenderProperty);
|
||||
}
|
||||
|
||||
- (CGFloat)descender
|
||||
{
|
||||
return _descender.load();
|
||||
}
|
||||
|
||||
#pragma mark - ASAbsoluteLayoutElement
|
||||
|
||||
- (void)setLayoutPosition:(CGPoint)layoutPosition
|
||||
{
|
||||
_layoutPosition.store(layoutPosition);
|
||||
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleLayoutPositionProperty);
|
||||
}
|
||||
|
||||
- (CGPoint)layoutPosition
|
||||
{
|
||||
return _layoutPosition.load();
|
||||
}
|
||||
|
||||
#pragma mark - Extensions
|
||||
|
||||
- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
_extensions.boolExtensions[idx] = value;
|
||||
}
|
||||
|
||||
- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
return _extensions.boolExtensions[idx];
|
||||
}
|
||||
|
||||
- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
_extensions.integerExtensions[idx] = value;
|
||||
}
|
||||
|
||||
- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
return _extensions.integerExtensions[idx];
|
||||
}
|
||||
|
||||
- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
_extensions.edgeInsetsExtensions[idx] = value;
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx
|
||||
{
|
||||
NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space");
|
||||
|
||||
MutexLocker l(__instanceLock__);
|
||||
return _extensions.edgeInsetsExtensions[idx];
|
||||
}
|
||||
|
||||
#pragma mark - Debugging
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, [self propertiesForDescription]);
|
||||
}
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
|
||||
|
||||
if ((self.minLayoutSize.width.unit != ASDimensionUnitAuto ||
|
||||
self.minLayoutSize.height.unit != ASDimensionUnitAuto)) {
|
||||
[result addObject:@{ @"minLayoutSize" : NSStringFromASLayoutSize(self.minLayoutSize) }];
|
||||
}
|
||||
|
||||
if ((self.preferredLayoutSize.width.unit != ASDimensionUnitAuto ||
|
||||
self.preferredLayoutSize.height.unit != ASDimensionUnitAuto)) {
|
||||
[result addObject:@{ @"preferredSize" : NSStringFromASLayoutSize(self.preferredLayoutSize) }];
|
||||
}
|
||||
|
||||
if ((self.maxLayoutSize.width.unit != ASDimensionUnitAuto ||
|
||||
self.maxLayoutSize.height.unit != ASDimensionUnitAuto)) {
|
||||
[result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }];
|
||||
}
|
||||
|
||||
if (self.alignSelf != ASStackLayoutAlignSelfAuto) {
|
||||
[result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto",
|
||||
@"ASStackLayoutAlignSelfStart",
|
||||
@"ASStackLayoutAlignSelfEnd",
|
||||
@"ASStackLayoutAlignSelfCenter",
|
||||
@"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }];
|
||||
}
|
||||
|
||||
if (self.ascender != 0) {
|
||||
[result addObject:@{ @"ascender" : @(self.ascender) }];
|
||||
}
|
||||
|
||||
if (self.descender != 0) {
|
||||
[result addObject:@{ @"descender" : @(self.descender) }];
|
||||
}
|
||||
|
||||
if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) {
|
||||
[result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }];
|
||||
}
|
||||
|
||||
if (self.flexGrow != 0) {
|
||||
[result addObject:@{ @"flexGrow" : @(self.flexGrow) }];
|
||||
}
|
||||
|
||||
if (self.flexShrink != 0) {
|
||||
[result addObject:@{ @"flexShrink" : @(self.flexShrink) }];
|
||||
}
|
||||
|
||||
if (self.spacingAfter != 0) {
|
||||
[result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }];
|
||||
}
|
||||
|
||||
if (self.spacingBefore != 0) {
|
||||
[result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }];
|
||||
}
|
||||
|
||||
if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) {
|
||||
[result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)propertyDidChange:(NSString *)propertyName
|
||||
{
|
||||
#if YOGA
|
||||
/* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT
|
||||
void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow);
|
||||
void YGNodeStyleSetFlex(YGNodeRef node, float flex);
|
||||
*/
|
||||
|
||||
if (_yogaNode == NULL) {
|
||||
return;
|
||||
}
|
||||
// Because the NSStrings used to identify each property are const, use efficient pointer comparison.
|
||||
if (propertyName == ASLayoutElementStyleWidthProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, Width, self.width);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleMinWidthProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinWidth, self.minWidth);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleMaxWidthProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxWidth, self.maxWidth);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleHeightProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, Height, self.height);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleMinHeightProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinHeight, self.minHeight);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleMaxHeightProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxHeight, self.maxHeight);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleFlexGrowProperty) {
|
||||
YGNodeStyleSetFlexGrow(_yogaNode, self.flexGrow);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleFlexShrinkProperty) {
|
||||
YGNodeStyleSetFlexShrink(_yogaNode, self.flexShrink);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleFlexBasisProperty) {
|
||||
YGNODE_STYLE_SET_DIMENSION(_yogaNode, FlexBasis, self.flexBasis);
|
||||
}
|
||||
else if (propertyName == ASLayoutElementStyleAlignSelfProperty) {
|
||||
YGNodeStyleSetAlignSelf(_yogaNode, yogaAlignSelf(self.alignSelf));
|
||||
}
|
||||
else if (propertyName == ASYogaFlexWrapProperty) {
|
||||
YGNodeStyleSetFlexWrap(_yogaNode, self.flexWrap);
|
||||
}
|
||||
else if (propertyName == ASYogaFlexDirectionProperty) {
|
||||
YGNodeStyleSetFlexDirection(_yogaNode, yogaFlexDirection(self.flexDirection));
|
||||
}
|
||||
else if (propertyName == ASYogaDirectionProperty) {
|
||||
YGNodeStyleSetDirection(_yogaNode, self.direction);
|
||||
}
|
||||
else if (propertyName == ASYogaJustifyContentProperty) {
|
||||
YGNodeStyleSetJustifyContent(_yogaNode, yogaJustifyContent(self.justifyContent));
|
||||
}
|
||||
else if (propertyName == ASYogaAlignItemsProperty) {
|
||||
ASStackLayoutAlignItems alignItems = self.alignItems;
|
||||
if (alignItems != ASStackLayoutAlignItemsNotSet) {
|
||||
YGNodeStyleSetAlignItems(_yogaNode, yogaAlignItems(alignItems));
|
||||
}
|
||||
}
|
||||
else if (propertyName == ASYogaPositionTypeProperty) {
|
||||
YGNodeStyleSetPositionType(_yogaNode, self.positionType);
|
||||
}
|
||||
else if (propertyName == ASYogaPositionProperty) {
|
||||
ASEdgeInsets position = self.position;
|
||||
YGEdge edge = YGEdgeLeft;
|
||||
for (int i = 0; i < YGEdgeAll + 1; ++i) {
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge);
|
||||
edge = (YGEdge)(edge + 1);
|
||||
}
|
||||
}
|
||||
else if (propertyName == ASYogaMarginProperty) {
|
||||
ASEdgeInsets margin = self.margin;
|
||||
YGEdge edge = YGEdgeLeft;
|
||||
for (int i = 0; i < YGEdgeAll + 1; ++i) {
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge);
|
||||
edge = (YGEdge)(edge + 1);
|
||||
}
|
||||
}
|
||||
else if (propertyName == ASYogaPaddingProperty) {
|
||||
ASEdgeInsets padding = self.padding;
|
||||
YGEdge edge = YGEdgeLeft;
|
||||
for (int i = 0; i < YGEdgeAll + 1; ++i) {
|
||||
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge);
|
||||
edge = (YGEdge)(edge + 1);
|
||||
}
|
||||
}
|
||||
else if (propertyName == ASYogaBorderProperty) {
|
||||
ASEdgeInsets border = self.border;
|
||||
YGEdge edge = YGEdgeLeft;
|
||||
for (int i = 0; i < YGEdgeAll + 1; ++i) {
|
||||
YGNODE_STYLE_SET_FLOAT_WITH_EDGE(_yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge);
|
||||
edge = (YGEdge)(edge + 1);
|
||||
}
|
||||
}
|
||||
else if (propertyName == ASYogaAspectRatioProperty) {
|
||||
CGFloat aspectRatio = self.aspectRatio;
|
||||
if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) {
|
||||
YGNodeStyleSetAspectRatio(_yogaNode, aspectRatio);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - Yoga Flexbox Properties
|
||||
|
||||
#if YOGA
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[super initialize];
|
||||
YGConfigSetPointScaleFactor(YGConfigGetDefault(), ASScreenScale());
|
||||
// Yoga recommends using Web Defaults for all new projects. This will be enabled for Texture very soon.
|
||||
//YGConfigSetUseWebDefaults(YGConfigGetDefault(), true);
|
||||
}
|
||||
|
||||
- (YGNodeRef)yogaNode
|
||||
{
|
||||
return _yogaNode;
|
||||
}
|
||||
|
||||
- (YGNodeRef)yogaNodeCreateIfNeeded
|
||||
{
|
||||
if (_yogaNode == NULL) {
|
||||
_yogaNode = YGNodeNew();
|
||||
}
|
||||
return _yogaNode;
|
||||
}
|
||||
|
||||
- (void)destroyYogaNode
|
||||
{
|
||||
if (_yogaNode != NULL) {
|
||||
// Release the __bridge_retained Context object.
|
||||
ASLayoutElementYogaUpdateMeasureFunc(_yogaNode, nil);
|
||||
YGNodeFree(_yogaNode);
|
||||
_yogaNode = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self destroyYogaNode];
|
||||
}
|
||||
|
||||
- (YGWrap)flexWrap { return _flexWrap.load(); }
|
||||
- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); }
|
||||
- (YGDirection)direction { return _direction.load(); }
|
||||
- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); }
|
||||
- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); }
|
||||
- (YGPositionType)positionType { return _positionType.load(); }
|
||||
- (ASEdgeInsets)position { return _position.load(); }
|
||||
- (ASEdgeInsets)margin { return _margin.load(); }
|
||||
- (ASEdgeInsets)padding { return _padding.load(); }
|
||||
- (ASEdgeInsets)border { return _border.load(); }
|
||||
- (CGFloat)aspectRatio { return _aspectRatio.load(); }
|
||||
// private (ASLayoutElementStylePrivate.h)
|
||||
- (ASStackLayoutAlignItems)parentAlignStyle {
|
||||
return _parentAlignStyle;
|
||||
}
|
||||
|
||||
- (void)setFlexWrap:(YGWrap)flexWrap {
|
||||
_flexWrap.store(flexWrap);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaFlexWrapProperty);
|
||||
}
|
||||
- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection {
|
||||
_flexDirection.store(flexDirection);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaFlexDirectionProperty);
|
||||
}
|
||||
- (void)setDirection:(YGDirection)direction {
|
||||
_direction.store(direction);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaDirectionProperty);
|
||||
}
|
||||
- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify {
|
||||
_justifyContent.store(justify);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaJustifyContentProperty);
|
||||
}
|
||||
- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems {
|
||||
_alignItems.store(alignItems);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaAlignItemsProperty);
|
||||
}
|
||||
- (void)setPositionType:(YGPositionType)positionType {
|
||||
_positionType.store(positionType);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaPositionTypeProperty);
|
||||
}
|
||||
- (void)setPosition:(ASEdgeInsets)position {
|
||||
_position.store(position);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaPositionProperty);
|
||||
}
|
||||
- (void)setMargin:(ASEdgeInsets)margin {
|
||||
_margin.store(margin);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaMarginProperty);
|
||||
}
|
||||
- (void)setPadding:(ASEdgeInsets)padding {
|
||||
_padding.store(padding);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaPaddingProperty);
|
||||
}
|
||||
- (void)setBorder:(ASEdgeInsets)border {
|
||||
_border.store(border);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaBorderProperty);
|
||||
}
|
||||
- (void)setAspectRatio:(CGFloat)aspectRatio {
|
||||
_aspectRatio.store(aspectRatio);
|
||||
ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty);
|
||||
}
|
||||
// private (ASLayoutElementStylePrivate.h)
|
||||
- (void)setParentAlignStyle:(ASStackLayoutAlignItems)style {
|
||||
_parentAlignStyle = style;
|
||||
}
|
||||
|
||||
#endif /* YOGA */
|
||||
|
||||
@end
|
@ -0,0 +1,31 @@
|
||||
//
|
||||
// ASLayoutElementStylePrivate.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
@interface ASLayoutElementStyle () <ASDescriptionProvider>
|
||||
|
||||
/**
|
||||
* @abstract The object that acts as the delegate of the style.
|
||||
*
|
||||
* @discussion The delegate must adopt the ASLayoutElementStyleDelegate protocol. The delegate is not retained.
|
||||
*/
|
||||
@property (nullable, nonatomic, weak) id<ASLayoutElementStyleDelegate> delegate;
|
||||
|
||||
/**
|
||||
* @abstract A size constraint that should apply to this ASLayoutElement.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASLayoutElementSize size;
|
||||
|
||||
@property (nonatomic, assign) ASStackLayoutAlignItems parentAlignStyle;
|
||||
|
||||
@end
|
16
submodules/AsyncDisplayKit/Source/ASLayoutManager.h
Normal file
16
submodules/AsyncDisplayKit/Source/ASLayoutManager.h
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// ASLayoutManager.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASLayoutManager : NSLayoutManager
|
||||
|
||||
@end
|
42
submodules/AsyncDisplayKit/Source/ASLayoutManager.mm
Normal file
42
submodules/AsyncDisplayKit/Source/ASLayoutManager.mm
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// ASLayoutManager.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutManager.h"
|
||||
|
||||
@implementation ASLayoutManager
|
||||
|
||||
- (void)showCGGlyphs:(const CGGlyph *)glyphs
|
||||
positions:(const CGPoint *)positions
|
||||
count:(NSUInteger)glyphCount
|
||||
font:(UIFont *)font
|
||||
matrix:(CGAffineTransform)textMatrix
|
||||
attributes:(NSDictionary *)attributes
|
||||
inContext:(CGContextRef)graphicsContext
|
||||
{
|
||||
|
||||
// NSLayoutManager has a hard coded internal color for hyperlinks which ignores
|
||||
// NSForegroundColorAttributeName. To get around this, we force the fill color
|
||||
// in the current context to match NSForegroundColorAttributeName.
|
||||
UIColor *foregroundColor = attributes[NSForegroundColorAttributeName];
|
||||
|
||||
if (foregroundColor)
|
||||
{
|
||||
CGContextSetFillColorWithColor(graphicsContext, foregroundColor.CGColor);
|
||||
}
|
||||
|
||||
[super showCGGlyphs:glyphs
|
||||
positions:positions
|
||||
count:glyphCount
|
||||
font:font
|
||||
matrix:textMatrix
|
||||
attributes:attributes
|
||||
inContext:graphicsContext];
|
||||
}
|
||||
|
||||
@end
|
59
submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h
Normal file
59
submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// ASLayoutSpec+Subclasses.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASLayoutElement;
|
||||
|
||||
@interface ASLayoutSpec (Subclassing)
|
||||
|
||||
/**
|
||||
* Adds a child with the given identifier to this layout spec.
|
||||
*
|
||||
* @param child A child to be added.
|
||||
*
|
||||
* @param index An index associated with the child.
|
||||
*
|
||||
* @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the
|
||||
* responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec,
|
||||
* only require a single child.
|
||||
*
|
||||
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
|
||||
* a subclass can use the setChild method to set the "primary" child. It should then use this method
|
||||
* to set any other required children. Ideally a subclass would hide this from the user, and use the
|
||||
* setChild:forIndex: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
|
||||
* property that behind the scenes is calling setChild:forIndex:.
|
||||
*/
|
||||
- (void)setChild:(id<ASLayoutElement>)child atIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
* Returns the child added to this layout spec using the given index.
|
||||
*
|
||||
* @param index An identifier associated with the the child.
|
||||
*/
|
||||
- (nullable id<ASLayoutElement>)childAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASLayout ()
|
||||
|
||||
/**
|
||||
* Position in parent. Default to CGPointNull.
|
||||
*
|
||||
* @discussion When being used as a sublayout, this property must not equal CGPointNull.
|
||||
*/
|
||||
@property (nonatomic) CGPoint position;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
87
submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm
Normal file
87
submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// ASLayoutSpec+Subclasses.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutSpec+Subclasses.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import "ASLayoutSpecPrivate.h"
|
||||
|
||||
#pragma mark - ASNullLayoutSpec
|
||||
|
||||
@interface ASNullLayoutSpec : ASLayoutSpec
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (ASNullLayoutSpec *)null;
|
||||
@end
|
||||
|
||||
@implementation ASNullLayoutSpec : ASLayoutSpec
|
||||
|
||||
+ (ASNullLayoutSpec *)null
|
||||
{
|
||||
static ASNullLayoutSpec *sharedNullLayoutSpec = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedNullLayoutSpec = [[self alloc] init];
|
||||
});
|
||||
return sharedNullLayoutSpec;
|
||||
}
|
||||
|
||||
- (BOOL)isMutable
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return [ASLayout layoutWithLayoutElement:self size:CGSizeZero];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASLayoutSpec (Subclassing)
|
||||
|
||||
@implementation ASLayoutSpec (Subclassing)
|
||||
|
||||
#pragma mark - Child with index
|
||||
|
||||
- (void)setChild:(id<ASLayoutElement>)child atIndex:(NSUInteger)index
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
|
||||
id<ASLayoutElement> layoutElement = child ?: [ASNullLayoutSpec null];
|
||||
|
||||
if (child) {
|
||||
if (_childrenArray.count < index) {
|
||||
// Fill up the array with null objects until the index
|
||||
NSInteger i = _childrenArray.count;
|
||||
while (i < index) {
|
||||
_childrenArray[i] = [ASNullLayoutSpec null];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace object at the given index with the layoutElement
|
||||
_childrenArray[index] = layoutElement;
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)childAtIndex:(NSUInteger)index
|
||||
{
|
||||
id<ASLayoutElement> layoutElement = nil;
|
||||
if (index < _childrenArray.count) {
|
||||
layoutElement = _childrenArray[index];
|
||||
}
|
||||
|
||||
// Null layoutElement should not be accessed
|
||||
ASDisplayNodeAssert(layoutElement != [ASNullLayoutSpec null], @"Access child at index without set a child at that index");
|
||||
|
||||
return layoutElement;
|
||||
}
|
||||
|
||||
@end
|
338
submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm
Normal file
338
submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm
Normal file
@ -0,0 +1,338 @@
|
||||
//
|
||||
// ASLayoutSpec.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import "ASLayoutSpecPrivate.h"
|
||||
|
||||
#import "ASLayoutSpec+Subclasses.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
#import "ASLayoutElementStylePrivate.h"
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <map>
|
||||
#import <vector>
|
||||
|
||||
@implementation ASLayoutSpec
|
||||
|
||||
// Dynamic properties for ASLayoutElements
|
||||
@dynamic layoutElementType;
|
||||
@synthesize debugName = _debugName;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_isMutable = YES;
|
||||
_primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault();
|
||||
_childrenArray = [[NSMutableArray alloc] init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutElementType)layoutElementType
|
||||
{
|
||||
return ASLayoutElementTypeLayoutSpec;
|
||||
}
|
||||
|
||||
- (BOOL)canLayoutAsynchronous
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)implementsLayoutMethod
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Style
|
||||
|
||||
- (ASLayoutElementStyle *)style
|
||||
{
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
if (_style == nil) {
|
||||
_style = [[ASLayoutElementStyle alloc] init];
|
||||
}
|
||||
return _style;
|
||||
}
|
||||
|
||||
- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock
|
||||
{
|
||||
styleBlock(self.style);
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
ASLayoutElementLayoutCalculationDefaults
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min];
|
||||
}
|
||||
|
||||
#pragma mark - Child
|
||||
|
||||
- (void)setChild:(id<ASLayoutElement>)child
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API");
|
||||
|
||||
if (child) {
|
||||
_childrenArray[0] = child;
|
||||
} else {
|
||||
if (_childrenArray.count) {
|
||||
[_childrenArray removeObjectAtIndex:0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)child
|
||||
{
|
||||
ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API");
|
||||
|
||||
return _childrenArray.firstObject;
|
||||
}
|
||||
|
||||
#pragma mark - Children
|
||||
|
||||
- (void)setChildren:(NSArray<id<ASLayoutElement>> *)children
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
|
||||
for (id<ASLayoutElement> child in children) {
|
||||
ASDisplayNodeAssert([child conformsToProtocol:NSProtocolFromString(@"ASLayoutElement")], @"Child %@ of spec %@ is not an ASLayoutElement!", child, self);
|
||||
}
|
||||
#endif
|
||||
[_childrenArray setArray:children];
|
||||
}
|
||||
|
||||
- (nullable NSArray<id<ASLayoutElement>> *)children
|
||||
{
|
||||
return [_childrenArray copy];
|
||||
}
|
||||
|
||||
- (NSArray<id<ASLayoutElement>> *)sublayoutElements
|
||||
{
|
||||
return [_childrenArray copy];
|
||||
}
|
||||
|
||||
#pragma mark - NSFastEnumeration
|
||||
|
||||
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len
|
||||
{
|
||||
return [_childrenArray countByEnumeratingWithState:state objects:buffer count:len];
|
||||
}
|
||||
|
||||
#pragma mark - ASTraitEnvironment
|
||||
|
||||
- (ASTraitCollection *)asyncTraitCollection
|
||||
{
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection];
|
||||
}
|
||||
|
||||
ASPrimitiveTraitCollectionDefaults
|
||||
|
||||
#pragma mark - ASLayoutElementStyleExtensibility
|
||||
|
||||
ASLayoutElementStyleExtensibilityForwarding
|
||||
|
||||
#pragma mark - ASDescriptionProvider
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
const auto result = [NSMutableArray<NSDictionary *> array];
|
||||
if (NSArray *children = self.children) {
|
||||
// Use tiny descriptions because these trees can get nested very deep.
|
||||
const auto tinyDescriptions = ASArrayByFlatMapping(children, id object, ASObjectDescriptionMakeTiny(object));
|
||||
[result addObject:@{ @"children": tinyDescriptions }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, [self propertiesForDescription]);
|
||||
}
|
||||
|
||||
#pragma mark - Framework Private
|
||||
|
||||
#if AS_DEDUPE_LAYOUT_SPEC_TREE
|
||||
- (nullable NSHashTable<id<ASLayoutElement>> *)findDuplicatedElementsInSubtree
|
||||
{
|
||||
NSHashTable *result = nil;
|
||||
NSUInteger count = 0;
|
||||
[self _findDuplicatedElementsInSubtreeWithWorkingSet:[NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality] workingCount:&count result:&result];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is extremely performance-sensitive, so we do some strange things.
|
||||
*
|
||||
* @param workingSet A working set of elements for use in the recursion.
|
||||
* @param workingCount The current count of the set for use in the recursion.
|
||||
* @param result The set into which to put the result. This initially points to @c nil to save time if no duplicates exist.
|
||||
*/
|
||||
- (void)_findDuplicatedElementsInSubtreeWithWorkingSet:(NSHashTable<id<ASLayoutElement>> *)workingSet workingCount:(NSUInteger *)workingCount result:(NSHashTable<id<ASLayoutElement>> * _Nullable *)result
|
||||
{
|
||||
Class layoutSpecClass = [ASLayoutSpec class];
|
||||
|
||||
for (id<ASLayoutElement> child in self) {
|
||||
// Add the object into the set.
|
||||
[workingSet addObject:child];
|
||||
|
||||
// Check that addObject: caused the count to increase.
|
||||
// This is faster than using containsObject.
|
||||
NSUInteger oldCount = *workingCount;
|
||||
NSUInteger newCount = workingSet.count;
|
||||
BOOL objectAlreadyExisted = (newCount != oldCount + 1);
|
||||
if (objectAlreadyExisted) {
|
||||
if (*result == nil) {
|
||||
*result = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
}
|
||||
[*result addObject:child];
|
||||
} else {
|
||||
*workingCount = newCount;
|
||||
// If child is a layout spec we haven't visited, recurse its children.
|
||||
if ([child isKindOfClass:layoutSpecClass]) {
|
||||
[(ASLayoutSpec *)child _findDuplicatedElementsInSubtreeWithWorkingSet:workingSet workingCount:workingCount result:result];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark - Debugging
|
||||
|
||||
- (NSString *)debugName
|
||||
{
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
return _debugName;
|
||||
}
|
||||
|
||||
- (void)setDebugName:(NSString *)debugName
|
||||
{
|
||||
AS::MutexLocker l(__instanceLock__);
|
||||
if (!ASObjectIsEqual(_debugName, debugName)) {
|
||||
_debugName = [debugName copy];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||
|
||||
- (NSString *)asciiArtString
|
||||
{
|
||||
NSArray *children = self.children.count < 2 && self.child ? @[self.child] : self.children;
|
||||
return [ASLayoutSpec asciiArtStringForChildren:children parentName:[self asciiArtName]];
|
||||
}
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding];
|
||||
if (_debugName) {
|
||||
[result appendFormat:@" (%@)", _debugName];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ASSynthesizeLockingMethodsWithMutex(__instanceLock__)
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASWrapperLayoutSpec
|
||||
|
||||
@implementation ASWrapperLayoutSpec
|
||||
|
||||
+ (instancetype)wrapperWithLayoutElement:(id<ASLayoutElement>)layoutElement NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[self alloc] initWithLayoutElement:layoutElement];
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.child = layoutElement;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)wrapperWithLayoutElements:(NSArray<id<ASLayoutElement>> *)layoutElements NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[self alloc] initWithLayoutElements:layoutElements];
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayoutElements:(NSArray<id<ASLayoutElement>> *)layoutElements
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.children = layoutElements;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
NSArray *children = self.children;
|
||||
const auto count = children.count;
|
||||
ASLayout *rawSublayouts[count];
|
||||
int i = 0;
|
||||
|
||||
CGSize size = constrainedSize.min;
|
||||
for (id<ASLayoutElement> child in children) {
|
||||
ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max];
|
||||
sublayout.position = CGPointZero;
|
||||
|
||||
size.width = MAX(size.width, sublayout.size.width);
|
||||
size.height = MAX(size.height, sublayout.size.height);
|
||||
|
||||
rawSublayouts[i++] = sublayout;
|
||||
}
|
||||
const auto sublayouts = [NSArray<ASLayout *> arrayByTransferring:rawSublayouts count:i];
|
||||
return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASLayoutSpec (Debugging)
|
||||
|
||||
@implementation ASLayoutSpec (Debugging)
|
||||
|
||||
#pragma mark - ASCII Art Helpers
|
||||
|
||||
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction
|
||||
{
|
||||
NSMutableArray *childStrings = [NSMutableArray array];
|
||||
for (id<ASLayoutElementAsciiArtProtocol> layoutChild in children) {
|
||||
NSString *childString = [layoutChild asciiArtString];
|
||||
if (childString) {
|
||||
[childStrings addObject:childString];
|
||||
}
|
||||
}
|
||||
if (direction == ASStackLayoutDirectionHorizontal) {
|
||||
return [ASAsciiArtBoxCreator horizontalBoxStringForChildren:childStrings parent:parentName];
|
||||
}
|
||||
return [ASAsciiArtBoxCreator verticalBoxStringForChildren:childStrings parent:parentName];
|
||||
}
|
||||
|
||||
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName
|
||||
{
|
||||
return [self asciiArtStringForChildren:children parentName:parentName direction:ASStackLayoutDirectionHorizontal];
|
||||
}
|
||||
|
||||
@end
|
37
submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h
Normal file
37
submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// ASLayoutSpecPrivate.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
#if DEBUG
|
||||
#define AS_DEDUPE_LAYOUT_SPEC_TREE 1
|
||||
#else
|
||||
#define AS_DEDUPE_LAYOUT_SPEC_TREE 0
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASLayoutSpec() {
|
||||
AS::RecursiveMutex __instanceLock__;
|
||||
std::atomic <ASPrimitiveTraitCollection> _primitiveTraitCollection;
|
||||
ASLayoutElementStyle *_style;
|
||||
NSMutableArray *_childrenArray;
|
||||
}
|
||||
|
||||
#if AS_DEDUPE_LAYOUT_SPEC_TREE
|
||||
/**
|
||||
* Recursively search the subtree for elements that occur more than once.
|
||||
*/
|
||||
- (nullable NSHashTable<id<ASLayoutElement>> *)findDuplicatedElementsInSubtree;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
103
submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h
Normal file
103
submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h
Normal file
@ -0,0 +1,103 @@
|
||||
//
|
||||
// ASLayoutSpecUtilities.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
#import <algorithm>
|
||||
#import <functional>
|
||||
#import <type_traits>
|
||||
#import <vector>
|
||||
|
||||
namespace AS {
|
||||
// adopted from http://stackoverflow.com/questions/14945223/map-function-with-c11-constructs
|
||||
// Takes an iterable, applies a function to every element,
|
||||
// and returns a vector of the results
|
||||
//
|
||||
template <typename T, typename Func>
|
||||
auto map(const T &iterable, Func &&func) -> std::vector<decltype(func(std::declval<typename T::value_type>()))>
|
||||
{
|
||||
// Some convenience type definitions
|
||||
typedef decltype(func(std::declval<typename T::value_type>())) value_type;
|
||||
typedef std::vector<value_type> result_type;
|
||||
|
||||
// Prepares an output vector of the appropriate size
|
||||
result_type res(iterable.size());
|
||||
|
||||
// Let std::transform apply `func` to all elements
|
||||
// (use perfect forwarding for the function object)
|
||||
std::transform(
|
||||
begin(iterable), end(iterable), res.begin(),
|
||||
std::forward<Func>(func)
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename Func>
|
||||
auto map(id<NSFastEnumeration> collection, Func &&func) -> std::vector<decltype(func(std::declval<id>()))>
|
||||
{
|
||||
std::vector<decltype(func(std::declval<id>()))> to;
|
||||
for (id obj in collection) {
|
||||
to.push_back(func(obj));
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
template <typename T, typename Func>
|
||||
auto filter(const T &iterable, Func &&func) -> std::vector<typename T::value_type>
|
||||
{
|
||||
std::vector<typename T::value_type> to;
|
||||
for (auto obj : iterable) {
|
||||
if (func(obj)) {
|
||||
to.push_back(obj);
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
};
|
||||
|
||||
inline CGPoint operator+(const CGPoint &p1, const CGPoint &p2)
|
||||
{
|
||||
return { p1.x + p2.x, p1.y + p2.y };
|
||||
}
|
||||
|
||||
inline CGPoint operator-(const CGPoint &p1, const CGPoint &p2)
|
||||
{
|
||||
return { p1.x - p2.x, p1.y - p2.y };
|
||||
}
|
||||
|
||||
inline CGSize operator+(const CGSize &s1, const CGSize &s2)
|
||||
{
|
||||
return { s1.width + s2.width, s1.height + s2.height };
|
||||
}
|
||||
|
||||
inline CGSize operator-(const CGSize &s1, const CGSize &s2)
|
||||
{
|
||||
return { s1.width - s2.width, s1.height - s2.height };
|
||||
}
|
||||
|
||||
inline UIEdgeInsets operator+(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
|
||||
{
|
||||
return { e1.top + e2.top, e1.left + e2.left, e1.bottom + e2.bottom, e1.right + e2.right };
|
||||
}
|
||||
|
||||
inline UIEdgeInsets operator-(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
|
||||
{
|
||||
return { e1.top - e2.top, e1.left - e2.left, e1.bottom - e2.bottom, e1.right - e2.right };
|
||||
}
|
||||
|
||||
inline UIEdgeInsets operator*(const UIEdgeInsets &e1, const UIEdgeInsets &e2)
|
||||
{
|
||||
return { e1.top * e2.top, e1.left * e2.left, e1.bottom * e2.bottom, e1.right * e2.right };
|
||||
}
|
||||
|
||||
inline UIEdgeInsets operator-(const UIEdgeInsets &e)
|
||||
{
|
||||
return { -e.top, -e.left, -e.bottom, -e.right };
|
||||
}
|
94
submodules/AsyncDisplayKit/Source/ASLayoutTransition.h
Normal file
94
submodules/AsyncDisplayKit/Source/ASLayoutTransition.h
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// ASLayoutTransition.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/_ASTransitionContext.h>
|
||||
#import "ASDisplayNodeLayout.h"
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - ASLayoutElementTransition
|
||||
|
||||
/**
|
||||
* Objects conform to this project returns if it's possible to layout asynchronous
|
||||
*/
|
||||
@protocol ASLayoutElementTransition <NSObject>
|
||||
|
||||
/**
|
||||
* @abstract Returns if the layoutElement can be used to layout in an asynchronous way on a background thread.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL canLayoutAsynchronous;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode () <ASLayoutElementTransition>
|
||||
@end
|
||||
@interface ASLayoutSpec () <ASLayoutElementTransition>
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASLayoutTransition
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASLayoutTransition : NSObject <_ASTransitionContextLayoutDelegate>
|
||||
|
||||
/**
|
||||
* Node to apply layout transition on
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) ASDisplayNode *node;
|
||||
|
||||
/**
|
||||
* Previous layout to transition from
|
||||
*/
|
||||
@property (nonatomic, readonly) const ASDisplayNodeLayout &previousLayout NS_RETURNS_INNER_POINTER;
|
||||
|
||||
/**
|
||||
* Pending layout to transition to
|
||||
*/
|
||||
@property (nonatomic, readonly) const ASDisplayNodeLayout &pendingLayout NS_RETURNS_INNER_POINTER;
|
||||
|
||||
/**
|
||||
* Returns if the layout transition needs to happen synchronously
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isSynchronous;
|
||||
|
||||
/**
|
||||
* Returns a newly initialized layout transition
|
||||
*/
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
pendingLayout:(const ASDisplayNodeLayout &)pendingLayout
|
||||
previousLayout:(const ASDisplayNodeLayout &)previousLayout NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* Insert and remove subnodes that were added or removed between the previousLayout and the pendingLayout
|
||||
*/
|
||||
- (void)commitTransition;
|
||||
|
||||
/**
|
||||
* Insert all new subnodes that were added and move the subnodes that moved between the previous layout and
|
||||
* the pending layout.
|
||||
*/
|
||||
- (void)applySubnodeInsertionsAndMoves;
|
||||
|
||||
/**
|
||||
* Remove all subnodes that are removed between the previous layout and the pending layout
|
||||
*/
|
||||
- (void)applySubnodeRemovals;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)new NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
298
submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm
Normal file
298
submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm
Normal file
@ -0,0 +1,298 @@
|
||||
//
|
||||
// ASLayoutTransition.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASLayoutTransition.h"
|
||||
|
||||
#import <AsyncDisplayKit/NSArray+Diffing.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import "ASDisplayNodeInternal.h" // Required for _insertSubnode... / _removeFromSupernode.
|
||||
|
||||
#import <queue>
|
||||
|
||||
#if AS_IG_LIST_KIT
|
||||
#import <IGListKit/IGListKit.h>
|
||||
#import <AsyncDisplayKit/ASLayout+IGListKit.h>
|
||||
#endif
|
||||
|
||||
using AS::MutexLocker;
|
||||
|
||||
/**
|
||||
* Search the whole layout stack if at least one layout has a layoutElement object that can not be layed out asynchronous.
|
||||
* This can be the case for example if a node was already loaded
|
||||
*/
|
||||
static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
|
||||
// Queue used to keep track of sublayouts while traversing this layout in a BFS fashion.
|
||||
std::queue<ASLayout *> queue;
|
||||
queue.push(layout);
|
||||
|
||||
while (!queue.empty()) {
|
||||
layout = queue.front();
|
||||
queue.pop();
|
||||
|
||||
#if DEBUG
|
||||
ASDisplayNodeCAssert([layout.layoutElement conformsToProtocol:@protocol(ASLayoutElementTransition)], @"ASLayoutElement in a layout transition needs to conforms to the ASLayoutElementTransition protocol.");
|
||||
#endif
|
||||
if (((id<ASLayoutElementTransition>)layout.layoutElement).canLayoutAsynchronous == NO) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Add all sublayouts to process in next step
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
queue.push(sublayout);
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@implementation ASLayoutTransition {
|
||||
std::shared_ptr<AS::RecursiveMutex> __instanceLock__;
|
||||
|
||||
BOOL _calculatedSubnodeOperations;
|
||||
NSArray<ASDisplayNode *> *_insertedSubnodes;
|
||||
NSArray<ASDisplayNode *> *_removedSubnodes;
|
||||
std::vector<NSUInteger> _insertedSubnodePositions;
|
||||
std::vector<std::pair<ASDisplayNode *, NSUInteger>> _subnodeMoves;
|
||||
ASDisplayNodeLayout _pendingLayout;
|
||||
ASDisplayNodeLayout _previousLayout;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
pendingLayout:(const ASDisplayNodeLayout &)pendingLayout
|
||||
previousLayout:(const ASDisplayNodeLayout &)previousLayout
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
__instanceLock__ = std::make_shared<AS::RecursiveMutex>();
|
||||
|
||||
_node = node;
|
||||
_pendingLayout = pendingLayout;
|
||||
_previousLayout = previousLayout;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isSynchronous
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
return !ASLayoutCanTransitionAsynchronous(_pendingLayout.layout);
|
||||
}
|
||||
|
||||
- (void)commitTransition
|
||||
{
|
||||
[self applySubnodeRemovals];
|
||||
[self applySubnodeInsertionsAndMoves];
|
||||
}
|
||||
|
||||
- (void)applySubnodeInsertionsAndMoves
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
|
||||
// Create an activity even if no subnodes affected.
|
||||
if (_insertedSubnodePositions.size() == 0 && _subnodeMoves.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASDisplayNodeLogEvent(_node, @"insertSubnodes: %@", _insertedSubnodes);
|
||||
NSUInteger i = 0;
|
||||
NSUInteger j = 0;
|
||||
for (auto const &move : _subnodeMoves) {
|
||||
[move.first _removeFromSupernodeIfEqualTo:_node];
|
||||
}
|
||||
j = 0;
|
||||
while (i < _insertedSubnodePositions.size() && j < _subnodeMoves.size()) {
|
||||
NSUInteger p = _insertedSubnodePositions[i];
|
||||
NSUInteger q = _subnodeMoves[j].second;
|
||||
if (p < q) {
|
||||
[_node _insertSubnode:_insertedSubnodes[i] atIndex:p];
|
||||
i++;
|
||||
} else {
|
||||
[_node _insertSubnode:_subnodeMoves[j].first atIndex:q];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for (; i < _insertedSubnodePositions.size(); ++i) {
|
||||
[_node _insertSubnode:_insertedSubnodes[i] atIndex:_insertedSubnodePositions[i]];
|
||||
}
|
||||
for (; j < _subnodeMoves.size(); ++j) {
|
||||
[_node _insertSubnode:_subnodeMoves[j].first atIndex:_subnodeMoves[j].second];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applySubnodeRemovals
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
|
||||
if (_removedSubnodes.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASDisplayNodeLogEvent(_node, @"removeSubnodes: %@", _removedSubnodes);
|
||||
for (ASDisplayNode *subnode in _removedSubnodes) {
|
||||
// In this case we should only remove the subnode if it's still a subnode of the _node that executes a layout transition.
|
||||
// It can happen that a node already did a layout transition and added this subnode, in this case the subnode
|
||||
// would be removed from the new node instead of _node
|
||||
if (_node.automaticallyManagesSubnodes) {
|
||||
[subnode _removeFromSupernodeIfEqualTo:_node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)calculateSubnodeOperationsIfNeeded
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
if (_calculatedSubnodeOperations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an activity even if no subnodes affected.
|
||||
ASLayout *previousLayout = _previousLayout.layout;
|
||||
ASLayout *pendingLayout = _pendingLayout.layout;
|
||||
|
||||
if (previousLayout) {
|
||||
#if AS_IG_LIST_KIT
|
||||
// IGListDiff completes in linear time O(m+n), so use it if we have it:
|
||||
IGListIndexSetResult *result = IGListDiff(previousLayout.sublayouts, pendingLayout.sublayouts, IGListDiffEquality);
|
||||
_insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, result.inserts, &_insertedSubnodes);
|
||||
findNodesInLayoutAtIndexes(previousLayout, result.deletes, &_removedSubnodes);
|
||||
for (IGListMoveIndex *move in result.moves) {
|
||||
_subnodeMoves.push_back(std::make_pair(previousLayout.sublayouts[move.from].layoutElement, move.to));
|
||||
}
|
||||
|
||||
// Sort by ascending order of move destinations, this will allow easy loop of `insertSubnode:AtIndex` later.
|
||||
std::sort(_subnodeMoves.begin(), _subnodeMoves.end(), [](std::pair<id<ASLayoutElement>, NSUInteger> a,
|
||||
std::pair<ASDisplayNode *, NSUInteger> b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
#else
|
||||
NSIndexSet *insertions, *deletions;
|
||||
NSArray<NSIndexPath *> *moves;
|
||||
NSArray<ASDisplayNode *> *previousNodes = [previousLayout.sublayouts valueForKey:@"layoutElement"];
|
||||
NSArray<ASDisplayNode *> *pendingNodes = [pendingLayout.sublayouts valueForKey:@"layoutElement"];
|
||||
[previousNodes asdk_diffWithArray:pendingNodes
|
||||
insertions:&insertions
|
||||
deletions:&deletions
|
||||
moves:&moves];
|
||||
|
||||
_insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, insertions, &_insertedSubnodes);
|
||||
_removedSubnodes = [previousNodes objectsAtIndexes:deletions];
|
||||
// These should arrive sorted in ascending order of move destinations.
|
||||
for (NSIndexPath *move in moves) {
|
||||
_subnodeMoves.push_back(std::make_pair(previousLayout.sublayouts[([move indexAtPosition:0])].layoutElement,
|
||||
[move indexAtPosition:1]));
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.sublayouts count])];
|
||||
_insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, indexes, &_insertedSubnodes);
|
||||
_removedSubnodes = nil;
|
||||
}
|
||||
_calculatedSubnodeOperations = YES;
|
||||
}
|
||||
|
||||
#pragma mark - _ASTransitionContextDelegate
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
return _node.subnodes;
|
||||
}
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
return _insertedSubnodes;
|
||||
}
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
return _removedSubnodes;
|
||||
}
|
||||
|
||||
- (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
|
||||
return _previousLayout.layout;
|
||||
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
|
||||
return _pendingLayout.layout;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key
|
||||
{
|
||||
MutexLocker l(*__instanceLock__);
|
||||
if ([key isEqualToString:ASTransitionContextFromLayoutKey]) {
|
||||
return _previousLayout.constrainedSize;
|
||||
} else if ([key isEqualToString:ASTransitionContextToLayoutKey]) {
|
||||
return _pendingLayout.constrainedSize;
|
||||
} else {
|
||||
return ASSizeRangeMake(CGSizeZero, CGSizeZero);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Filter helpers
|
||||
|
||||
/**
|
||||
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
|
||||
*/
|
||||
static inline std::vector<NSUInteger> findNodesInLayoutAtIndexes(ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes)
|
||||
{
|
||||
return findNodesInLayoutAtIndexesWithFilteredNodes(layout, indexes, nil, storedNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
|
||||
* Call only with a flattened layout.
|
||||
* @discussion If the node exists in the `filteredNodes` array, the node is not added to `storedNodes`.
|
||||
*/
|
||||
static inline std::vector<NSUInteger> findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> *filteredNodes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes)
|
||||
{
|
||||
NSMutableArray<ASDisplayNode *> *nodes = [NSMutableArray arrayWithCapacity:indexes.count];
|
||||
std::vector<NSUInteger> positions = std::vector<NSUInteger>();
|
||||
|
||||
// From inspection, this is how enumerateObjectsAtIndexes: works under the hood
|
||||
NSUInteger firstIndex = indexes.firstIndex;
|
||||
NSUInteger lastIndex = indexes.lastIndex;
|
||||
NSUInteger idx = 0;
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
if (idx > lastIndex) { break; }
|
||||
if (idx >= firstIndex && [indexes containsIndex:idx]) {
|
||||
ASDisplayNode *node = (ASDisplayNode *)(sublayout.layoutElement);
|
||||
ASDisplayNodeCAssert(node, @"ASDisplayNode was deallocated before it was added to a subnode. It's likely the case that you use automatically manages subnodes and allocate a ASDisplayNode in layoutSpecThatFits: and don't have any strong reference to it.");
|
||||
ASDisplayNodeCAssert([node isKindOfClass:[ASDisplayNode class]], @"sublayout is an ASLayout, but not an ASDisplayNode - only call findNodesInLayoutAtIndexesWithFilteredNodes with a flattened layout (all sublayouts are ASDisplayNodes).");
|
||||
if (node != nil) {
|
||||
BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound);
|
||||
if (notFiltered) {
|
||||
[nodes addObject:node];
|
||||
positions.push_back(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
*storedNodes = nodes;
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
@end
|
19
submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h
Normal file
19
submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// ASMainSerialQueue.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASMainSerialQueue : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger numberOfScheduledBlocks;
|
||||
- (void)performBlockOnMainThread:(dispatch_block_t)block;
|
||||
|
||||
@end
|
81
submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm
Normal file
81
submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm
Normal file
@ -0,0 +1,81 @@
|
||||
//
|
||||
// ASMainSerialQueue.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASMainSerialQueue.h"
|
||||
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
@interface ASMainSerialQueue ()
|
||||
{
|
||||
AS::Mutex _serialQueueLock;
|
||||
NSMutableArray *_blocks;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMainSerialQueue
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_blocks = [[NSMutableArray alloc] init];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfScheduledBlocks
|
||||
{
|
||||
AS::MutexLocker l(_serialQueueLock);
|
||||
return _blocks.count;
|
||||
}
|
||||
|
||||
- (void)performBlockOnMainThread:(dispatch_block_t)block
|
||||
{
|
||||
|
||||
AS::UniqueLock l(_serialQueueLock);
|
||||
[_blocks addObject:block];
|
||||
{
|
||||
l.unlock();
|
||||
[self runBlocks];
|
||||
l.lock();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)runBlocks
|
||||
{
|
||||
dispatch_block_t mainThread = ^{
|
||||
AS::UniqueLock l(self->_serialQueueLock);
|
||||
do {
|
||||
dispatch_block_t block;
|
||||
if (self->_blocks.count > 0) {
|
||||
block = _blocks[0];
|
||||
[self->_blocks removeObjectAtIndex:0];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
{
|
||||
l.unlock();
|
||||
block();
|
||||
l.lock();
|
||||
}
|
||||
} while (true);
|
||||
};
|
||||
|
||||
ASPerformBlockOnMainThread(mainThread);
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [[super description] stringByAppendingFormat:@" Blocks: %@", _blocks];
|
||||
}
|
||||
|
||||
@end
|
199
submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm
Normal file
199
submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm
Normal file
@ -0,0 +1,199 @@
|
||||
//
|
||||
// ASMainThreadDeallocation.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@implementation NSObject (ASMainThreadIvarTeardown)
|
||||
|
||||
- (void)scheduleIvarsForMainThreadDeallocation
|
||||
{
|
||||
if (ASDisplayNodeThreadIsMain()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSValue *ivarsObj = [[self class] _ivarsThatMayNeedMainDeallocation];
|
||||
|
||||
// Unwrap the ivar array
|
||||
unsigned int count = 0;
|
||||
// Will be unused if assertions are disabled.
|
||||
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
|
||||
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
|
||||
Ivar ivars[count];
|
||||
[ivarsObj getValue:ivars];
|
||||
|
||||
for (Ivar ivar : ivars) {
|
||||
id value = object_getIvar(self, ivar);
|
||||
if (value == nil) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([object_getClass(value) needsMainThreadDeallocation]) {
|
||||
// Release the ivar's reference before handing the object to the queue so we
|
||||
// don't risk holding onto it longer than the queue does.
|
||||
object_setIvar(self, ivar, nil);
|
||||
|
||||
ASPerformMainThreadDeallocation(&value);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an NSValue-wrapped array of all the ivars in this class or its superclasses
|
||||
* up through ASDisplayNode, that we expect may need to be deallocated on main.
|
||||
*
|
||||
* This method caches its results.
|
||||
*
|
||||
* Result is of type NSValue<[Ivar]>
|
||||
*/
|
||||
+ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED
|
||||
{
|
||||
static NSCache<Class, NSValue *> *ivarsCache;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
ivarsCache = [[NSCache alloc] init];
|
||||
});
|
||||
|
||||
NSValue *result = [ivarsCache objectForKey:self];
|
||||
if (result != nil) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Cache miss.
|
||||
unsigned int resultCount = 0;
|
||||
static const int kMaxDealloc2MainIvarsPerClassTree = 64;
|
||||
Ivar resultIvars[kMaxDealloc2MainIvarsPerClassTree];
|
||||
|
||||
// Get superclass results first.
|
||||
Class c = class_getSuperclass(self);
|
||||
if (c != [NSObject class]) {
|
||||
NSValue *ivarsObj = [c _ivarsThatMayNeedMainDeallocation];
|
||||
// Unwrap the ivar array and append it to our working array
|
||||
unsigned int count = 0;
|
||||
// Will be unused if assertions are disabled.
|
||||
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
|
||||
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
|
||||
ASDisplayNodeCAssert(resultCount + count < kMaxDealloc2MainIvarsPerClassTree, @"More than %d dealloc2main ivars are not supported. Count: %d", kMaxDealloc2MainIvarsPerClassTree, resultCount + count);
|
||||
[ivarsObj getValue:resultIvars + resultCount];
|
||||
resultCount += count;
|
||||
}
|
||||
|
||||
// Now gather ivars from this particular class.
|
||||
unsigned int allMyIvarsCount;
|
||||
Ivar *allMyIvars = class_copyIvarList(self, &allMyIvarsCount);
|
||||
|
||||
for (NSUInteger i = 0; i < allMyIvarsCount; i++) {
|
||||
Ivar ivar = allMyIvars[i];
|
||||
|
||||
// NOTE: Would be great to exclude weak/unowned ivars, since we don't
|
||||
// release them. Unfortunately the objc_ivar_management access is private and
|
||||
// class_getWeakIvarLayout does not have a well-defined structure.
|
||||
|
||||
const char *type = ivar_getTypeEncoding(ivar);
|
||||
|
||||
if (type != NULL && strcmp(type, @encode(id)) == 0) {
|
||||
// If it's `id` we have to include it just in case.
|
||||
resultIvars[resultCount] = ivar;
|
||||
resultCount += 1;
|
||||
} else {
|
||||
// If it's an ivar with a static type, check the type.
|
||||
Class c = ASGetClassFromType(type);
|
||||
if ([c needsMainThreadDeallocation]) {
|
||||
resultIvars[resultCount] = ivar;
|
||||
resultCount += 1;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
free(allMyIvars);
|
||||
|
||||
// Encode the type (array of Ivars) into a string and wrap it in an NSValue
|
||||
char arrayType[32];
|
||||
snprintf(arrayType, 32, "[%u^{objc_ivar}]", resultCount);
|
||||
result = [NSValue valueWithBytes:resultIvars objCType:arrayType];
|
||||
|
||||
[ivarsCache setObject:result forKey:self];
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSObject (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
const auto name = class_getName(self);
|
||||
if (0 == strncmp(name, "AV", 2) || 0 == strncmp(name, "UI", 2) || 0 == strncmp(name, "CA", 2)) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CALayer (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIColor (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIGestureRecognizer (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIImage (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIResponder (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSProxy (ASNeedsMainThreadDeallocation)
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
101
submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm
Normal file
101
submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// ASObjectDescriptionHelpers.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
#import <UIKit/UIGeometry.h>
|
||||
|
||||
#import "NSIndexSet+ASHelpers.h"
|
||||
|
||||
NSString *ASGetDescriptionValueString(id object)
|
||||
{
|
||||
if ([object isKindOfClass:[NSValue class]]) {
|
||||
// Use shortened NSValue descriptions
|
||||
NSValue *value = object;
|
||||
const char *type = value.objCType;
|
||||
|
||||
if (strcmp(type, @encode(CGRect)) == 0) {
|
||||
CGRect rect = [value CGRectValue];
|
||||
return [NSString stringWithFormat:@"(%g %g; %g %g)", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height];
|
||||
} else if (strcmp(type, @encode(CGSize)) == 0) {
|
||||
return NSStringFromCGSize(value.CGSizeValue);
|
||||
} else if (strcmp(type, @encode(CGPoint)) == 0) {
|
||||
return NSStringFromCGPoint(value.CGPointValue);
|
||||
}
|
||||
|
||||
} else if ([object isKindOfClass:[NSIndexSet class]]) {
|
||||
return [object as_smallDescription];
|
||||
} else if ([object isKindOfClass:[NSIndexPath class]]) {
|
||||
// index paths like (0, 7)
|
||||
NSIndexPath *indexPath = object;
|
||||
NSMutableArray *strings = [NSMutableArray array];
|
||||
for (NSUInteger i = 0; i < indexPath.length; i++) {
|
||||
[strings addObject:[NSString stringWithFormat:@"%lu", (unsigned long)[indexPath indexAtPosition:i]]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"(%@)", [strings componentsJoinedByString:@", "]];
|
||||
} else if ([object respondsToSelector:@selector(componentsJoinedByString:)]) {
|
||||
// e.g. "[ <MYObject: 0x00000000> <MYObject: 0xFFFFFFFF> ]"
|
||||
return [NSString stringWithFormat:@"[ %@ ]", [object componentsJoinedByString:@" "]];
|
||||
}
|
||||
return [object description];
|
||||
}
|
||||
|
||||
NSString *_ASObjectDescriptionMakePropertyList(NSArray<NSDictionary *> * _Nullable propertyGroups)
|
||||
{
|
||||
NSMutableArray *components = [NSMutableArray array];
|
||||
for (NSDictionary *properties in propertyGroups) {
|
||||
[properties enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
|
||||
NSString *str;
|
||||
if (key == (id)kCFNull) {
|
||||
str = ASGetDescriptionValueString(obj);
|
||||
} else {
|
||||
str = [NSString stringWithFormat:@"%@ = %@", key, ASGetDescriptionValueString(obj)];
|
||||
}
|
||||
[components addObject:str];
|
||||
}];
|
||||
}
|
||||
return [components componentsJoinedByString:@"; "];
|
||||
}
|
||||
|
||||
NSString *ASObjectDescriptionMakeWithoutObject(NSArray<NSDictionary *> * _Nullable propertyGroups)
|
||||
{
|
||||
return [NSString stringWithFormat:@"{ %@ }", _ASObjectDescriptionMakePropertyList(propertyGroups)];
|
||||
}
|
||||
|
||||
NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray<NSDictionary *> *propertyGroups)
|
||||
{
|
||||
if (object == nil) {
|
||||
return @"(null)";
|
||||
}
|
||||
|
||||
NSMutableString *str = [NSMutableString stringWithFormat:@"<%s: %p", object_getClassName(object), object];
|
||||
|
||||
NSString *propList = _ASObjectDescriptionMakePropertyList(propertyGroups);
|
||||
if (propList.length > 0) {
|
||||
[str appendFormat:@"; %@", propList];
|
||||
}
|
||||
[str appendString:@">"];
|
||||
return str;
|
||||
}
|
||||
|
||||
NSString *ASObjectDescriptionMakeTiny(__autoreleasing id object) {
|
||||
return ASObjectDescriptionMake(object, nil);
|
||||
}
|
||||
|
||||
NSString *ASStringWithQuotesIfMultiword(NSString *string) {
|
||||
if (string == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]].location != NSNotFound) {
|
||||
return [NSString stringWithFormat:@"\"%@\"", string];
|
||||
} else {
|
||||
return string;
|
||||
}
|
||||
}
|
50
submodules/AsyncDisplayKit/Source/ASPendingStateController.h
Normal file
50
submodules/AsyncDisplayKit/Source/ASPendingStateController.h
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// ASPendingStateController.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
@class ASDisplayNode;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A singleton that is responsible for applying changes to
|
||||
UIView/CALayer properties of display nodes when they
|
||||
have been set on background threads.
|
||||
|
||||
This controller will enqueue run-loop events to flush changes
|
||||
but if you need them flushed now you can call `flush` from the main thread.
|
||||
*/
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASPendingStateController : NSObject
|
||||
|
||||
+ (ASPendingStateController *)sharedInstance;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasChanges;
|
||||
|
||||
/**
|
||||
Flush all pending states for nodes now. Any UIView/CALayer properties
|
||||
that have been set in the background will be applied to their
|
||||
corresponding views/layers before this method returns.
|
||||
|
||||
You must call this method on the main thread.
|
||||
*/
|
||||
- (void)flush;
|
||||
|
||||
/**
|
||||
Register this node as having pending state that needs to be copied
|
||||
over to the view/layer. This is called automatically by display nodes
|
||||
when their view/layer properties are set post-load on background threads.
|
||||
*/
|
||||
- (void)registerNode:(ASDisplayNode *)node;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
102
submodules/AsyncDisplayKit/Source/ASPendingStateController.mm
Normal file
102
submodules/AsyncDisplayKit/Source/ASPendingStateController.mm
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// ASPendingStateController.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASPendingStateController.h"
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
#import "ASDisplayNodeInternal.h" // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate
|
||||
|
||||
@interface ASPendingStateController()
|
||||
{
|
||||
AS::Mutex _lock;
|
||||
|
||||
struct ASPendingStateControllerFlags {
|
||||
unsigned pendingFlush:1;
|
||||
} _flags;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) ASWeakSet<ASDisplayNode *> *dirtyNodes;
|
||||
@end
|
||||
|
||||
@implementation ASPendingStateController
|
||||
|
||||
#pragma mark Lifecycle & Singleton
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_dirtyNodes = [[ASWeakSet alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (ASPendingStateController *)sharedInstance
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static ASPendingStateController *controller = nil;
|
||||
dispatch_once(&onceToken, ^{
|
||||
controller = [[ASPendingStateController alloc] init];
|
||||
});
|
||||
return controller;
|
||||
}
|
||||
|
||||
#pragma mark External API
|
||||
|
||||
- (void)registerNode:(ASDisplayNode *)node
|
||||
{
|
||||
ASDisplayNodeAssert(node.nodeLoaded, @"Expected display node to be loaded before it was registered with ASPendingStateController. Node: %@", node);
|
||||
AS::MutexLocker l(_lock);
|
||||
[_dirtyNodes addObject:node];
|
||||
|
||||
[self scheduleFlushIfNeeded];
|
||||
}
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
_lock.lock();
|
||||
ASWeakSet *dirtyNodes = _dirtyNodes;
|
||||
_dirtyNodes = [[ASWeakSet alloc] init];
|
||||
_flags.pendingFlush = NO;
|
||||
_lock.unlock();
|
||||
|
||||
for (ASDisplayNode *node in dirtyNodes) {
|
||||
[node applyPendingViewState];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Private Methods
|
||||
|
||||
/**
|
||||
This method is assumed to be called with the lock held.
|
||||
*/
|
||||
- (void)scheduleFlushIfNeeded
|
||||
{
|
||||
if (_flags.pendingFlush) {
|
||||
return;
|
||||
}
|
||||
|
||||
_flags.pendingFlush = YES;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self flush];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPendingStateController (Testing)
|
||||
|
||||
- (BOOL)test_isFlushScheduled
|
||||
{
|
||||
return _flags.pendingFlush;
|
||||
}
|
||||
|
||||
@end
|
83
submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm
Normal file
83
submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm
Normal file
@ -0,0 +1,83 @@
|
||||
//
|
||||
// ASRecursiveUnfairLock.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASRecursiveUnfairLock.h>
|
||||
|
||||
#import <stdatomic.h>
|
||||
|
||||
/**
|
||||
* For our atomic _thread, we use acquire/release memory order so that we can have
|
||||
* the minimum possible constraint on the hardware. The default, `memory_order_seq_cst`
|
||||
* demands that there be a total order of all such modifications as seen by all threads.
|
||||
* Acquire/release only requires that modifications to this specific atomic are
|
||||
* synchronized across acquire/release pairs.
|
||||
* http://en.cppreference.com/w/cpp/atomic/memory_order
|
||||
*
|
||||
* Note also that the unfair_lock involves a thread fence as well, so we don't need to
|
||||
* take care of synchronizing other values. Just the thread value.
|
||||
*/
|
||||
#define rul_set_thread(l, t) atomic_store_explicit(&l->_thread, t, memory_order_release)
|
||||
#define rul_get_thread(l) atomic_load_explicit(&l->_thread, memory_order_acquire)
|
||||
|
||||
void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l)
|
||||
{
|
||||
// Try to lock without blocking. If we fail, check what thread owns it.
|
||||
// Note that the owning thread CAN CHANGE freely, but it can't become `self`
|
||||
// because only we are `self`. And if it's already `self` then we already have
|
||||
// the lock, because we reset it to NULL before we unlock. So (thread == self) is
|
||||
// invariant.
|
||||
|
||||
const pthread_t s = pthread_self();
|
||||
if (os_unfair_lock_trylock(&l->_lock)) {
|
||||
// Owned by nobody. We now have the lock. Assign self.
|
||||
rul_set_thread(l, s);
|
||||
} else if (rul_get_thread(l) == s) {
|
||||
// Owned by self (recursive lock). nop.
|
||||
} else {
|
||||
// Owned by other thread. Block and then set thread to self.
|
||||
os_unfair_lock_lock(&l->_lock);
|
||||
rul_set_thread(l, s);
|
||||
}
|
||||
|
||||
l->_count++;
|
||||
}
|
||||
|
||||
BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l)
|
||||
{
|
||||
// Same as Lock above. See comments there.
|
||||
|
||||
const pthread_t s = pthread_self();
|
||||
if (os_unfair_lock_trylock(&l->_lock)) {
|
||||
// Owned by nobody. We now have the lock. Assign self.
|
||||
rul_set_thread(l, s);
|
||||
} else if (rul_get_thread(l) == s) {
|
||||
// Owned by self (recursive lock). nop.
|
||||
} else {
|
||||
// Owned by other thread. Fail.
|
||||
return NO;
|
||||
}
|
||||
|
||||
l->_count++;
|
||||
return YES;
|
||||
}
|
||||
|
||||
void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l)
|
||||
{
|
||||
// Ensure we have the lock. This check may miss some pathological cases,
|
||||
// but it'll catch 99.999999% of this serious programmer error.
|
||||
NSCAssert(rul_get_thread(l) == pthread_self(), @"Unlocking from a different thread than locked.");
|
||||
|
||||
if (0 == --l->_count) {
|
||||
// Note that we have to clear this before unlocking because, if another thread
|
||||
// succeeds in locking above, but hasn't managed to update _thread, and we
|
||||
// try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly
|
||||
// think that we still own the lock and proceed without blocking.
|
||||
rul_set_thread(l, NULL);
|
||||
os_unfair_lock_unlock(&l->_lock);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
//
|
||||
// ASResponderChainEnumerator.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIResponder.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASResponderChainEnumerator : NSEnumerator
|
||||
|
||||
- (instancetype)initWithResponder:(UIResponder *)responder;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIResponder (ASResponderChainEnumerator)
|
||||
|
||||
- (ASResponderChainEnumerator *)asdk_responderChainEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// ASResponderChainEnumerator.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASResponderChainEnumerator.h"
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
@implementation ASResponderChainEnumerator {
|
||||
UIResponder *_currentResponder;
|
||||
}
|
||||
|
||||
- (instancetype)initWithResponder:(UIResponder *)responder
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (self = [super init]) {
|
||||
_currentResponder = responder;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - NSEnumerator
|
||||
|
||||
- (id)nextObject
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
id result = [_currentResponder nextResponder];
|
||||
_currentResponder = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIResponder (ASResponderChainEnumerator)
|
||||
|
||||
- (NSEnumerator *)asdk_responderChainEnumerator
|
||||
{
|
||||
return [[ASResponderChainEnumerator alloc] initWithResponder:self];
|
||||
}
|
||||
|
||||
@end
|
464
submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm
Normal file
464
submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm
Normal file
@ -0,0 +1,464 @@
|
||||
//
|
||||
// ASRunLoopQueue.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import "ASSignpost.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <cstdlib>
|
||||
#import <deque>
|
||||
#import <vector>
|
||||
|
||||
#define ASRunLoopQueueLoggingEnabled 0
|
||||
#define ASRunLoopQueueVerboseLoggingEnabled 0
|
||||
|
||||
using AS::MutexLocker;
|
||||
|
||||
static void runLoopSourceCallback(void *info) {
|
||||
// No-op
|
||||
#if ASRunLoopQueueVerboseLoggingEnabled
|
||||
NSLog(@"<%@> - Called runLoopSourceCallback", info);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - ASDeallocQueue
|
||||
|
||||
@implementation ASDeallocQueue {
|
||||
std::vector<CFTypeRef> _queue;
|
||||
AS::Mutex _lock;
|
||||
}
|
||||
|
||||
+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED
|
||||
{
|
||||
static ASDeallocQueue *deallocQueue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
deallocQueue = [[ASDeallocQueue alloc] init];
|
||||
});
|
||||
return deallocQueue;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
ASDisplayNodeFailAssert(@"Singleton should not dealloc.");
|
||||
}
|
||||
|
||||
- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr
|
||||
{
|
||||
NSParameterAssert(objectPtr != NULL);
|
||||
|
||||
// Cast to CFType so we can manipulate retain count manually.
|
||||
const auto cfPtr = (CFTypeRef *)(void *)objectPtr;
|
||||
if (!cfPtr || !*cfPtr) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lock.lock();
|
||||
const auto isFirstEntry = _queue.empty();
|
||||
// Push the pointer into our queue and clear their pointer.
|
||||
// This "steals" the +1 from ARC and nils their pointer so they can't
|
||||
// access or release the object.
|
||||
_queue.push_back(*cfPtr);
|
||||
*cfPtr = NULL;
|
||||
_lock.unlock();
|
||||
|
||||
if (isFirstEntry) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.100 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
[self drain];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drain
|
||||
{
|
||||
_lock.lock();
|
||||
const auto q = std::move(_queue);
|
||||
_lock.unlock();
|
||||
for (CFTypeRef ref : q) {
|
||||
// NOTE: Could check that retain count is 1 and retry later if not.
|
||||
CFRelease(ref);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASAbstractRunLoopQueue
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self == nil) {
|
||||
return nil;
|
||||
}
|
||||
ASDisplayNodeAssert(self.class != [ASAbstractRunLoopQueue class], @"Should never create instances of abstract class ASAbstractRunLoopQueue.");
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASRunLoopQueue
|
||||
|
||||
@interface ASRunLoopQueue () {
|
||||
CFRunLoopRef _runLoop;
|
||||
CFRunLoopSourceRef _runLoopSource;
|
||||
CFRunLoopObserverRef _runLoopObserver;
|
||||
NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance.
|
||||
AS::RecursiveMutex _internalQueueLock;
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSTimer *_runloopQueueLoggingTimer;
|
||||
#endif
|
||||
}
|
||||
|
||||
@property (nonatomic) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained);
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASRunLoopQueue
|
||||
|
||||
- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_runLoop = runloop;
|
||||
NSPointerFunctionsOptions options = retainsObjects ? NSPointerFunctionsStrongMemory : NSPointerFunctionsWeakMemory;
|
||||
_internalQueue = [[NSPointerArray alloc] initWithOptions:options];
|
||||
_queueConsumer = handlerBlock;
|
||||
_batchSize = 1;
|
||||
_ensureExclusiveMembership = YES;
|
||||
|
||||
// Self is guaranteed to outlive the observer. Without the high cost of a weak pointer,
|
||||
// __unsafe_unretained allows us to avoid flagging the memory cycle detector.
|
||||
__unsafe_unretained __typeof__(self) weakSelf = self;
|
||||
void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
||||
[weakSelf processQueue];
|
||||
};
|
||||
_runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock);
|
||||
CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes);
|
||||
|
||||
// It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of
|
||||
// the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done
|
||||
CFRunLoopSourceContext sourceContext = {};
|
||||
sourceContext.perform = runLoopSourceCallback;
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
sourceContext.info = (__bridge void *)self;
|
||||
#endif
|
||||
_runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
|
||||
CFRunLoopAddSource(runloop, _runLoopSource, kCFRunLoopCommonModes);
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
_runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (CFRunLoopContainsSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes)) {
|
||||
CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes);
|
||||
}
|
||||
CFRelease(_runLoopSource);
|
||||
_runLoopSource = nil;
|
||||
|
||||
if (CFRunLoopObserverIsValid(_runLoopObserver)) {
|
||||
CFRunLoopObserverInvalidate(_runLoopObserver);
|
||||
}
|
||||
CFRelease(_runLoopObserver);
|
||||
_runLoopObserver = nil;
|
||||
}
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
- (void)checkRunLoop
|
||||
{
|
||||
NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.count);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)processQueue
|
||||
{
|
||||
BOOL hasExecutionBlock = (_queueConsumer != nil);
|
||||
|
||||
// If we have an execution block, this vector will be populated, otherwise remains empty.
|
||||
// This is to avoid needlessly retaining/releasing the objects if we don't have a block.
|
||||
std::vector<id> itemsToProcess;
|
||||
|
||||
BOOL isQueueDrained = NO;
|
||||
{
|
||||
MutexLocker l(_internalQueueLock);
|
||||
|
||||
NSInteger internalQueueCount = _internalQueue.count;
|
||||
// Early-exit if the queue is empty.
|
||||
if (internalQueueCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSignpostStart(ASSignpostRunLoopQueueBatch);
|
||||
|
||||
// Snatch the next batch of items.
|
||||
NSInteger maxCountToProcess = MIN(internalQueueCount, self.batchSize);
|
||||
|
||||
/**
|
||||
* For each item in the next batch, if it's non-nil then NULL it out
|
||||
* and if we have an execution block then add it in.
|
||||
* This could be written a bunch of different ways but
|
||||
* this particular one nicely balances readability, safety, and efficiency.
|
||||
*/
|
||||
NSInteger foundItemCount = 0;
|
||||
for (NSInteger i = 0; i < internalQueueCount && foundItemCount < maxCountToProcess; i++) {
|
||||
/**
|
||||
* It is safe to use unsafe_unretained here. If the queue is weak, the
|
||||
* object will be added to the autorelease pool. If the queue is strong,
|
||||
* it will retain the object until we transfer it (retain it) in itemsToProcess.
|
||||
*/
|
||||
__unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i];
|
||||
if (ptr != nil) {
|
||||
foundItemCount++;
|
||||
if (hasExecutionBlock) {
|
||||
itemsToProcess.push_back(ptr);
|
||||
}
|
||||
[_internalQueue replacePointerAtIndex:i withPointer:NULL];
|
||||
}
|
||||
}
|
||||
|
||||
if (foundItemCount == 0) {
|
||||
// If _internalQueue holds weak references, and all of them just become NULL, then the array
|
||||
// is never marked as needsCompletion, and compact will return early, not removing the NULL's.
|
||||
// Inserting a NULL here ensures the compaction will take place.
|
||||
// See http://www.openradar.me/15396578 and https://stackoverflow.com/a/40274426/1136669
|
||||
[_internalQueue addPointer:NULL];
|
||||
}
|
||||
|
||||
[_internalQueue compact];
|
||||
if (_internalQueue.count == 0) {
|
||||
isQueueDrained = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// itemsToProcess will be empty if _queueConsumer == nil so no need to check again.
|
||||
const auto count = itemsToProcess.size();
|
||||
if (count > 0) {
|
||||
const auto itemsEnd = itemsToProcess.cend();
|
||||
for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) {
|
||||
__unsafe_unretained id value = *iterator;
|
||||
_queueConsumer(value, isQueueDrained && iterator == itemsEnd - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If the queue is not fully drained yet force another run loop to process next batch of items
|
||||
if (!isQueueDrained) {
|
||||
CFRunLoopSourceSignal(_runLoopSource);
|
||||
CFRunLoopWakeUp(_runLoop);
|
||||
}
|
||||
|
||||
ASSignpostEnd(ASSignpostRunLoopQueueBatch);
|
||||
}
|
||||
|
||||
- (void)enqueue:(id)object
|
||||
{
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLocker l(_internalQueueLock);
|
||||
|
||||
// Check if the object exists.
|
||||
BOOL foundObject = NO;
|
||||
|
||||
if (_ensureExclusiveMembership) {
|
||||
for (id currentObject in _internalQueue) {
|
||||
if (currentObject == object) {
|
||||
foundObject = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundObject) {
|
||||
[_internalQueue addPointer:(__bridge void *)object];
|
||||
if (_internalQueue.count == 1) {
|
||||
CFRunLoopSourceSignal(_runLoopSource);
|
||||
CFRunLoopWakeUp(_runLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
MutexLocker l(_internalQueueLock);
|
||||
return _internalQueue.count == 0;
|
||||
}
|
||||
|
||||
ASSynthesizeLockingMethodsWithMutex(_internalQueueLock)
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASCATransactionQueue
|
||||
|
||||
@interface ASCATransactionQueue () {
|
||||
CFRunLoopSourceRef _runLoopSource;
|
||||
CFRunLoopObserverRef _preTransactionObserver;
|
||||
|
||||
// Current buffer for new entries, only accessed from within its mutex.
|
||||
std::vector<id<ASCATransactionQueueObserving>> _internalQueue;
|
||||
|
||||
// No retain, no release, pointer hash, pointer equality.
|
||||
// Enforce uniqueness in our queue. std::unordered_set does a heap allocation for each entry – not good.
|
||||
CFMutableSetRef _internalQueueHashSet;
|
||||
|
||||
// Temporary buffer, only accessed from the main thread in -process.
|
||||
std::vector<id<ASCATransactionQueueObserving>> _batchBuffer;
|
||||
|
||||
AS::Mutex _internalQueueLock;
|
||||
|
||||
// In order to not pollute the top-level activities, each queue has 1 root activity.
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSTimer *_runloopQueueLoggingTimer;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCATransactionQueue
|
||||
|
||||
// CoreAnimation commit order is 2000000, the goal of this is to process shortly beforehand
|
||||
// but after most other scheduled work on the runloop has processed.
|
||||
static int const kASASCATransactionQueueOrder = 1000000;
|
||||
|
||||
ASCATransactionQueue *_ASSharedCATransactionQueue;
|
||||
dispatch_once_t _ASSharedCATransactionQueueOnceToken;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_internalQueueHashSet = CFSetCreateMutable(NULL, 0, NULL);
|
||||
|
||||
// This is going to be a very busy queue – every node in the preload range will enter this queue.
|
||||
// Save some time on first render by reserving space up front.
|
||||
static constexpr int kInternalQueueInitialCapacity = 64;
|
||||
_internalQueue.reserve(kInternalQueueInitialCapacity);
|
||||
_batchBuffer.reserve(kInternalQueueInitialCapacity);
|
||||
|
||||
// Self is guaranteed to outlive the observer. Without the high cost of a weak pointer,
|
||||
// __unsafe_unretained allows us to avoid flagging the memory cycle detector.
|
||||
__unsafe_unretained __typeof__(self) weakSelf = self;
|
||||
_preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
||||
while (!weakSelf->_internalQueue.empty()) {
|
||||
[weakSelf processQueue];
|
||||
}
|
||||
});
|
||||
|
||||
CFRunLoopAddObserver(CFRunLoopGetMain(), _preTransactionObserver, kCFRunLoopCommonModes);
|
||||
|
||||
// It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of
|
||||
// the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done
|
||||
CFRunLoopSourceContext sourceContext = {};
|
||||
sourceContext.perform = runLoopSourceCallback;
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
sourceContext.info = (__bridge void *)self;
|
||||
#endif
|
||||
_runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), _runLoopSource, kCFRunLoopCommonModes);
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
_runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
CFRelease(_internalQueueHashSet);
|
||||
CFRunLoopRemoveSource(CFRunLoopGetMain(), _runLoopSource, kCFRunLoopCommonModes);
|
||||
CFRelease(_runLoopSource);
|
||||
_runLoopSource = nil;
|
||||
|
||||
if (CFRunLoopObserverIsValid(_preTransactionObserver)) {
|
||||
CFRunLoopObserverInvalidate(_preTransactionObserver);
|
||||
}
|
||||
CFRelease(_preTransactionObserver);
|
||||
_preTransactionObserver = nil;
|
||||
}
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
- (void)checkRunLoop
|
||||
{
|
||||
NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.count);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)processQueue
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
AS::UniqueLock l(_internalQueueLock);
|
||||
NSInteger count = _internalQueue.size();
|
||||
// Early-exit if the queue is empty.
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
ASSignpostStart(ASSignpostRunLoopQueueBatch);
|
||||
|
||||
// Swap buffers, clear our hash table.
|
||||
_internalQueue.swap(_batchBuffer);
|
||||
CFSetRemoveAllValues(_internalQueueHashSet);
|
||||
|
||||
// Unlock early. We are done with internal queue, and batch buffer is main-thread-only so no lock.
|
||||
l.unlock();
|
||||
|
||||
for (const id<ASCATransactionQueueObserving> &value : _batchBuffer) {
|
||||
[value prepareForCATransactionCommit];
|
||||
}
|
||||
_batchBuffer.clear();
|
||||
ASSignpostEnd(ASSignpostRunLoopQueueBatch);
|
||||
}
|
||||
|
||||
- (void)enqueue:(id<ASCATransactionQueueObserving>)object
|
||||
{
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.enabled) {
|
||||
[object prepareForCATransactionCommit];
|
||||
return;
|
||||
}
|
||||
|
||||
MutexLocker l(_internalQueueLock);
|
||||
if (CFSetContainsValue(_internalQueueHashSet, (__bridge void *)object)) {
|
||||
return;
|
||||
}
|
||||
CFSetAddValue(_internalQueueHashSet, (__bridge void *)object);
|
||||
_internalQueue.emplace_back(object);
|
||||
if (_internalQueue.size() == 1) {
|
||||
CFRunLoopSourceSignal(_runLoopSource);
|
||||
CFRunLoopWakeUp(CFRunLoopGetMain());
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
MutexLocker l(_internalQueueLock);
|
||||
return _internalQueue.empty();
|
||||
}
|
||||
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
return ASActivateExperimentalFeature(ASExperimentalInterfaceStateCoalescing);
|
||||
}
|
||||
|
||||
@end
|
64
submodules/AsyncDisplayKit/Source/ASScrollDirection.mm
Normal file
64
submodules/AsyncDisplayKit/Source/ASScrollDirection.mm
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// ASScrollDirection.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
const ASScrollDirection ASScrollDirectionHorizontalDirections = ASScrollDirectionLeft | ASScrollDirectionRight;
|
||||
const ASScrollDirection ASScrollDirectionVerticalDirections = ASScrollDirectionUp | ASScrollDirectionDown;
|
||||
|
||||
BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionVerticalDirections) != 0;
|
||||
}
|
||||
|
||||
BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionHorizontalDirections) != 0;
|
||||
}
|
||||
|
||||
BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionRight) != 0;
|
||||
}
|
||||
|
||||
BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionLeft) != 0;
|
||||
}
|
||||
|
||||
BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionUp) != 0;
|
||||
}
|
||||
|
||||
BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection) {
|
||||
return (scrollDirection & ASScrollDirectionDown) != 0;
|
||||
}
|
||||
|
||||
ASScrollDirection ASScrollDirectionInvertHorizontally(ASScrollDirection scrollDirection) {
|
||||
if (scrollDirection == ASScrollDirectionRight) {
|
||||
return ASScrollDirectionLeft;
|
||||
} else if (scrollDirection == ASScrollDirectionLeft) {
|
||||
return ASScrollDirectionRight;
|
||||
}
|
||||
return scrollDirection;
|
||||
}
|
||||
|
||||
ASScrollDirection ASScrollDirectionInvertVertically(ASScrollDirection scrollDirection) {
|
||||
if (scrollDirection == ASScrollDirectionUp) {
|
||||
return ASScrollDirectionDown;
|
||||
} else if (scrollDirection == ASScrollDirectionDown) {
|
||||
return ASScrollDirectionUp;
|
||||
}
|
||||
return scrollDirection;
|
||||
}
|
||||
|
||||
ASScrollDirection ASScrollDirectionApplyTransform(ASScrollDirection scrollDirection, CGAffineTransform transform) {
|
||||
if ((transform.a < 0) && ASScrollDirectionContainsHorizontalDirection(scrollDirection)) {
|
||||
return ASScrollDirectionInvertHorizontally(scrollDirection);
|
||||
} else if ((transform.d < 0) && ASScrollDirectionContainsVerticalDirection(scrollDirection)) {
|
||||
return ASScrollDirectionInvertVertically(scrollDirection);
|
||||
}
|
||||
return scrollDirection;
|
||||
}
|
178
submodules/AsyncDisplayKit/Source/ASScrollNode.mm
Normal file
178
submodules/AsyncDisplayKit/Source/ASScrollNode.mm
Normal file
@ -0,0 +1,178 @@
|
||||
//
|
||||
// ASScrollNode.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASScrollNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
@interface ASScrollView : UIScrollView
|
||||
@end
|
||||
|
||||
@implementation ASScrollView
|
||||
|
||||
// This special +layerClass allows ASScrollNode to get -layout calls from -layoutSublayers.
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [_ASDisplayLayer class];
|
||||
}
|
||||
|
||||
- (ASScrollNode *)scrollNode
|
||||
{
|
||||
return (ASScrollNode *)ASViewToDisplayNode(self);
|
||||
}
|
||||
|
||||
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
|
||||
if ([[self scrollNode] canCancelAllTouchesInViews]) {
|
||||
return true;
|
||||
}
|
||||
return [super touchesShouldCancelInContentView:view];
|
||||
}
|
||||
|
||||
#pragma mark - _ASDisplayView behavior substitutions
|
||||
// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element.
|
||||
// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView.
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar.
|
||||
BOOL visible = (newWindow != nil);
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
ASDisplayNode *node = self.scrollNode; // Create strong reference to weak ivar.
|
||||
BOOL visible = (self.window != nil);
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)accessibilityElements
|
||||
{
|
||||
return [self.asyncdisplaykit_node accessibilityElements];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASScrollNode
|
||||
{
|
||||
ASScrollDirection _scrollableDirections;
|
||||
BOOL _automaticallyManagesContentSize;
|
||||
CGSize _contentCalculatedSizeFromLayout;
|
||||
}
|
||||
@dynamic view;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
[self setViewBlock:^UIView *{ return [[ASScrollView alloc] init]; }];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
|
||||
restrictedToSize:(ASLayoutElementSize)size
|
||||
relativeToParentSize:(CGSize)parentSize
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
|
||||
ASSizeRange contentConstrainedSize = constrainedSize;
|
||||
if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) {
|
||||
contentConstrainedSize.max.height = CGFLOAT_MAX;
|
||||
}
|
||||
if (ASScrollDirectionContainsHorizontalDirection(_scrollableDirections)) {
|
||||
contentConstrainedSize.max.width = CGFLOAT_MAX;
|
||||
}
|
||||
|
||||
ASLayout *layout = [super calculateLayoutThatFits:contentConstrainedSize
|
||||
restrictedToSize:size
|
||||
relativeToParentSize:parentSize];
|
||||
|
||||
if (_automaticallyManagesContentSize) {
|
||||
// To understand this code, imagine we're containing a horizontal stack set within a vertical table node.
|
||||
// Our parentSize is fixed ~375pt width, but 0 - INF height. Our stack measures 1000pt width, 50pt height.
|
||||
// In this case, we want our scrollNode.bounds to be 375pt wide, and 50pt high. ContentSize 1000pt, 50pt.
|
||||
// We can achieve this behavior by:
|
||||
// 1. Always set contentSize to layout.size.
|
||||
// 2. Set bounds to a size that is calculated by clamping parentSize against constrained size,
|
||||
// unless one dimension is not defined, in which case adopt the contentSize for that dimension.
|
||||
_contentCalculatedSizeFromLayout = layout.size;
|
||||
CGSize selfSize = ASSizeRangeClamp(constrainedSize, parentSize);
|
||||
if (ASPointsValidForLayout(selfSize.width) == NO) {
|
||||
selfSize.width = _contentCalculatedSizeFromLayout.width;
|
||||
}
|
||||
if (ASPointsValidForLayout(selfSize.height) == NO) {
|
||||
selfSize.height = _contentCalculatedSizeFromLayout.height;
|
||||
}
|
||||
// Don't provide a position, as that should be set by the parent.
|
||||
layout = [ASLayout layoutWithLayoutElement:self
|
||||
size:selfSize
|
||||
sublayouts:layout.sublayouts];
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
|
||||
ASLockScopeSelf(); // Lock for using our two instance variables.
|
||||
|
||||
if (_automaticallyManagesContentSize) {
|
||||
CGSize contentSize = _contentCalculatedSizeFromLayout;
|
||||
if (ASIsCGSizeValidForLayout(contentSize) == NO) {
|
||||
NSLog(@"%@ calculated a size in its layout spec that can't be applied to .contentSize: %@. Applying parentSize (scrollNode's bounds) instead: %@.", self, NSStringFromCGSize(contentSize), NSStringFromCGSize(self.calculatedSize));
|
||||
contentSize = self.calculatedSize;
|
||||
}
|
||||
self.view.contentSize = contentSize;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)automaticallyManagesContentSize
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
return _automaticallyManagesContentSize;
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyManagesContentSize:(BOOL)automaticallyManagesContentSize
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
_automaticallyManagesContentSize = automaticallyManagesContentSize;
|
||||
if (_automaticallyManagesContentSize == YES
|
||||
&& ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO
|
||||
&& ASScrollDirectionContainsHorizontalDirection(_scrollableDirections) == NO) {
|
||||
// Set the @default value, for more user-friendly behavior of the most
|
||||
// common use cases of .automaticallyManagesContentSize.
|
||||
_scrollableDirections = ASScrollDirectionVerticalDirections;
|
||||
}
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollableDirections
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
return _scrollableDirections;
|
||||
}
|
||||
|
||||
- (void)setScrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
if (_scrollableDirections != scrollableDirections) {
|
||||
_scrollableDirections = scrollableDirections;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
94
submodules/AsyncDisplayKit/Source/ASSignpost.h
Normal file
94
submodules/AsyncDisplayKit/Source/ASSignpost.h
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// ASSignpost.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
/// The signposts we use. Signposts are grouped by color. The SystemTrace.tracetemplate file
|
||||
/// should be kept up-to-date with these values.
|
||||
typedef NS_ENUM(uint32_t, ASSignpostName) {
|
||||
// Collection/Table (Blue)
|
||||
ASSignpostDataControllerBatch = 300, // Alloc/layout nodes before collection update.
|
||||
ASSignpostRangeControllerUpdate, // Ranges update pass.
|
||||
ASSignpostCollectionUpdate, // Entire update process, from -endUpdates to [super perform…]
|
||||
|
||||
// Rendering (Green)
|
||||
ASSignpostLayerDisplay = 325, // Client display callout.
|
||||
ASSignpostRunLoopQueueBatch, // One batch of ASRunLoopQueue.
|
||||
|
||||
// Layout (Purple)
|
||||
ASSignpostCalculateLayout = 350, // Start of calculateLayoutThatFits to end. Max 1 per thread.
|
||||
|
||||
// Misc (Orange)
|
||||
ASSignpostDeallocQueueDrain = 375, // One chunk of dealloc queue work. arg0 is count.
|
||||
ASSignpostCATransactionLayout, // The CA transaction commit layout phase.
|
||||
ASSignpostCATransactionCommit // The CA transaction commit post-layout phase.
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uintptr_t, ASSignpostColor) {
|
||||
ASSignpostColorBlue,
|
||||
ASSignpostColorGreen,
|
||||
ASSignpostColorPurple,
|
||||
ASSignpostColorOrange,
|
||||
ASSignpostColorRed,
|
||||
ASSignpostColorDefault
|
||||
};
|
||||
|
||||
static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpostColor colorPref) {
|
||||
if (colorPref == ASSignpostColorDefault) {
|
||||
return (ASSignpostColor)((name / 25) % 4);
|
||||
} else {
|
||||
return colorPref;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(PROFILE) && __has_include(<sys/kdebug_signpost.h>)
|
||||
#define AS_KDEBUG_ENABLE 1
|
||||
#else
|
||||
#define AS_KDEBUG_ENABLE 0
|
||||
#endif
|
||||
|
||||
#if AS_KDEBUG_ENABLE
|
||||
|
||||
#import <sys/kdebug_signpost.h>
|
||||
|
||||
// These definitions are required to build the backward-compatible kdebug trace
|
||||
// on the iOS 10 SDK. The kdebug_trace function crashes if run on iOS 9 and earlier.
|
||||
// It's valuable to support trace signposts on iOS 9, because A5 devices don't support iOS 10.
|
||||
#ifndef DBG_MACH_CHUD
|
||||
#define DBG_MACH_CHUD 0x0A
|
||||
#define DBG_FUNC_NONE 0
|
||||
#define DBG_FUNC_START 1
|
||||
#define DBG_FUNC_END 2
|
||||
#define DBG_APPS 33
|
||||
#define SYS_kdebug_trace 180
|
||||
#define KDBG_CODE(Class, SubClass, code) (((Class & 0xff) << 24) | ((SubClass & 0xff) << 16) | ((code & 0x3fff) << 2))
|
||||
#define APPSDBG_CODE(SubClass,code) KDBG_CODE(DBG_APPS, SubClass, code)
|
||||
#endif
|
||||
|
||||
// Currently we'll reserve arg3.
|
||||
#define ASSignpost(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_NONE, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
|
||||
#define ASSignpostStartCustom(name, identifier, arg2) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_start(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_START, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0);
|
||||
#define ASSignpostStart(name) ASSignpostStartCustom(name, self, 0)
|
||||
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_end(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_END, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
#define ASSignpostEnd(name) ASSignpostEndCustom(name, self, 0, ASSignpostColorDefault)
|
||||
|
||||
#else
|
||||
|
||||
#define ASSignpost(name, identifier, arg2, color)
|
||||
#define ASSignpostStartCustom(name, identifier, arg2)
|
||||
#define ASSignpostStart(name)
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color)
|
||||
#define ASSignpostEnd(name)
|
||||
|
||||
#endif
|
194
submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm
Normal file
194
submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm
Normal file
@ -0,0 +1,194 @@
|
||||
//
|
||||
// ASTextKitComponents.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASTextKitComponents.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
|
||||
|
||||
#import <tgmath.h>
|
||||
|
||||
@interface ASTextKitComponentsTextView () {
|
||||
// Prevent UITextView from updating contentOffset while deallocating: https://github.com/TextureGroup/Texture/issues/860
|
||||
BOOL _deallocating;
|
||||
}
|
||||
@property CGRect threadSafeBounds;
|
||||
@end
|
||||
|
||||
@implementation ASTextKitComponentsTextView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer
|
||||
{
|
||||
self = [super initWithFrame:frame textContainer:textContainer];
|
||||
if (self) {
|
||||
_threadSafeBounds = self.bounds;
|
||||
_deallocating = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_deallocating = YES;
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super setFrame:frame];
|
||||
self.threadSafeBounds = self.bounds;
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super setBounds:bounds];
|
||||
self.threadSafeBounds = bounds;
|
||||
}
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset
|
||||
{
|
||||
if (_deallocating) {
|
||||
return;
|
||||
}
|
||||
|
||||
[super setContentOffset:contentOffset];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTextKitComponents ()
|
||||
|
||||
// read-write redeclarations
|
||||
@property (nonatomic) NSTextStorage *textStorage;
|
||||
@property (nonatomic) NSTextContainer *textContainer;
|
||||
@property (nonatomic) NSLayoutManager *layoutManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextKitComponents
|
||||
|
||||
#pragma mark - Class
|
||||
|
||||
+ (instancetype)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString
|
||||
textContainerSize:(CGSize)textContainerSize NS_RETURNS_RETAINED
|
||||
{
|
||||
NSTextStorage *textStorage = attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init];
|
||||
|
||||
return [self componentsWithTextStorage:textStorage
|
||||
textContainerSize:textContainerSize
|
||||
layoutManager:[[NSLayoutManager alloc] init]];
|
||||
}
|
||||
|
||||
+ (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage
|
||||
textContainerSize:(CGSize)textContainerSize
|
||||
layoutManager:(NSLayoutManager *)layoutManager NS_RETURNS_RETAINED
|
||||
{
|
||||
ASTextKitComponents *components = [[self alloc] init];
|
||||
|
||||
components.textStorage = textStorage;
|
||||
|
||||
components.layoutManager = layoutManager;
|
||||
[components.textStorage addLayoutManager:components.layoutManager];
|
||||
|
||||
components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize];
|
||||
components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view.
|
||||
[components.layoutManager addTextContainer:components.textContainer];
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
+ (BOOL)needsMainThreadDeallocation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Nil out all delegates to prevent crash
|
||||
if (_textView) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
_textView.delegate = nil;
|
||||
}
|
||||
_layoutManager.delegate = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Sizing
|
||||
|
||||
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
|
||||
{
|
||||
ASTextKitComponents *components = self;
|
||||
|
||||
// If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation.
|
||||
// Otherwise, we create a temporary stack to size for `constrainedWidth`.
|
||||
if (CGRectGetWidth(components.textView.threadSafeBounds) != constrainedWidth) {
|
||||
components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)];
|
||||
}
|
||||
|
||||
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:).
|
||||
[components.layoutManager ensureLayoutForTextContainer:components.textContainer];
|
||||
CGSize textSize = [components.layoutManager usedRectForTextContainer:components.textContainer].size;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
|
||||
forMaxNumberOfLines:(NSInteger)maxNumberOfLines
|
||||
{
|
||||
if (maxNumberOfLines == 0) {
|
||||
return [self sizeForConstrainedWidth:constrainedWidth];
|
||||
}
|
||||
|
||||
ASTextKitComponents *components = self;
|
||||
|
||||
// Always use temporary stack in case of threading issues
|
||||
components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)];
|
||||
|
||||
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:).
|
||||
[components.layoutManager ensureLayoutForTextContainer:components.textContainer];
|
||||
|
||||
CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width;
|
||||
|
||||
// Calculate height based on line fragments
|
||||
// Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220
|
||||
NSRange glyphRange, lineRange = NSMakeRange(0, 0);
|
||||
CGRect rect = CGRectZero;
|
||||
CGFloat height = 0;
|
||||
CGFloat lastOriginY = -1.0;
|
||||
NSUInteger numberOfLines = 0;
|
||||
|
||||
glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer];
|
||||
|
||||
while (lineRange.location < NSMaxRange(glyphRange)) {
|
||||
rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location
|
||||
effectiveRange:&lineRange];
|
||||
|
||||
if (CGRectGetMinY(rect) > lastOriginY) {
|
||||
++numberOfLines;
|
||||
if (numberOfLines == maxNumberOfLines) {
|
||||
height = rect.origin.y + rect.size.height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lastOriginY = CGRectGetMinY(rect);
|
||||
lineRange.location = NSMaxRange(lineRange);
|
||||
}
|
||||
|
||||
CGFloat fragmentHeight = rect.origin.y + rect.size.height;
|
||||
CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight));
|
||||
|
||||
CGSize size = CGSizeMake(width, finalHeight);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@end
|
53
submodules/AsyncDisplayKit/Source/ASTextKitContext.h
Normal file
53
submodules/AsyncDisplayKit/Source/ASTextKitContext.h
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// ASTextKitContext.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
/**
|
||||
A threadsafe container for the TextKit components that ASTextKit uses to lay out and truncate its text.
|
||||
|
||||
This container is the sole owner and manager of the TextKit classes. This is an important model because of major
|
||||
thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods.
|
||||
*/
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASTextKitContext : NSObject
|
||||
|
||||
/**
|
||||
Initializes a context and its associated TextKit components.
|
||||
|
||||
Initialization of TextKit components is a globally locking operation so be careful of bottlenecks with this class.
|
||||
*/
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
lineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
constrainedSize:(CGSize)constrainedSize;
|
||||
|
||||
/**
|
||||
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
|
||||
TextKit components may cause crashes.
|
||||
|
||||
The block provided MUST not call out to client code from within its scope or it is possible for this to cause deadlocks
|
||||
in your application. Use with EXTREME care.
|
||||
|
||||
Callers MUST NOT keep a ref to these internal objects and use them later. This WILL cause crashes in your application.
|
||||
*/
|
||||
- (void)performBlockWithLockedTextKitComponents:(AS_NOESCAPE void (^)(NSLayoutManager *layoutManager,
|
||||
NSTextStorage *textStorage,
|
||||
NSTextContainer *textContainer))block;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
84
submodules/AsyncDisplayKit/Source/ASTextKitContext.mm
Normal file
84
submodules/AsyncDisplayKit/Source/ASTextKitContext.mm
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// ASTextKitContext.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTextKitContext.h"
|
||||
|
||||
#if AS_ENABLE_TEXTNODE
|
||||
|
||||
#import "ASLayoutManager.h"
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@implementation ASTextKitContext
|
||||
{
|
||||
// All TextKit operations (even non-mutative ones) must be executed serially.
|
||||
std::shared_ptr<AS::Mutex> __instanceLock__;
|
||||
|
||||
NSLayoutManager *_layoutManager;
|
||||
NSTextStorage *_textStorage;
|
||||
NSTextContainer *_textContainer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
|
||||
lineBreakMode:(NSLineBreakMode)lineBreakMode
|
||||
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
exclusionPaths:(NSArray *)exclusionPaths
|
||||
constrainedSize:(CGSize)constrainedSize
|
||||
|
||||
{
|
||||
if (self = [super init]) {
|
||||
static dispatch_once_t onceToken;
|
||||
static AS::Mutex *mutex;
|
||||
dispatch_once(&onceToken, ^{
|
||||
mutex = new AS::Mutex();
|
||||
});
|
||||
|
||||
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
|
||||
AS::MutexLocker l(*mutex);
|
||||
|
||||
__instanceLock__ = std::make_shared<AS::Mutex>();
|
||||
|
||||
// Create the TextKit component stack with our default configuration.
|
||||
|
||||
_textStorage = [[NSTextStorage alloc] init];
|
||||
_layoutManager = [[ASLayoutManager alloc] init];
|
||||
_layoutManager.usesFontLeading = NO;
|
||||
[_textStorage addLayoutManager:_layoutManager];
|
||||
|
||||
// Instead of calling [NSTextStorage initWithAttributedString:], setting attributedString just after calling addlayoutManager can fix CJK language layout issues.
|
||||
// See https://github.com/facebook/AsyncDisplayKit/issues/2894
|
||||
if (attributedString) {
|
||||
[_textStorage setAttributedString:attributedString];
|
||||
}
|
||||
|
||||
_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
|
||||
// We want the text laid out up to the very edges of the container.
|
||||
_textContainer.lineFragmentPadding = 0;
|
||||
_textContainer.lineBreakMode = lineBreakMode;
|
||||
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
|
||||
_textContainer.exclusionPaths = exclusionPaths;
|
||||
[_layoutManager addTextContainer:_textContainer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performBlockWithLockedTextKitComponents:(NS_NOESCAPE void (^)(NSLayoutManager *,
|
||||
NSTextStorage *,
|
||||
NSTextContainer *))block
|
||||
{
|
||||
AS::MutexLocker l(*__instanceLock__);
|
||||
if (block) {
|
||||
block(_layoutManager, _textStorage, _textContainer);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
34
submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h
Normal file
34
submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// ASTextNodeCommon.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#define AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE() { \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
NSLog(@"[Texture] Warning: Feature %@ is unimplemented in %@.", NSStringFromSelector(_cmd), NSStringFromClass(self.class)); \
|
||||
});\
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight styles.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
|
||||
/**
|
||||
* Highlight style for text on a light background.
|
||||
*/
|
||||
ASTextNodeHighlightStyleLight,
|
||||
|
||||
/**
|
||||
* Highlight style for text on a dark background.
|
||||
*/
|
||||
ASTextNodeHighlightStyleDark
|
||||
};
|
||||
|
37
submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h
Normal file
37
submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// ASTextNodeWordKerner.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/NSLayoutManager.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@abstract This class acts as the NSLayoutManagerDelegate for ASTextNode.
|
||||
@discussion Its current job is word kerning, i.e. adjusting the width of spaces to match the set
|
||||
wordKernedSpaceWidth. If word kerning is not needed, set the layoutManager's delegate to nil.
|
||||
*/
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASTextNodeWordKerner : NSObject <NSLayoutManagerDelegate>
|
||||
|
||||
/**
|
||||
The following @optional NSLayoutManagerDelegate methods are implemented:
|
||||
|
||||
- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)props characterIndexes:(const NSUInteger *)charIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange NS_AVAILABLE_IOS(7_0);
|
||||
|
||||
- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)action forControlCharacterAtIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0);
|
||||
|
||||
- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0);
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
130
submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm
Normal file
130
submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm
Normal file
@ -0,0 +1,130 @@
|
||||
//
|
||||
// ASTextNodeWordKerner.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASTextNodeWordKerner.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTextNodeTypes.h>
|
||||
|
||||
@implementation ASTextNodeWordKerner
|
||||
|
||||
#pragma mark - NSLayoutManager Delegate
|
||||
- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)properties characterIndexes:(const NSUInteger *)characterIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange
|
||||
{
|
||||
NSUInteger glyphCount = glyphRange.length;
|
||||
NSGlyphProperty *newGlyphProperties = NULL;
|
||||
|
||||
BOOL usesWordKerning = NO;
|
||||
|
||||
// If our typing attributes specify word kerning, specify the spaces as whitespace control characters so we can customize their width.
|
||||
// Are any of the characters spaces?
|
||||
NSString *textStorageString = layoutManager.textStorage.string;
|
||||
for (NSUInteger arrayIndex = 0; arrayIndex < glyphCount; arrayIndex++) {
|
||||
NSUInteger characterIndex = characterIndexes[arrayIndex];
|
||||
if ([textStorageString characterAtIndex:characterIndex] != ' ')
|
||||
continue;
|
||||
|
||||
// If we've set the whitespace control character for this space already, we have nothing to do.
|
||||
if (properties[arrayIndex] == NSGlyphPropertyControlCharacter) {
|
||||
usesWordKerning = YES;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create new glyph properties, if necessary.
|
||||
if (!newGlyphProperties) {
|
||||
newGlyphProperties = (NSGlyphProperty *)malloc(sizeof(NSGlyphProperty) * glyphCount);
|
||||
memcpy(newGlyphProperties, properties, (sizeof(NSGlyphProperty) * glyphCount));
|
||||
}
|
||||
|
||||
// It's a space. Make it a whitespace control character.
|
||||
newGlyphProperties[arrayIndex] = NSGlyphPropertyControlCharacter;
|
||||
}
|
||||
|
||||
// If we don't have any custom glyph properties, return 0 to indicate to the layout manager that it should use the standard glyphs+properties.
|
||||
if (!newGlyphProperties) {
|
||||
if (usesWordKerning) {
|
||||
// If the text does use word kerning we have to make sure we return the correct glyphCount, or the layout manager will just use the default properties and ignore our kerning.
|
||||
[layoutManager setGlyphs:glyphs properties:properties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange];
|
||||
return glyphCount;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, use our custom glyph properties.
|
||||
[layoutManager setGlyphs:glyphs properties:newGlyphProperties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange];
|
||||
free(newGlyphProperties);
|
||||
|
||||
return glyphCount;
|
||||
}
|
||||
|
||||
- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)defaultAction forControlCharacterAtIndex:(NSUInteger)characterIndex
|
||||
{
|
||||
// If it's a space character and we have custom word kerning, use the whitespace action control character.
|
||||
if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ')
|
||||
return NSControlCharacterActionWhitespace;
|
||||
|
||||
return defaultAction;
|
||||
}
|
||||
|
||||
- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)characterIndex
|
||||
{
|
||||
CGFloat wordKernedSpaceWidth = [self _wordKernedSpaceWidthForCharacterAtIndex:characterIndex atGlyphPosition:glyphPosition forTextContainer:textContainer layoutManager:layoutManager];
|
||||
return CGRectMake(glyphPosition.x, glyphPosition.y, wordKernedSpaceWidth, CGRectGetHeight(proposedRect));
|
||||
}
|
||||
|
||||
- (CGFloat)_wordKernedSpaceWidthForCharacterAtIndex:(NSUInteger)characterIndex atGlyphPosition:(CGPoint)glyphPosition forTextContainer:(NSTextContainer *)textContainer layoutManager:(NSLayoutManager *)layoutManager
|
||||
{
|
||||
// We use a map table for pointer equality and non-copying keys.
|
||||
static NSMapTable *spaceSizes;
|
||||
// NSMapTable is a defined thread unsafe class, so we need to synchronize
|
||||
// access in a light manner. So we use dispatch_sync on this queue for all
|
||||
// access to the map table.
|
||||
static dispatch_queue_t mapQueue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
spaceSizes = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:1];
|
||||
mapQueue = dispatch_queue_create("org.AsyncDisplayKit.wordKerningQueue", DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
CGFloat ordinarySpaceWidth;
|
||||
UIFont *font = [layoutManager.textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
|
||||
CGFloat wordKerning = [[layoutManager.textStorage attribute:ASTextNodeWordKerningAttributeName atIndex:characterIndex effectiveRange:NULL] floatValue];
|
||||
__block NSNumber *ordinarySpaceSizeValue;
|
||||
dispatch_sync(mapQueue, ^{
|
||||
ordinarySpaceSizeValue = [spaceSizes objectForKey:font];
|
||||
});
|
||||
if (ordinarySpaceSizeValue == nil) {
|
||||
ordinarySpaceWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width;
|
||||
dispatch_async(mapQueue, ^{
|
||||
[spaceSizes setObject:@(ordinarySpaceWidth) forKey:font];
|
||||
});
|
||||
} else {
|
||||
ordinarySpaceWidth = [ordinarySpaceSizeValue floatValue];
|
||||
}
|
||||
|
||||
CGFloat totalKernedWidth = (ordinarySpaceWidth + wordKerning);
|
||||
|
||||
// TextKit normally handles whitespace by increasing the advance of the previous glyph, rather than displaying an
|
||||
// actual glyph for the whitespace itself. However, in order to implement word kerning, we explicitly require a
|
||||
// discrete glyph whose bounding box we can specify. The problem is that TextKit does not know this glyph is
|
||||
// invisible. From TextKit's perspective, this whitespace glyph is a glyph that MUST be displayed. Thus when it
|
||||
// comes to determining linebreaks, the width of this trailing whitespace glyph is considered. This causes
|
||||
// our text to wrap sooner than it otherwise would, as room is allocated at the end of each line for a glyph that
|
||||
// isn't actually visible. To implement our desired behavior, we check to see if the current whitespace glyph
|
||||
// would break to the next line. If it breaks to the next line, then this constitutes trailing whitespace, and
|
||||
// we specify enough room to fill up the remainder of the line, but nothing more.
|
||||
if (glyphPosition.x + totalKernedWidth > textContainer.size.width) {
|
||||
return (textContainer.size.width - glyphPosition.x);
|
||||
}
|
||||
|
||||
return totalKernedWidth;
|
||||
}
|
||||
|
||||
@end
|
256
submodules/AsyncDisplayKit/Source/ASTraitCollection.mm
Normal file
256
submodules/AsyncDisplayKit/Source/ASTraitCollection.mm
Normal file
@ -0,0 +1,256 @@
|
||||
//
|
||||
// ASTraitCollection.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASHashing.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
|
||||
#pragma mark - ASPrimitiveTraitCollection
|
||||
|
||||
void ASTraitCollectionPropagateDown(id<ASLayoutElement> element, ASPrimitiveTraitCollection traitCollection) {
|
||||
if (element) {
|
||||
element.primitiveTraitCollection = traitCollection;
|
||||
}
|
||||
|
||||
for (id<ASLayoutElement> subelement in element.sublayoutElements) {
|
||||
ASTraitCollectionPropagateDown(subelement, traitCollection);
|
||||
}
|
||||
}
|
||||
|
||||
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() {
|
||||
ASPrimitiveTraitCollection tc = {};
|
||||
tc.userInterfaceIdiom = UIUserInterfaceIdiomUnspecified;
|
||||
tc.forceTouchCapability = UIForceTouchCapabilityUnknown;
|
||||
tc.displayScale = 0.0;
|
||||
tc.horizontalSizeClass = UIUserInterfaceSizeClassUnspecified;
|
||||
tc.verticalSizeClass = UIUserInterfaceSizeClassUnspecified;
|
||||
tc.containerSize = CGSizeZero;
|
||||
if (AS_AVAILABLE_IOS(10)) {
|
||||
tc.displayGamut = UIDisplayGamutUnspecified;
|
||||
tc.preferredContentSizeCategory = UIContentSizeCategoryUnspecified;
|
||||
tc.layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified;
|
||||
}
|
||||
#if AS_BUILD_UIUSERINTERFACESTYLE
|
||||
if (AS_AVAILABLE_IOS_TVOS(12, 10)) {
|
||||
tc.userInterfaceStyle = UIUserInterfaceStyleUnspecified;
|
||||
}
|
||||
#endif
|
||||
return tc;
|
||||
}
|
||||
|
||||
ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) {
|
||||
ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault();
|
||||
environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass;
|
||||
environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass;
|
||||
environmentTraitCollection.displayScale = traitCollection.displayScale;
|
||||
environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom;
|
||||
environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability;
|
||||
if (AS_AVAILABLE_IOS(10)) {
|
||||
environmentTraitCollection.displayGamut = traitCollection.displayGamut;
|
||||
environmentTraitCollection.layoutDirection = traitCollection.layoutDirection;
|
||||
|
||||
ASDisplayNodeCAssertPermanent(traitCollection.preferredContentSizeCategory);
|
||||
environmentTraitCollection.preferredContentSizeCategory = traitCollection.preferredContentSizeCategory;
|
||||
}
|
||||
#if AS_BUILD_UIUSERINTERFACESTYLE
|
||||
if (AS_AVAILABLE_IOS_TVOS(12, 10)) {
|
||||
environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle;
|
||||
}
|
||||
#endif
|
||||
return environmentTraitCollection;
|
||||
}
|
||||
|
||||
BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) {
|
||||
return !memcmp(&lhs, &rhs, sizeof(ASPrimitiveTraitCollection));
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) {
|
||||
switch (idiom) {
|
||||
case UIUserInterfaceIdiomTV:
|
||||
return @"TV";
|
||||
case UIUserInterfaceIdiomPad:
|
||||
return @"Pad";
|
||||
case UIUserInterfaceIdiomPhone:
|
||||
return @"Phone";
|
||||
case UIUserInterfaceIdiomCarPlay:
|
||||
return @"CarPlay";
|
||||
default:
|
||||
return @"Unspecified";
|
||||
}
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIForceTouchCapability(UIForceTouchCapability capability) {
|
||||
switch (capability) {
|
||||
case UIForceTouchCapabilityAvailable:
|
||||
return @"Available";
|
||||
case UIForceTouchCapabilityUnavailable:
|
||||
return @"Unavailable";
|
||||
default:
|
||||
return @"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInterfaceSizeClass sizeClass) {
|
||||
switch (sizeClass) {
|
||||
case UIUserInterfaceSizeClassCompact:
|
||||
return @"Compact";
|
||||
case UIUserInterfaceSizeClassRegular:
|
||||
return @"Regular";
|
||||
default:
|
||||
return @"Unspecified";
|
||||
}
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
API_AVAILABLE(ios(10))
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) {
|
||||
switch (displayGamut) {
|
||||
case UIDisplayGamutSRGB:
|
||||
return @"sRGB";
|
||||
case UIDisplayGamutP3:
|
||||
return @"P3";
|
||||
default:
|
||||
return @"Unspecified";
|
||||
}
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
API_AVAILABLE(ios(10))
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) {
|
||||
switch (layoutDirection) {
|
||||
case UITraitEnvironmentLayoutDirectionLeftToRight:
|
||||
return @"LeftToRight";
|
||||
case UITraitEnvironmentLayoutDirectionRightToLeft:
|
||||
return @"RightToLeft";
|
||||
default:
|
||||
return @"Unspecified";
|
||||
}
|
||||
}
|
||||
|
||||
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
|
||||
#if AS_BUILD_UIUSERINTERFACESTYLE
|
||||
API_AVAILABLE(tvos(10.0), ios(12.0))
|
||||
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) {
|
||||
switch (userInterfaceStyle) {
|
||||
case UIUserInterfaceStyleLight:
|
||||
return @"Light";
|
||||
case UIUserInterfaceStyleDark:
|
||||
return @"Dark";
|
||||
default:
|
||||
return @"Unspecified";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) {
|
||||
NSMutableArray<NSDictionary *> *props = [NSMutableArray array];
|
||||
[props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }];
|
||||
[props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }];
|
||||
[props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }];
|
||||
[props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }];
|
||||
[props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }];
|
||||
#if AS_BUILD_UIUSERINTERFACESTYLE
|
||||
if (AS_AVAILABLE_IOS_TVOS(12, 10)) {
|
||||
[props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }];
|
||||
}
|
||||
#endif
|
||||
if (AS_AVAILABLE_IOS(10)) {
|
||||
[props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }];
|
||||
[props addObject:@{ @"preferredContentSizeCategory": traits.preferredContentSizeCategory }];
|
||||
[props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }];
|
||||
}
|
||||
[props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }];
|
||||
return ASObjectDescriptionMakeWithoutObject(props);
|
||||
}
|
||||
|
||||
#pragma mark - ASTraitCollection
|
||||
|
||||
@implementation ASTraitCollection {
|
||||
ASPrimitiveTraitCollection _prim;
|
||||
}
|
||||
|
||||
+ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits NS_RETURNS_RETAINED {
|
||||
ASTraitCollection *tc = [[ASTraitCollection alloc] init];
|
||||
if (AS_AVAILABLE_IOS(10)) {
|
||||
ASDisplayNodeCAssertPermanent(traits.preferredContentSizeCategory);
|
||||
}
|
||||
tc->_prim = traits;
|
||||
return tc;
|
||||
}
|
||||
|
||||
- (ASPrimitiveTraitCollection)primitiveTraitCollection {
|
||||
return _prim;
|
||||
}
|
||||
- (UIUserInterfaceSizeClass)horizontalSizeClass
|
||||
{
|
||||
return _prim.horizontalSizeClass;
|
||||
}
|
||||
-(UIUserInterfaceSizeClass)verticalSizeClass
|
||||
{
|
||||
return _prim.verticalSizeClass;
|
||||
}
|
||||
- (CGFloat)displayScale
|
||||
{
|
||||
return _prim.displayScale;
|
||||
}
|
||||
- (UIDisplayGamut)displayGamut
|
||||
{
|
||||
return _prim.displayGamut;
|
||||
}
|
||||
- (UIForceTouchCapability)forceTouchCapability
|
||||
{
|
||||
return _prim.forceTouchCapability;
|
||||
}
|
||||
- (UITraitEnvironmentLayoutDirection)layoutDirection
|
||||
{
|
||||
return _prim.layoutDirection;
|
||||
}
|
||||
- (CGSize)containerSize
|
||||
{
|
||||
return _prim.containerSize;
|
||||
}
|
||||
#if AS_BUILD_UIUSERINTERFACESTYLE
|
||||
- (UIUserInterfaceStyle)userInterfaceStyle
|
||||
{
|
||||
return _prim.userInterfaceStyle;
|
||||
}
|
||||
#endif
|
||||
- (UIContentSizeCategory)preferredContentSizeCategory
|
||||
{
|
||||
return _prim.preferredContentSizeCategory;
|
||||
}
|
||||
- (NSUInteger)hash {
|
||||
return ASHashBytes(&_prim, sizeof(ASPrimitiveTraitCollection));
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (!object || ![object isKindOfClass:ASTraitCollection.class]) {
|
||||
return NO;
|
||||
}
|
||||
return [self isEqualToTraitCollection:object];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection
|
||||
{
|
||||
if (traitCollection == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (self == traitCollection) {
|
||||
return YES;
|
||||
}
|
||||
return ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_prim, traitCollection->_prim);
|
||||
}
|
||||
|
||||
@end
|
59
submodules/AsyncDisplayKit/Source/ASWeakMap.h
Normal file
59
submodules/AsyncDisplayKit/Source/ASWeakMap.h
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// ASWeakMap.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
/**
|
||||
* This class is used in conjunction with ASWeakMap. Instances of this type are returned by an ASWeakMap,
|
||||
* must retain this value for as long as they want the entry to exist in the map.
|
||||
*/
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASWeakMapEntry<Value> : NSObject
|
||||
|
||||
@property (readonly) Value value;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* This is not a full-featured map. It does not support features like `count` and FastEnumeration because there
|
||||
* is not currently a need.
|
||||
*
|
||||
* This is a map that does not retain keys or values added to it. When both getting and setting, the caller is
|
||||
* returned a ASWeakMapEntry and must retain it for as long as it wishes the key/value to remain in the map.
|
||||
* We return a single Entry value to the caller to avoid two potential problems:
|
||||
*
|
||||
* 1) It's easier for callers to retain one value (the Entry) and not two (a key and a value).
|
||||
* 2) Key values are tested for `isEqual` equality. If if a caller asks for a key "A" that is equal to a key "B"
|
||||
* already in the map, then we need the caller to retain key "B" and not key "A". Returning an Entry simplifies
|
||||
* the semantics.
|
||||
*
|
||||
* The underlying storage is a hash table and the Key type should implement `hash` and `isEqual:`.
|
||||
*/
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASWeakMap<__covariant Key, Value> : NSObject
|
||||
|
||||
/**
|
||||
* Read from the cache. The Value object is accessible from the returned ASWeakMapEntry.
|
||||
*/
|
||||
- (nullable ASWeakMapEntry<Value> *)entryForKey:(Key)key AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Put a value into the cache. If an entry with an equal key already exists, then the value is updated on the existing entry.
|
||||
*/
|
||||
- (ASWeakMapEntry<Value> *)setObject:(Value)value forKey:(Key)key AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
78
submodules/AsyncDisplayKit/Source/ASWeakMap.mm
Normal file
78
submodules/AsyncDisplayKit/Source/ASWeakMap.mm
Normal file
@ -0,0 +1,78 @@
|
||||
//
|
||||
// ASWeakMap.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASWeakMap.h"
|
||||
|
||||
@interface ASWeakMapEntry ()
|
||||
@property (nonatomic, readonly) id key;
|
||||
@property id value;
|
||||
@end
|
||||
|
||||
@implementation ASWeakMapEntry
|
||||
|
||||
- (instancetype)initWithKey:(id)key value:(id)value
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_key = key;
|
||||
_value = value;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ASWeakMap ()
|
||||
@property (nonatomic, readonly) NSMapTable<id, ASWeakMapEntry *> *hashTable;
|
||||
@end
|
||||
|
||||
/**
|
||||
* Implementation details:
|
||||
*
|
||||
* The retained size of our keys is potentially very large (for example, a UIImage is commonly part of a key).
|
||||
* Unfortunately, NSMapTable does not make guarantees about how quickly it will dispose of entries where
|
||||
* either the key or the value is weak and has been disposed. So, a NSMapTable with "strong key to weak value" is
|
||||
* unsuitable for our purpose because the strong keys are retained longer than the value and for an indefininte period of time.
|
||||
* More details here: http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
|
||||
*
|
||||
* Our NSMapTable is "weak key to weak value" where each key maps to an Entry. The Entry object is responsible
|
||||
* for retaining both the key and value. Our convention is that the caller must retain the Entry object
|
||||
* in order to keep the key and the value in the cache.
|
||||
*/
|
||||
@implementation ASWeakMap
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_hashTable = [NSMapTable weakToWeakObjectsMapTable];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASWeakMapEntry *)entryForKey:(id)key
|
||||
{
|
||||
return [self.hashTable objectForKey:key];
|
||||
}
|
||||
|
||||
- (ASWeakMapEntry *)setObject:(id)value forKey:(id)key
|
||||
{
|
||||
ASWeakMapEntry *entry = [self.hashTable objectForKey:key];
|
||||
if (entry != nil) {
|
||||
// Update the value in the existing entry.
|
||||
entry.value = value;
|
||||
} else {
|
||||
entry = [[ASWeakMapEntry alloc] initWithKey:key value:value];
|
||||
[self.hashTable setObject:entry forKey:key];
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@end
|
32
submodules/AsyncDisplayKit/Source/ASWeakProxy.h
Normal file
32
submodules/AsyncDisplayKit/Source/ASWeakProxy.h
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// ASWeakProxy.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASWeakProxy : NSProxy
|
||||
|
||||
/**
|
||||
* @return target The target which will be forwarded all messages sent to the weak proxy.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) id target;
|
||||
|
||||
/**
|
||||
* An object which forwards messages to a target which it weakly references
|
||||
*
|
||||
* @discussion This class is useful for breaking retain cycles. You can pass this in place
|
||||
* of the target to something which creates a strong reference. All messages sent to the
|
||||
* proxy will be passed onto the target.
|
||||
*
|
||||
* @return an instance of ASWeakProxy
|
||||
*/
|
||||
+ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED;
|
||||
|
||||
@end
|
72
submodules/AsyncDisplayKit/Source/ASWeakProxy.mm
Normal file
72
submodules/AsyncDisplayKit/Source/ASWeakProxy.mm
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// ASWeakProxy.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASWeakProxy.h"
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
@implementation ASWeakProxy
|
||||
|
||||
- (instancetype)initWithTarget:(id)target
|
||||
{
|
||||
if (self) {
|
||||
_target = target;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[ASWeakProxy alloc] initWithTarget:target];
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)aSelector
|
||||
{
|
||||
return [_target respondsToSelector:aSelector];
|
||||
}
|
||||
|
||||
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
|
||||
{
|
||||
return [_target conformsToProtocol:aProtocol];
|
||||
}
|
||||
|
||||
/// Strangely, this method doesn't get forwarded by ObjC.
|
||||
- (BOOL)isKindOfClass:(Class)aClass
|
||||
{
|
||||
return [_target isKindOfClass:aClass];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, @[@{ @"target": _target ?: (id)kCFNull }]);
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
|
||||
{
|
||||
ASDisplayNodeAssertNil(_target, @"ASWeakProxy got %@ when its target is still alive, which is unexpected.", NSStringFromSelector(_cmd));
|
||||
// Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature
|
||||
// from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found.
|
||||
// This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method
|
||||
// returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines
|
||||
// the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will
|
||||
// suffice. Since the -forwardInvocation call will do nothing if the target does not respond to the selector,
|
||||
// the dud NSMethodSignature simply gets us around the exception.
|
||||
return [NSMethodSignature signatureWithObjCTypes:"@^v^c"];
|
||||
}
|
||||
- (void)forwardInvocation:(NSInvocation *)invocation
|
||||
{
|
||||
ASDisplayNodeAssertNil(_target, @"ASWeakProxy got %@ when its target is still alive, which is unexpected.", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
@end
|
84
submodules/AsyncDisplayKit/Source/ASWeakSet.mm
Normal file
84
submodules/AsyncDisplayKit/Source/ASWeakSet.mm
Normal file
@ -0,0 +1,84 @@
|
||||
//
|
||||
// ASWeakSet.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
|
||||
@interface ASWeakSet<__covariant ObjectType> ()
|
||||
@property (nonatomic, readonly) NSHashTable<ObjectType> *hashTable;
|
||||
@end
|
||||
|
||||
@implementation ASWeakSet
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_hashTable = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory | NSHashTableObjectPointerPersonality];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addObject:(id)object
|
||||
{
|
||||
[_hashTable addObject:object];
|
||||
}
|
||||
|
||||
- (void)removeObject:(id)object
|
||||
{
|
||||
[_hashTable removeObject:object];
|
||||
}
|
||||
|
||||
- (void)removeAllObjects
|
||||
{
|
||||
[_hashTable removeAllObjects];
|
||||
}
|
||||
|
||||
- (NSArray *)allObjects
|
||||
{
|
||||
return _hashTable.allObjects;
|
||||
}
|
||||
|
||||
- (BOOL)containsObject:(id)object
|
||||
{
|
||||
return [_hashTable containsObject:object];
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty
|
||||
{
|
||||
return [_hashTable anyObject] == nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Note: The `count` property of NSHashTable is unreliable
|
||||
in the case of weak-memory hash tables because entries
|
||||
that have been deallocated are not removed immediately.
|
||||
|
||||
In order to get the true count we have to fall back to using
|
||||
fast enumeration.
|
||||
*/
|
||||
- (NSUInteger)count
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
for (__unused id object in _hashTable) {
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id _Nonnull *)buffer count:(NSUInteger)len
|
||||
{
|
||||
return [_hashTable countByEnumeratingWithState:state objects:buffer count:len];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [[super description] stringByAppendingFormat:@" count: %tu, contents: %@", self.count, _hashTable];
|
||||
}
|
||||
|
||||
@end
|
177
submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm
Normal file
177
submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm
Normal file
@ -0,0 +1,177 @@
|
||||
//
|
||||
// NSArray+Diffing.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/NSArray+Diffing.h>
|
||||
#import <UIKit/NSIndexPath+UIKitAdditions.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <unordered_map>
|
||||
|
||||
@implementation NSArray (Diffing)
|
||||
|
||||
typedef BOOL (^compareBlock)(id _Nonnull lhs, id _Nonnull rhs);
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions
|
||||
{
|
||||
[self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:nil compareBlock:[NSArray defaultCompareBlock]];
|
||||
}
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions
|
||||
compareBlock:(compareBlock)comparison
|
||||
{
|
||||
[self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:nil compareBlock:comparison];
|
||||
}
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions
|
||||
moves:(NSArray<NSIndexPath *> **)moves
|
||||
{
|
||||
[self asdk_diffWithArray:array insertions:insertions deletions:deletions moves:moves
|
||||
compareBlock:[NSArray defaultCompareBlock]];
|
||||
}
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions
|
||||
moves:(NSArray<NSIndexPath *> **)moves compareBlock:(compareBlock)comparison
|
||||
{
|
||||
struct NSObjectHash
|
||||
{
|
||||
std::size_t operator()(id <NSObject> k) const { return (std::size_t) [k hash]; };
|
||||
};
|
||||
struct NSObjectCompare
|
||||
{
|
||||
bool operator()(id <NSObject> lhs, id <NSObject> rhs) const { return (bool) [lhs isEqual:rhs]; };
|
||||
};
|
||||
std::unordered_multimap<unowned id, NSUInteger, NSObjectHash, NSObjectCompare> potentialMoves;
|
||||
|
||||
NSAssert(comparison != nil, @"Comparison block is required");
|
||||
NSAssert(moves == nil || comparison == [NSArray defaultCompareBlock], @"move detection requires isEqual: and hash (no custom compare)");
|
||||
NSMutableArray<NSIndexPath *> *moveIndexPaths = nil;
|
||||
NSMutableIndexSet *insertionIndexes = nil, *deletionIndexes = nil;
|
||||
if (moves) {
|
||||
moveIndexPaths = [NSMutableArray new];
|
||||
}
|
||||
NSMutableIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison];
|
||||
|
||||
if (deletions || moves) {
|
||||
deletionIndexes = [NSMutableIndexSet indexSet];
|
||||
NSUInteger i = 0;
|
||||
for (id element in self) {
|
||||
if (![commonIndexes containsIndex:i]) {
|
||||
[deletionIndexes addIndex:i];
|
||||
}
|
||||
if (moves) {
|
||||
potentialMoves.insert(std::pair<id, NSUInteger>(element, i));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertions || moves) {
|
||||
insertionIndexes = [NSMutableIndexSet indexSet];
|
||||
NSArray *commonObjects = [self objectsAtIndexes:commonIndexes];
|
||||
for (NSUInteger i = 0, j = 0; j < array.count; j++) {
|
||||
auto moveFound = potentialMoves.find(array[j]);
|
||||
NSUInteger movedFrom = NSNotFound;
|
||||
if (moveFound != potentialMoves.end() && moveFound->second != j) {
|
||||
movedFrom = moveFound->second;
|
||||
potentialMoves.erase(moveFound);
|
||||
[moveIndexPaths addObject:[NSIndexPath indexPathForItem:j inSection:movedFrom]];
|
||||
}
|
||||
if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) {
|
||||
i++;
|
||||
} else {
|
||||
if (movedFrom != NSNotFound) {
|
||||
// moves will coalesce a delete / insert - the insert is just not done, and here we remove the delete:
|
||||
[deletionIndexes removeIndex:movedFrom];
|
||||
// OR a move will have come from the LCS:
|
||||
if ([commonIndexes containsIndex:movedFrom]) {
|
||||
[commonIndexes removeIndex:movedFrom];
|
||||
commonObjects = [self objectsAtIndexes:commonIndexes];
|
||||
}
|
||||
} else {
|
||||
[insertionIndexes addIndex:j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moves) {*moves = moveIndexPaths;}
|
||||
if (deletions) {*deletions = deletionIndexes;}
|
||||
if (insertions) {*insertions = insertionIndexes;}
|
||||
}
|
||||
|
||||
// https://github.com/raywenderlich/swift-algorithm-club/tree/master/Longest%20Common%20Subsequence is not exactly this code (obviously), but
|
||||
// is a good commentary on the algorithm.
|
||||
- (NSMutableIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison
|
||||
{
|
||||
NSAssert(comparison != nil, @"Comparison block is required");
|
||||
|
||||
NSInteger selfCount = self.count;
|
||||
NSInteger arrayCount = array.count;
|
||||
|
||||
// Allocate the diff map in the heap so we don't blow the stack for large arrays.
|
||||
NSInteger **lengths = NULL;
|
||||
lengths = (NSInteger **)malloc(sizeof(NSInteger*) * (selfCount+1));
|
||||
if (lengths == NULL) {
|
||||
ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing");
|
||||
return nil;
|
||||
}
|
||||
// Fill in a LCS length matrix:
|
||||
for (NSInteger i = 0; i <= selfCount; i++) {
|
||||
lengths[i] = (NSInteger *)malloc(sizeof(NSInteger) * (arrayCount+1));
|
||||
if (lengths[i] == NULL) {
|
||||
ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing");
|
||||
return nil;
|
||||
}
|
||||
id selfObj = i > 0 ? self[i-1] : nil;
|
||||
for (NSInteger j = 0; j <= arrayCount; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
lengths[i][j] = 0;
|
||||
} else if (comparison(selfObj, array[j-1])) {
|
||||
lengths[i][j] = 1 + lengths[i-1][j-1];
|
||||
} else {
|
||||
lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Backtrack to fill in indices based on length matrix:
|
||||
NSMutableIndexSet *common = [NSMutableIndexSet indexSet];
|
||||
NSInteger i = selfCount, j = arrayCount;
|
||||
while(i > 0 && j > 0) {
|
||||
if (comparison(self[i-1], array[j-1])) {
|
||||
[common addIndex:(i-1)];
|
||||
i--; j--;
|
||||
} else if (lengths[i-1][j] > lengths[i][j-1]) {
|
||||
i--;
|
||||
} else {
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
for (NSInteger i = 0; i <= selfCount; i++) {
|
||||
free(lengths[i]);
|
||||
}
|
||||
free(lengths);
|
||||
return common;
|
||||
}
|
||||
|
||||
static compareBlock defaultCompare = nil;
|
||||
|
||||
+ (compareBlock)defaultCompareBlock
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
defaultCompare = ^BOOL(id lhs, id rhs) {
|
||||
return [lhs isEqual:rhs];
|
||||
};
|
||||
});
|
||||
|
||||
return defaultCompare;
|
||||
}
|
||||
|
||||
@end
|
29
submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h
Normal file
29
submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// NSIndexSet+ASHelpers.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSIndexSet (ASHelpers)
|
||||
|
||||
- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger idx))block;
|
||||
|
||||
- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes;
|
||||
|
||||
/// Returns all the item indexes from the given index paths that are in the given section.
|
||||
+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths inSection:(NSUInteger)section;
|
||||
|
||||
/// If you've got an old index, and you insert items using this index set, this returns the change to get to the new index.
|
||||
- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index;
|
||||
|
||||
- (NSString *)as_smallDescription;
|
||||
|
||||
/// Returns all the section indexes contained in the index paths array.
|
||||
+ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
@end
|
91
submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm
Normal file
91
submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// NSIndexSet+ASHelpers.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
// UIKit indexPath helpers
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "NSIndexSet+ASHelpers.h"
|
||||
|
||||
@implementation NSIndexSet (ASHelpers)
|
||||
|
||||
- (NSIndexSet *)as_indexesByMapping:(NSUInteger (^)(NSUInteger))block
|
||||
{
|
||||
NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init];
|
||||
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
NSUInteger newIndex = block(i);
|
||||
if (newIndex != NSNotFound) {
|
||||
[result addIndex:newIndex];
|
||||
}
|
||||
}
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)as_intersectionWithIndexes:(NSIndexSet *)indexes
|
||||
{
|
||||
NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init];
|
||||
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
[indexes enumerateRangesInRange:range options:kNilOptions usingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
[result addIndexesInRange:range];
|
||||
}];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSIndexSet *)as_indexSetFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths inSection:(NSUInteger)section
|
||||
{
|
||||
NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init];
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
if (indexPath.section == section) {
|
||||
[result addIndex:indexPath.item];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSUInteger)as_indexChangeByInsertingItemsBelowIndex:(NSUInteger)index
|
||||
{
|
||||
__block NSUInteger newIndex = index;
|
||||
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
if (i <= newIndex) {
|
||||
newIndex += 1;
|
||||
} else {
|
||||
*stop = YES;
|
||||
}
|
||||
}
|
||||
}];
|
||||
return newIndex - index;
|
||||
}
|
||||
|
||||
- (NSString *)as_smallDescription
|
||||
{
|
||||
NSMutableString *result = [NSMutableString stringWithString:@"{ "];
|
||||
[self enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
if (range.length == 1) {
|
||||
[result appendFormat:@"%tu ", range.location];
|
||||
} else {
|
||||
[result appendFormat:@"%tu-%tu ", range.location, NSMaxRange(range) - 1];
|
||||
}
|
||||
}];
|
||||
[result appendString:@"}"];
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSIndexSet *)as_sectionsFromIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
||||
{
|
||||
NSMutableIndexSet *result = [[NSMutableIndexSet alloc] init];
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
[result addIndex:indexPath.section];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
@ -989,4 +989,8 @@ typedef NS_ENUM(NSInteger, ASLayoutEngineType) {
|
||||
@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node;
|
||||
@end
|
||||
|
||||
@interface CALayer (ASDisplayNodeInternal)
|
||||
@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// UIResponder+AsyncDisplayKit.m
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/UIResponder+AsyncDisplayKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import "ASResponderChainEnumerator.h"
|
||||
|
||||
@implementation UIResponder (AsyncDisplayKit)
|
||||
|
||||
- (__kindof UIViewController *)asdk_associatedViewController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
for (UIResponder *responder in [self asdk_responderChainEnumerator]) {
|
||||
UIViewController *vc = ASDynamicCast(responder, UIViewController);
|
||||
if (vc) {
|
||||
return vc;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
465
submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm
Normal file
465
submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm
Normal file
@ -0,0 +1,465 @@
|
||||
//
|
||||
// _ASAsyncTransaction.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
|
||||
#import <AsyncDisplayKit/_ASAsyncTransaction.h>
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionGroup.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <list>
|
||||
#import <map>
|
||||
#import <mutex>
|
||||
|
||||
#ifndef __STRICT_ANSI__
|
||||
#warning "Texture must be compiled with std=c++11 to prevent layout issues. gnu++ is not supported. This is hopefully temporary."
|
||||
#endif
|
||||
|
||||
AS_EXTERN NSRunLoopMode const UITrackingRunLoopMode;
|
||||
|
||||
NSInteger const ASDefaultTransactionPriority = 0;
|
||||
|
||||
@interface ASAsyncTransactionOperation : NSObject
|
||||
- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock;
|
||||
@property (nonatomic) asyncdisplaykit_async_transaction_operation_completion_block_t operationCompletionBlock;
|
||||
@property id value; // set on bg queue by the operation block
|
||||
@end
|
||||
|
||||
@implementation ASAsyncTransactionOperation
|
||||
|
||||
- (instancetype)initWithOperationCompletionBlock:(asyncdisplaykit_async_transaction_operation_completion_block_t)operationCompletionBlock
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_operationCompletionBlock = operationCompletionBlock;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
NSAssert(_operationCompletionBlock == nil, @"Should have been called and released before -dealloc");
|
||||
}
|
||||
|
||||
- (void)callAndReleaseCompletionBlock:(BOOL)canceled;
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_operationCompletionBlock) {
|
||||
_operationCompletionBlock(self.value, canceled);
|
||||
// Guarantee that _operationCompletionBlock is released on main thread
|
||||
_operationCompletionBlock = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<ASAsyncTransactionOperation: %p - value = %@>", self, self.value];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Lightweight operation queue for _ASAsyncTransaction that limits number of spawned threads
|
||||
class ASAsyncTransactionQueue
|
||||
{
|
||||
public:
|
||||
|
||||
// Similar to dispatch_group_t
|
||||
class Group
|
||||
{
|
||||
public:
|
||||
// call when group is no longer needed; after last scheduled operation the group will delete itself
|
||||
virtual void release() = 0;
|
||||
|
||||
// schedule block on given queue
|
||||
virtual void schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block) = 0;
|
||||
|
||||
// dispatch block on given queue when all previously scheduled blocks finished executing
|
||||
virtual void notify(dispatch_queue_t queue, dispatch_block_t block) = 0;
|
||||
|
||||
// used when manually executing blocks
|
||||
virtual void enter() = 0;
|
||||
virtual void leave() = 0;
|
||||
|
||||
// wait until all scheduled blocks finished executing
|
||||
virtual void wait() = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Group() { }; // call release() instead
|
||||
};
|
||||
|
||||
// Create new group
|
||||
Group *createGroup();
|
||||
|
||||
static ASAsyncTransactionQueue &instance();
|
||||
|
||||
private:
|
||||
|
||||
struct GroupNotify
|
||||
{
|
||||
dispatch_block_t _block;
|
||||
dispatch_queue_t _queue;
|
||||
};
|
||||
|
||||
class GroupImpl : public Group
|
||||
{
|
||||
public:
|
||||
GroupImpl(ASAsyncTransactionQueue &queue)
|
||||
: _pendingOperations(0)
|
||||
, _releaseCalled(false)
|
||||
, _queue(queue)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void release();
|
||||
virtual void schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block);
|
||||
virtual void notify(dispatch_queue_t queue, dispatch_block_t block);
|
||||
virtual void enter();
|
||||
virtual void leave();
|
||||
virtual void wait();
|
||||
|
||||
int _pendingOperations;
|
||||
std::list<GroupNotify> _notifyList;
|
||||
std::condition_variable _condition;
|
||||
BOOL _releaseCalled;
|
||||
ASAsyncTransactionQueue &_queue;
|
||||
};
|
||||
|
||||
struct Operation
|
||||
{
|
||||
dispatch_block_t _block;
|
||||
GroupImpl *_group;
|
||||
NSInteger _priority;
|
||||
};
|
||||
|
||||
struct DispatchEntry // entry for each dispatch queue
|
||||
{
|
||||
typedef std::list<Operation> OperationQueue;
|
||||
typedef std::list<OperationQueue::iterator> OperationIteratorList; // each item points to operation queue
|
||||
typedef std::map<NSInteger, OperationIteratorList> OperationPriorityMap; // sorted by priority
|
||||
|
||||
OperationQueue _operationQueue;
|
||||
OperationPriorityMap _operationPriorityMap;
|
||||
int _threadCount;
|
||||
|
||||
Operation popNextOperation(bool respectPriority); // assumes locked mutex
|
||||
void pushOperation(Operation operation); // assumes locked mutex
|
||||
};
|
||||
|
||||
std::map<dispatch_queue_t, DispatchEntry> _entries;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
|
||||
ASAsyncTransactionQueue::Group* ASAsyncTransactionQueue::createGroup()
|
||||
{
|
||||
Group *res = new GroupImpl(*this);
|
||||
return res;
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::release()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_queue._mutex);
|
||||
|
||||
if (_pendingOperations == 0) {
|
||||
delete this;
|
||||
} else {
|
||||
_releaseCalled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
ASAsyncTransactionQueue::Operation ASAsyncTransactionQueue::DispatchEntry::popNextOperation(bool respectPriority)
|
||||
{
|
||||
NSCAssert(!_operationQueue.empty() && !_operationPriorityMap.empty(), @"No scheduled operations available");
|
||||
|
||||
OperationQueue::iterator queueIterator;
|
||||
OperationPriorityMap::iterator mapIterator;
|
||||
|
||||
if (respectPriority) {
|
||||
mapIterator = --_operationPriorityMap.end(); // highest priority "bucket"
|
||||
queueIterator = *mapIterator->second.begin();
|
||||
} else {
|
||||
queueIterator = _operationQueue.begin();
|
||||
mapIterator = _operationPriorityMap.find(queueIterator->_priority);
|
||||
}
|
||||
|
||||
// no matter what, first item in "bucket" must match item in queue
|
||||
NSCAssert(mapIterator->second.front() == queueIterator, @"Queue inconsistency");
|
||||
|
||||
Operation res = *queueIterator;
|
||||
_operationQueue.erase(queueIterator);
|
||||
|
||||
mapIterator->second.pop_front();
|
||||
if (mapIterator->second.empty()) {
|
||||
_operationPriorityMap.erase(mapIterator);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::DispatchEntry::pushOperation(ASAsyncTransactionQueue::Operation operation)
|
||||
{
|
||||
_operationQueue.push_back(operation);
|
||||
|
||||
OperationIteratorList &list = _operationPriorityMap[operation._priority];
|
||||
list.push_back(--_operationQueue.end());
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::schedule(NSInteger priority, dispatch_queue_t queue, dispatch_block_t block)
|
||||
{
|
||||
ASAsyncTransactionQueue &q = _queue;
|
||||
std::lock_guard<std::mutex> l(q._mutex);
|
||||
|
||||
DispatchEntry &entry = q._entries[queue];
|
||||
|
||||
Operation operation;
|
||||
operation._block = block;
|
||||
operation._group = this;
|
||||
operation._priority = priority;
|
||||
entry.pushOperation(operation);
|
||||
|
||||
++_pendingOperations; // enter group
|
||||
|
||||
#if ASDISPLAYNODE_DELAY_DISPLAY
|
||||
NSUInteger maxThreads = 1;
|
||||
#else
|
||||
NSUInteger maxThreads = [NSProcessInfo processInfo].activeProcessorCount * 2;
|
||||
|
||||
// Bit questionable maybe - we can give main thread more CPU time during tracking.
|
||||
if ([[NSRunLoop mainRunLoop].currentMode isEqualToString:UITrackingRunLoopMode])
|
||||
--maxThreads;
|
||||
#endif
|
||||
|
||||
if (entry._threadCount < maxThreads) { // we need to spawn another thread
|
||||
|
||||
// first thread will take operations in queue order (regardless of priority), other threads will respect priority
|
||||
bool respectPriority = entry._threadCount > 0;
|
||||
++entry._threadCount;
|
||||
|
||||
dispatch_async(queue, ^{
|
||||
std::unique_lock<std::mutex> lock(q._mutex);
|
||||
|
||||
// go until there are no more pending operations
|
||||
while (!entry._operationQueue.empty()) {
|
||||
Operation operation = entry.popNextOperation(respectPriority);
|
||||
lock.unlock();
|
||||
if (operation._block) {
|
||||
operation._block();
|
||||
}
|
||||
operation._group->leave();
|
||||
operation._block = nil; // the block must be freed while mutex is unlocked
|
||||
lock.lock();
|
||||
}
|
||||
--entry._threadCount;
|
||||
|
||||
if (entry._threadCount == 0) {
|
||||
NSCAssert(entry._operationQueue.empty() || entry._operationPriorityMap.empty(), @"No working threads but operations are still scheduled"); // this shouldn't happen
|
||||
q._entries.erase(queue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::notify(dispatch_queue_t queue, dispatch_block_t block)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_queue._mutex);
|
||||
|
||||
if (_pendingOperations == 0) {
|
||||
dispatch_async(queue, block);
|
||||
} else {
|
||||
GroupNotify notify;
|
||||
notify._block = block;
|
||||
notify._queue = queue;
|
||||
_notifyList.push_back(notify);
|
||||
}
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::enter()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_queue._mutex);
|
||||
++_pendingOperations;
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::leave()
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_queue._mutex);
|
||||
--_pendingOperations;
|
||||
|
||||
if (_pendingOperations == 0) {
|
||||
std::list<GroupNotify> notifyList;
|
||||
_notifyList.swap(notifyList);
|
||||
|
||||
for (GroupNotify & notify : notifyList) {
|
||||
dispatch_async(notify._queue, notify._block);
|
||||
}
|
||||
|
||||
_condition.notify_one();
|
||||
|
||||
// there was attempt to release the group before, but we still
|
||||
// had operations scheduled so now is good time
|
||||
if (_releaseCalled) {
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ASAsyncTransactionQueue::GroupImpl::wait()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_queue._mutex);
|
||||
while (_pendingOperations > 0) {
|
||||
_condition.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
{
|
||||
static ASAsyncTransactionQueue *instance = new ASAsyncTransactionQueue();
|
||||
return *instance;
|
||||
}
|
||||
|
||||
@interface _ASAsyncTransaction ()
|
||||
@property ASAsyncTransactionState state;
|
||||
@end
|
||||
|
||||
|
||||
@implementation _ASAsyncTransaction
|
||||
{
|
||||
ASAsyncTransactionQueue::Group *_group;
|
||||
NSMutableArray<ASAsyncTransactionOperation *> *_operations;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithCompletionBlock:(void(^)(_ASAsyncTransaction *, BOOL))completionBlock
|
||||
{
|
||||
if ((self = [self init])) {
|
||||
_completionBlock = completionBlock;
|
||||
self.state = ASAsyncTransactionStateOpen;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Uncommitted transactions break our guarantees about releasing completion blocks on callbackQueue.
|
||||
NSAssert(self.state != ASAsyncTransactionStateOpen, @"Uncommitted ASAsyncTransactions are not allowed");
|
||||
if (_group) {
|
||||
_group->release();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Transaction Management
|
||||
|
||||
- (void)addOperationWithBlock:(asyncdisplaykit_async_transaction_operation_block_t)block
|
||||
priority:(NSInteger)priority
|
||||
queue:(dispatch_queue_t)queue
|
||||
completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSAssert(self.state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");
|
||||
|
||||
[self _ensureTransactionData];
|
||||
|
||||
ASAsyncTransactionOperation *operation = [[ASAsyncTransactionOperation alloc] initWithOperationCompletionBlock:completion];
|
||||
[_operations addObject:operation];
|
||||
_group->schedule(priority, queue, ^{
|
||||
@autoreleasepool {
|
||||
if (self.state != ASAsyncTransactionStateCanceled) {
|
||||
operation.value = block();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSAssert(self.state != ASAsyncTransactionStateOpen, @"You can only cancel a committed or already-canceled transaction");
|
||||
self.state = ASAsyncTransactionStateCanceled;
|
||||
}
|
||||
|
||||
- (void)commit
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSAssert(self.state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
|
||||
self.state = ASAsyncTransactionStateCommitted;
|
||||
|
||||
if ([_operations count] == 0) {
|
||||
// Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously.
|
||||
if (_completionBlock) {
|
||||
_completionBlock(self, NO);
|
||||
}
|
||||
} else {
|
||||
NSAssert(_group != NULL, @"If there are operations, dispatch group should have been created");
|
||||
|
||||
_group->notify(dispatch_get_main_queue(), ^{
|
||||
[self completeTransaction];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)completeTransaction
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASAsyncTransactionState state = self.state;
|
||||
if (state != ASAsyncTransactionStateComplete) {
|
||||
BOOL isCanceled = (state == ASAsyncTransactionStateCanceled);
|
||||
for (ASAsyncTransactionOperation *operation in _operations) {
|
||||
[operation callAndReleaseCompletionBlock:isCanceled];
|
||||
}
|
||||
|
||||
// Always set state to Complete, even if we were cancelled, to block any extraneous
|
||||
// calls to this method that may have been scheduled for the next runloop
|
||||
// (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled)
|
||||
self.state = ASAsyncTransactionStateComplete;
|
||||
|
||||
if (_completionBlock) {
|
||||
_completionBlock(self, isCanceled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)waitUntilComplete
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (self.state != ASAsyncTransactionStateComplete) {
|
||||
if (_group) {
|
||||
_group->wait();
|
||||
|
||||
// At this point, the asynchronous operation may have completed, but the runloop
|
||||
// observer has not committed the batch of transactions we belong to. It's important to
|
||||
// commit ourselves via the group to avoid double-committing the transaction.
|
||||
// This is only necessary when forcing display work to complete before allowing the runloop
|
||||
// to continue, e.g. in the implementation of -[ASDisplayNode recursivelyEnsureDisplay].
|
||||
if (self.state == ASAsyncTransactionStateOpen) {
|
||||
[_ASAsyncTransactionGroup.mainTransactionGroup commit];
|
||||
NSAssert(self.state != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group");
|
||||
}
|
||||
// If we needed to commit the group above, -completeTransaction may have already been run.
|
||||
// It is designed to accommodate this by checking _state to ensure it is not complete.
|
||||
[self completeTransaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Helper Methods
|
||||
|
||||
- (void)_ensureTransactionData
|
||||
{
|
||||
// Lazily initialize _group and _operations to avoid overhead in the case where no operations are added to the transaction
|
||||
if (_group == NULL) {
|
||||
_group = ASAsyncTransactionQueue::instance().createGroup();
|
||||
}
|
||||
if (_operations == nil) {
|
||||
_operations = [[NSMutableArray alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<_ASAsyncTransaction: %p - _state = %lu, _group = %p, _operations = %@>", self, (unsigned long)self.state, _group, _operations];
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,24 @@
|
||||
//
|
||||
// _ASAsyncTransactionContainer+Private.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class _ASAsyncTransaction;
|
||||
|
||||
@interface CALayer (ASAsyncTransactionContainerTransactions)
|
||||
@property (nonatomic, nullable, setter=asyncdisplaykit_setAsyncLayerTransactions:) NSHashTable<_ASAsyncTransaction *> *asyncdisplaykit_asyncLayerTransactions;
|
||||
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction;
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -0,0 +1,121 @@
|
||||
//
|
||||
// _ASAsyncTransactionContainer.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
|
||||
#import "_ASAsyncTransactionContainer+Private.h"
|
||||
|
||||
#import <AsyncDisplayKit/_ASAsyncTransaction.h>
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionGroup.h>
|
||||
|
||||
@implementation CALayer (ASAsyncTransactionContainerTransactions)
|
||||
@dynamic asyncdisplaykit_asyncLayerTransactions;
|
||||
|
||||
// No-ops in the base class. Mostly exposed for testing.
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:(_ASAsyncTransaction *)transaction {}
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:(_ASAsyncTransaction *)transaction {}
|
||||
@end
|
||||
|
||||
@implementation CALayer (ASAsyncTransactionContainer)
|
||||
@dynamic asyncdisplaykit_currentAsyncTransaction;
|
||||
@dynamic asyncdisplaykit_asyncTransactionContainer;
|
||||
|
||||
- (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState
|
||||
{
|
||||
return ([self.asyncdisplaykit_asyncLayerTransactions count] == 0) ? ASAsyncTransactionContainerStateNoTransactions : ASAsyncTransactionContainerStatePendingTransactions;
|
||||
}
|
||||
|
||||
- (void)asyncdisplaykit_cancelAsyncTransactions
|
||||
{
|
||||
// If there was an open transaction, commit and clear the current transaction. Otherwise:
|
||||
// (1) The run loop observer will try to commit a canceled transaction which is not allowed
|
||||
// (2) We leave the canceled transaction attached to the layer, dooming future operations
|
||||
_ASAsyncTransaction *currentTransaction = self.asyncdisplaykit_currentAsyncTransaction;
|
||||
[currentTransaction commit];
|
||||
self.asyncdisplaykit_currentAsyncTransaction = nil;
|
||||
|
||||
for (_ASAsyncTransaction *transaction in [self.asyncdisplaykit_asyncLayerTransactions copy]) {
|
||||
[transaction cancel];
|
||||
}
|
||||
}
|
||||
|
||||
- (_ASAsyncTransaction *)asyncdisplaykit_asyncTransaction
|
||||
{
|
||||
_ASAsyncTransaction *transaction = self.asyncdisplaykit_currentAsyncTransaction;
|
||||
if (transaction == nil) {
|
||||
NSHashTable *transactions = self.asyncdisplaykit_asyncLayerTransactions;
|
||||
if (transactions == nil) {
|
||||
transactions = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
self.asyncdisplaykit_asyncLayerTransactions = transactions;
|
||||
}
|
||||
__weak CALayer *weakSelf = self;
|
||||
transaction = [[_ASAsyncTransaction alloc] initWithCompletionBlock:^(_ASAsyncTransaction *completedTransaction, BOOL cancelled) {
|
||||
__strong CALayer *self = weakSelf;
|
||||
if (self == nil) {
|
||||
return;
|
||||
}
|
||||
[transactions removeObject:completedTransaction];
|
||||
[self asyncdisplaykit_asyncTransactionContainerDidCompleteTransaction:completedTransaction];
|
||||
}];
|
||||
[transactions addObject:transaction];
|
||||
self.asyncdisplaykit_currentAsyncTransaction = transaction;
|
||||
[self asyncdisplaykit_asyncTransactionContainerWillBeginTransaction:transaction];
|
||||
}
|
||||
[_ASAsyncTransactionGroup.mainTransactionGroup addTransactionContainer:self];
|
||||
return transaction;
|
||||
}
|
||||
|
||||
- (CALayer *)asyncdisplaykit_parentTransactionContainer
|
||||
{
|
||||
CALayer *containerLayer = self;
|
||||
while (containerLayer && !containerLayer.asyncdisplaykit_isAsyncTransactionContainer) {
|
||||
containerLayer = containerLayer.superlayer;
|
||||
}
|
||||
return containerLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIView (ASAsyncTransactionContainer)
|
||||
|
||||
- (BOOL)asyncdisplaykit_isAsyncTransactionContainer
|
||||
{
|
||||
return self.layer.asyncdisplaykit_isAsyncTransactionContainer;
|
||||
}
|
||||
|
||||
- (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)asyncTransactionContainer
|
||||
{
|
||||
self.layer.asyncdisplaykit_asyncTransactionContainer = asyncTransactionContainer;
|
||||
}
|
||||
|
||||
- (ASAsyncTransactionContainerState)asyncdisplaykit_asyncTransactionContainerState
|
||||
{
|
||||
return self.layer.asyncdisplaykit_asyncTransactionContainerState;
|
||||
}
|
||||
|
||||
- (void)asyncdisplaykit_cancelAsyncTransactions
|
||||
{
|
||||
[self.layer asyncdisplaykit_cancelAsyncTransactions];
|
||||
}
|
||||
|
||||
- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange
|
||||
{
|
||||
// No-op in the base class.
|
||||
}
|
||||
|
||||
- (void)asyncdisplaykit_setCurrentAsyncTransaction:(_ASAsyncTransaction *)transaction
|
||||
{
|
||||
self.layer.asyncdisplaykit_currentAsyncTransaction = transaction;
|
||||
}
|
||||
|
||||
- (_ASAsyncTransaction *)asyncdisplaykit_currentAsyncTransaction
|
||||
{
|
||||
return self.layer.asyncdisplaykit_currentAsyncTransaction;
|
||||
}
|
||||
|
||||
@end
|
@ -0,0 +1,88 @@
|
||||
//
|
||||
// _ASAsyncTransactionGroup.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASAsyncTransaction.h>
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionGroup.h>
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
|
||||
#import "_ASAsyncTransactionContainer+Private.h"
|
||||
|
||||
@implementation _ASAsyncTransactionGroup {
|
||||
NSHashTable<id<ASAsyncTransactionContainer>> *_containers;
|
||||
}
|
||||
|
||||
+ (_ASAsyncTransactionGroup *)mainTransactionGroup
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
static _ASAsyncTransactionGroup *mainTransactionGroup;
|
||||
|
||||
if (mainTransactionGroup == nil) {
|
||||
mainTransactionGroup = [[_ASAsyncTransactionGroup alloc] _init];
|
||||
[mainTransactionGroup registerAsMainRunloopObserver];
|
||||
}
|
||||
return mainTransactionGroup;
|
||||
}
|
||||
|
||||
- (void)registerAsMainRunloopObserver
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
static CFRunLoopObserverRef observer;
|
||||
ASDisplayNodeAssert(observer == NULL, @"A _ASAsyncTransactionGroup should not be registered on the main runloop twice");
|
||||
// defer the commit of the transaction so we can add more during the current runloop iteration
|
||||
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
|
||||
CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
|
||||
kCFRunLoopExit); // before exiting a runloop run
|
||||
|
||||
observer = CFRunLoopObserverCreateWithHandler(NULL, // allocator
|
||||
activities, // activities
|
||||
YES, // repeats
|
||||
INT_MAX, // order after CA transaction commits
|
||||
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
||||
ASDisplayNodeCAssertMainThread();
|
||||
[self commit];
|
||||
});
|
||||
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
|
||||
CFRelease(observer);
|
||||
}
|
||||
|
||||
- (instancetype)_init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addTransactionContainer:(id<ASAsyncTransactionContainer>)container
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(container != nil, @"No container");
|
||||
[_containers addObject:container];
|
||||
}
|
||||
|
||||
- (void)commit
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if ([_containers count]) {
|
||||
NSHashTable *containersToCommit = _containers;
|
||||
_containers = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality];
|
||||
|
||||
for (id<ASAsyncTransactionContainer> container in containersToCommit) {
|
||||
// Note that the act of committing a transaction may open a new transaction,
|
||||
// so we must nil out the transaction we're committing first.
|
||||
_ASAsyncTransaction *transaction = container.asyncdisplaykit_currentAsyncTransaction;
|
||||
container.asyncdisplaykit_currentAsyncTransaction = nil;
|
||||
[transaction commit];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
187
submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm
Normal file
187
submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm
Normal file
@ -0,0 +1,187 @@
|
||||
//
|
||||
// _ASCoreAnimationExtras.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
|
||||
void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image)
|
||||
{
|
||||
ASDisplayNodeSetResizableContents(layer, image);
|
||||
}
|
||||
|
||||
void ASDisplayNodeSetResizableContents(id<ASResizableContents> obj, UIImage *image)
|
||||
{
|
||||
// FIXME (https://github.com/TextureGroup/Texture/issues/1046): This method does not currently handle UIImageResizingModeTile, which is the default.
|
||||
// See also https://developer.apple.com/documentation/uikit/uiimage/1624157-resizingmode?language=objc
|
||||
// I'm not sure of a way to use CALayer directly to perform such tiling on the GPU, though the stretch is handled by the GPU,
|
||||
// and CALayer.h documents the fact that contentsCenter is used to stretch the pixels.
|
||||
|
||||
if (image) {
|
||||
ASDisplayNodeCAssert(image.resizingMode == UIImageResizingModeStretch || UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero),
|
||||
@"Image insets must be all-zero or resizingMode has to be UIImageResizingModeStretch. XCode assets default value is UIImageResizingModeTile which is not supported by Texture because of GPU-accelerated CALayer features.");
|
||||
|
||||
// Image may not actually be stretchable in one or both dimensions; this is handled
|
||||
obj.contents = (id)[image CGImage];
|
||||
obj.contentsScale = [image scale];
|
||||
obj.rasterizationScale = [image scale];
|
||||
CGSize imageSize = [image size];
|
||||
|
||||
UIEdgeInsets insets = [image capInsets];
|
||||
|
||||
// These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off.
|
||||
const CGFloat halfPixelFudge = 0.49f;
|
||||
const CGFloat otherPixelFudge = 0.02f;
|
||||
// Convert to unit coordinates for the contentsCenter property.
|
||||
CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
if (insets.left > 0 || insets.right > 0) {
|
||||
contentsCenter.origin.x = ((insets.left + halfPixelFudge) / imageSize.width);
|
||||
contentsCenter.size.width = (imageSize.width - (insets.left + insets.right + 1.f) + otherPixelFudge) / imageSize.width;
|
||||
}
|
||||
if (insets.top > 0 || insets.bottom > 0) {
|
||||
contentsCenter.origin.y = ((insets.top + halfPixelFudge) / imageSize.height);
|
||||
contentsCenter.size.height = (imageSize.height - (insets.top + insets.bottom + 1.f) + otherPixelFudge) / imageSize.height;
|
||||
}
|
||||
obj.contentsGravity = kCAGravityResize;
|
||||
obj.contentsCenter = contentsCenter;
|
||||
|
||||
} else {
|
||||
obj.contents = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct _UIContentModeStringLUTEntry {
|
||||
UIViewContentMode contentMode;
|
||||
NSString *const string;
|
||||
};
|
||||
|
||||
static const _UIContentModeStringLUTEntry *UIContentModeCAGravityLUT(size_t *count)
|
||||
{
|
||||
// Initialize this in a function (instead of at file level) to avoid
|
||||
// startup initialization time.
|
||||
static const _UIContentModeStringLUTEntry sUIContentModeCAGravityLUT[] = {
|
||||
{UIViewContentModeScaleToFill, kCAGravityResize},
|
||||
{UIViewContentModeScaleAspectFit, kCAGravityResizeAspect},
|
||||
{UIViewContentModeScaleAspectFill, kCAGravityResizeAspectFill},
|
||||
{UIViewContentModeCenter, kCAGravityCenter},
|
||||
{UIViewContentModeTop, kCAGravityBottom},
|
||||
{UIViewContentModeBottom, kCAGravityTop},
|
||||
{UIViewContentModeLeft, kCAGravityLeft},
|
||||
{UIViewContentModeRight, kCAGravityRight},
|
||||
{UIViewContentModeTopLeft, kCAGravityBottomLeft},
|
||||
{UIViewContentModeTopRight, kCAGravityBottomRight},
|
||||
{UIViewContentModeBottomLeft, kCAGravityTopLeft},
|
||||
{UIViewContentModeBottomRight, kCAGravityTopRight},
|
||||
};
|
||||
*count = sizeof(sUIContentModeCAGravityLUT) / sizeof(sUIContentModeCAGravityLUT[0]);
|
||||
return sUIContentModeCAGravityLUT;
|
||||
}
|
||||
|
||||
static const _UIContentModeStringLUTEntry *UIContentModeDescriptionLUT(size_t *count)
|
||||
{
|
||||
// Initialize this in a function (instead of at file level) to avoid
|
||||
// startup initialization time.
|
||||
static const _UIContentModeStringLUTEntry sUIContentModeDescriptionLUT[] = {
|
||||
{UIViewContentModeScaleToFill, @"scaleToFill"},
|
||||
{UIViewContentModeScaleAspectFit, @"aspectFit"},
|
||||
{UIViewContentModeScaleAspectFill, @"aspectFill"},
|
||||
{UIViewContentModeRedraw, @"redraw"},
|
||||
{UIViewContentModeCenter, @"center"},
|
||||
{UIViewContentModeTop, @"top"},
|
||||
{UIViewContentModeBottom, @"bottom"},
|
||||
{UIViewContentModeLeft, @"left"},
|
||||
{UIViewContentModeRight, @"right"},
|
||||
{UIViewContentModeTopLeft, @"topLeft"},
|
||||
{UIViewContentModeTopRight, @"topRight"},
|
||||
{UIViewContentModeBottomLeft, @"bottomLeft"},
|
||||
{UIViewContentModeBottomRight, @"bottomRight"},
|
||||
};
|
||||
*count = sizeof(sUIContentModeDescriptionLUT) / sizeof(sUIContentModeDescriptionLUT[0]);
|
||||
return sUIContentModeDescriptionLUT;
|
||||
}
|
||||
|
||||
NSString *ASDisplayNodeNSStringFromUIContentMode(UIViewContentMode contentMode)
|
||||
{
|
||||
size_t lutSize;
|
||||
const _UIContentModeStringLUTEntry *lut = UIContentModeDescriptionLUT(&lutSize);
|
||||
for (size_t i = 0; i < lutSize; ++i) {
|
||||
if (lut[i].contentMode == contentMode) {
|
||||
return lut[i].string;
|
||||
}
|
||||
}
|
||||
return [NSString stringWithFormat:@"%d", (int)contentMode];
|
||||
}
|
||||
|
||||
UIViewContentMode ASDisplayNodeUIContentModeFromNSString(NSString *string)
|
||||
{
|
||||
size_t lutSize;
|
||||
const _UIContentModeStringLUTEntry *lut = UIContentModeDescriptionLUT(&lutSize);
|
||||
for (size_t i = 0; i < lutSize; ++i) {
|
||||
if (ASObjectIsEqual(lut[i].string, string)) {
|
||||
return lut[i].contentMode;
|
||||
}
|
||||
}
|
||||
return UIViewContentModeScaleToFill;
|
||||
}
|
||||
|
||||
NSString *const ASDisplayNodeCAContentsGravityFromUIContentMode(UIViewContentMode contentMode)
|
||||
{
|
||||
size_t lutSize;
|
||||
const _UIContentModeStringLUTEntry *lut = UIContentModeCAGravityLUT(&lutSize);
|
||||
for (size_t i = 0; i < lutSize; ++i) {
|
||||
if (lut[i].contentMode == contentMode) {
|
||||
return lut[i].string;
|
||||
}
|
||||
}
|
||||
ASDisplayNodeCAssert(contentMode == UIViewContentModeRedraw, @"Encountered an unknown contentMode %ld. Is this a new version of iOS?", (long)contentMode);
|
||||
// Redraw is ok to return nil.
|
||||
return nil;
|
||||
}
|
||||
|
||||
#define ContentModeCacheSize 10
|
||||
UIViewContentMode ASDisplayNodeUIContentModeFromCAContentsGravity(NSString *const contentsGravity)
|
||||
{
|
||||
static int currentCacheIndex = 0;
|
||||
static NSMutableArray *cachedStrings = [NSMutableArray arrayWithCapacity:ContentModeCacheSize];
|
||||
static UIViewContentMode cachedModes[ContentModeCacheSize] = {};
|
||||
|
||||
NSInteger foundCacheIndex = [cachedStrings indexOfObjectIdenticalTo:contentsGravity];
|
||||
if (foundCacheIndex != NSNotFound && foundCacheIndex < ContentModeCacheSize) {
|
||||
return cachedModes[foundCacheIndex];
|
||||
}
|
||||
|
||||
size_t lutSize;
|
||||
const _UIContentModeStringLUTEntry *lut = UIContentModeCAGravityLUT(&lutSize);
|
||||
for (size_t i = 0; i < lutSize; ++i) {
|
||||
if (ASObjectIsEqual(lut[i].string, contentsGravity)) {
|
||||
UIViewContentMode foundContentMode = lut[i].contentMode;
|
||||
|
||||
if (currentCacheIndex < ContentModeCacheSize) {
|
||||
// Cache the input value. This is almost always a different pointer than in our LUT and will frequently
|
||||
// be the same value for an overwhelming majority of inputs.
|
||||
[cachedStrings addObject:contentsGravity];
|
||||
cachedModes[currentCacheIndex] = foundContentMode;
|
||||
currentCacheIndex++;
|
||||
}
|
||||
|
||||
return foundContentMode;
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNodeCAssert(contentsGravity, @"Encountered an unknown contentsGravity \"%@\". Is this a new version of iOS?", contentsGravity);
|
||||
ASDisplayNodeCAssert(!contentsGravity, @"You passed nil to ASDisplayNodeUIContentModeFromCAContentsGravity. We're falling back to resize, but this is probably a bug.");
|
||||
// If asserts disabled, fall back to this
|
||||
return UIViewContentModeScaleToFill;
|
||||
}
|
||||
|
||||
BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer)
|
||||
{
|
||||
return (layer.animationKeys.count != 0);
|
||||
}
|
212
submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm
Normal file
212
submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm
Normal file
@ -0,0 +1,212 @@
|
||||
//
|
||||
// _ASDisplayLayer.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
@implementation _ASDisplayLayer
|
||||
{
|
||||
BOOL _attemptedDisplayWhileZeroSized;
|
||||
|
||||
struct {
|
||||
BOOL delegateDidChangeBounds:1;
|
||||
} _delegateFlags;
|
||||
}
|
||||
|
||||
@dynamic displaysAsynchronously;
|
||||
|
||||
#ifdef DEBUG
|
||||
- (void)dealloc {
|
||||
if (![NSThread isMainThread]) {
|
||||
assert(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma mark - Properties
|
||||
|
||||
- (void)setDelegate:(id)delegate
|
||||
{
|
||||
[super setDelegate:delegate];
|
||||
_delegateFlags.delegateDidChangeBounds = [delegate respondsToSelector:@selector(layer:didChangeBoundsWithOldValue:newValue:)];
|
||||
}
|
||||
|
||||
- (void)setDisplaySuspended:(BOOL)displaySuspended
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_displaySuspended != displaySuspended) {
|
||||
_displaySuspended = displaySuspended;
|
||||
if (!displaySuspended) {
|
||||
// If resuming display, trigger a display now.
|
||||
[self setNeedsDisplay];
|
||||
} else {
|
||||
// If suspending display, cancel any current async display so that we don't have contents set on us when it's finished.
|
||||
[self cancelAsyncDisplay];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
BOOL valid = ASDisplayNodeAssertNonFatal(ASIsCGRectValidForLayout(bounds), @"Caught attempt to set invalid bounds %@ on %@.", NSStringFromCGRect(bounds), self);
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (_delegateFlags.delegateDidChangeBounds) {
|
||||
CGRect oldBounds = self.bounds;
|
||||
[super setBounds:bounds];
|
||||
self.asyncdisplaykit_node.threadSafeBounds = bounds;
|
||||
[(id<ASCALayerExtendedDelegate>)self.delegate layer:self didChangeBoundsWithOldValue:oldBounds newValue:bounds];
|
||||
|
||||
} else {
|
||||
[super setBounds:bounds];
|
||||
self.asyncdisplaykit_node.threadSafeBounds = bounds;
|
||||
}
|
||||
|
||||
if (_attemptedDisplayWhileZeroSized && CGRectIsEmpty(bounds) == NO && self.needsDisplayOnBoundsChange == NO) {
|
||||
_attemptedDisplayWhileZeroSized = NO;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG // These override is strictly to help detect application-level threading errors. Avoid method overhead in release.
|
||||
- (void)setContents:(id)contents
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super setContents:contents];
|
||||
}
|
||||
|
||||
- (void)setNeedsLayout
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super setNeedsLayout];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)layoutSublayers
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super layoutSublayers];
|
||||
|
||||
[self.asyncdisplaykit_node __layout];
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// FIXME: Reconsider whether we should cancel a display in progress.
|
||||
// We should definitely cancel a display that is scheduled, but unstarted display.
|
||||
[self cancelAsyncDisplay];
|
||||
|
||||
// Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.
|
||||
if (!_displaySuspended) {
|
||||
[super setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (dispatch_queue_t)displayQueue
|
||||
{
|
||||
static dispatch_queue_t displayQueue = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||
// we use the highpri queue to prioritize UI rendering over other async operations
|
||||
dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
});
|
||||
|
||||
return displayQueue;
|
||||
}
|
||||
|
||||
+ (id)defaultValueForKey:(NSString *)key
|
||||
{
|
||||
if ([key isEqualToString:@"displaysAsynchronously"]) {
|
||||
return @YES;
|
||||
} else if ([key isEqualToString:@"opaque"]) {
|
||||
return @YES;
|
||||
} else {
|
||||
return [super defaultValueForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Display
|
||||
|
||||
- (void)displayImmediately
|
||||
{
|
||||
// This method is a low-level bypass that avoids touching CA, including any reset of the
|
||||
// needsDisplay flag, until the .contents property is set with the result.
|
||||
// It is designed to be able to block the thread of any caller and fully execute the display.
|
||||
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self display:NO];
|
||||
}
|
||||
|
||||
- (void)_hackResetNeedsDisplay
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
// Don't listen to our subclasses crazy ideas about setContents by going through super
|
||||
super.contents = super.contents;
|
||||
}
|
||||
|
||||
- (void)display
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self _hackResetNeedsDisplay];
|
||||
|
||||
if (self.displaySuspended) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self display:self.displaysAsynchronously];
|
||||
}
|
||||
|
||||
- (void)display:(BOOL)asynchronously
|
||||
{
|
||||
if (CGRectIsEmpty(self.bounds)) {
|
||||
_attemptedDisplayWhileZeroSized = YES;
|
||||
}
|
||||
|
||||
[self.asyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
|
||||
}
|
||||
|
||||
- (void)cancelAsyncDisplay
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
[self.asyncDelegate cancelDisplayAsyncLayer:self];
|
||||
}
|
||||
|
||||
// e.g. <MYTextNodeLayer: 0xFFFFFF; node = <MYTextNode: 0xFFFFFFE; name = "Username node for user 179">>
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *description = [[super description] mutableCopy];
|
||||
ASDisplayNode *node = self.asyncdisplaykit_node;
|
||||
if (node != nil) {
|
||||
NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)];
|
||||
[description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)];
|
||||
NSUInteger insertionIndex = [description rangeOfString:@">"].location;
|
||||
if (insertionIndex != NSNotFound) {
|
||||
NSString *nodeString = [NSString stringWithFormat:@"; node = %@", node];
|
||||
[description insertString:nodeString atIndex:insertionIndex];
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
@end
|
569
submodules/AsyncDisplayKit/Source/_ASDisplayView.mm
Normal file
569
submodules/AsyncDisplayKit/Source/_ASDisplayView.mm
Normal file
@ -0,0 +1,569 @@
|
||||
//
|
||||
// _ASDisplayView.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
#import "_ASDisplayViewAccessiblity.h"
|
||||
|
||||
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Convenience.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
#pragma mark - _ASDisplayViewMethodOverrides
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, _ASDisplayViewMethodOverrides)
|
||||
{
|
||||
_ASDisplayViewMethodOverrideNone = 0,
|
||||
_ASDisplayViewMethodOverrideCanBecomeFirstResponder = 1 << 0,
|
||||
_ASDisplayViewMethodOverrideBecomeFirstResponder = 1 << 1,
|
||||
_ASDisplayViewMethodOverrideCanResignFirstResponder = 1 << 2,
|
||||
_ASDisplayViewMethodOverrideResignFirstResponder = 1 << 3,
|
||||
_ASDisplayViewMethodOverrideIsFirstResponder = 1 << 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns _ASDisplayViewMethodOverrides for the given class
|
||||
*
|
||||
* @param c the class, required.
|
||||
*
|
||||
* @return _ASDisplayViewMethodOverrides.
|
||||
*/
|
||||
static _ASDisplayViewMethodOverrides GetASDisplayViewMethodOverrides(Class c)
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(c, @"class is required");
|
||||
|
||||
_ASDisplayViewMethodOverrides overrides = _ASDisplayViewMethodOverrideNone;
|
||||
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canBecomeFirstResponder))) {
|
||||
overrides |= _ASDisplayViewMethodOverrideCanBecomeFirstResponder;
|
||||
}
|
||||
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(becomeFirstResponder))) {
|
||||
overrides |= _ASDisplayViewMethodOverrideBecomeFirstResponder;
|
||||
}
|
||||
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canResignFirstResponder))) {
|
||||
overrides |= _ASDisplayViewMethodOverrideCanResignFirstResponder;
|
||||
}
|
||||
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(resignFirstResponder))) {
|
||||
overrides |= _ASDisplayViewMethodOverrideResignFirstResponder;
|
||||
}
|
||||
if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(isFirstResponder))) {
|
||||
overrides |= _ASDisplayViewMethodOverrideIsFirstResponder;
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
|
||||
#pragma mark - _ASDisplayView
|
||||
|
||||
@interface _ASDisplayView ()
|
||||
|
||||
// Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release
|
||||
// the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction.
|
||||
@property (nonatomic) ASDisplayNode *keepalive_node;
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayView
|
||||
{
|
||||
BOOL _inHitTest;
|
||||
BOOL _inPointInside;
|
||||
|
||||
NSArray *_accessibilityElements;
|
||||
CGRect _lastAccessibilityElementsFrame;
|
||||
|
||||
_ASDisplayViewMethodOverrides _methodOverrides;
|
||||
}
|
||||
|
||||
#pragma mark - Class
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
__unused Class initializeSelf = self;
|
||||
IMP staticInitialize = imp_implementationWithBlock(^(_ASDisplayView *view) {
|
||||
ASDisplayNodeAssert(view.class == initializeSelf, @"View class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", view.class, initializeSelf);
|
||||
view->_methodOverrides = GetASDisplayViewMethodOverrides(view.class);
|
||||
});
|
||||
|
||||
class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@");
|
||||
}
|
||||
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [_ASDisplayLayer class];
|
||||
}
|
||||
|
||||
#pragma mark - NSObject Overrides
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
[self _initializeInstance];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initializeInstance
|
||||
{
|
||||
[self _staticInitialize];
|
||||
}
|
||||
|
||||
- (void)_staticInitialize
|
||||
{
|
||||
ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden");
|
||||
}
|
||||
|
||||
// e.g. <MYPhotoNodeView: 0xFFFFFF; node = <MYPhotoNode: 0xFFFFFE>; frame = ...>
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *description = [[super description] mutableCopy];
|
||||
|
||||
ASDisplayNode *node = _asyncdisplaykit_node;
|
||||
|
||||
if (node != nil) {
|
||||
NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)];
|
||||
[description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)];
|
||||
NSUInteger semicolon = [description rangeOfString:@";"].location;
|
||||
if (semicolon != NSNotFound) {
|
||||
NSString *nodeString = [NSString stringWithFormat:@"; node = %@", node];
|
||||
[description insertString:nodeString atIndex:semicolon];
|
||||
}
|
||||
// Remove layer description – it never contains valuable info and it duplicates the node info. Noisy.
|
||||
NSRange layerDescriptionRange = [description rangeOfString:@"; layer = <.*>" options:NSRegularExpressionSearch];
|
||||
if (layerDescriptionRange.location != NSNotFound) {
|
||||
[description replaceCharactersInRange:layerDescriptionRange withString:@""];
|
||||
// Our regex will grab the closing angle bracket and I'm not clever enough to come up with a better one, so re-add it if needed.
|
||||
if ([description hasSuffix:@">"] == NO) {
|
||||
[description appendString:@">"];
|
||||
}
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
#pragma mark - UIView Overrides
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
BOOL visible = (newWindow != nil);
|
||||
if (visible && !node.inHierarchy) {
|
||||
[node __enterHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
BOOL visible = (self.window != nil);
|
||||
if (!visible && node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willMoveToSuperview:(UIView *)newSuperview
|
||||
{
|
||||
// Keep the node alive while the view is in a view hierarchy. This helps ensure that async-drawing views can always
|
||||
// display their contents as long as they are visible somewhere, and aids in lifecycle management because the
|
||||
// lifecycle of the node can be treated as the same as the lifecycle of the view (let the view hierarchy own the
|
||||
// view).
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
UIView *currentSuperview = self.superview;
|
||||
if (!currentSuperview && newSuperview) {
|
||||
self.keepalive_node = node;
|
||||
}
|
||||
|
||||
if (newSuperview) {
|
||||
ASDisplayNode *supernode = node.supernode;
|
||||
BOOL supernodeLoaded = supernode.nodeLoaded;
|
||||
ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for _ASDisplayView's supernode to be layer-backed.");
|
||||
|
||||
BOOL needsSupernodeUpdate = NO;
|
||||
|
||||
if (supernode) {
|
||||
if (supernodeLoaded) {
|
||||
if (supernode.layerBacked) {
|
||||
// See comment in -didMoveToSuperview. This case should be avoided, but is possible with app-level coding errors.
|
||||
needsSupernodeUpdate = (supernode.layer != newSuperview.layer);
|
||||
} else {
|
||||
// If we have a supernode, compensate for users directly messing with views by hitching up to any new supernode.
|
||||
needsSupernodeUpdate = (supernode.view != newSuperview);
|
||||
}
|
||||
} else {
|
||||
needsSupernodeUpdate = YES;
|
||||
}
|
||||
} else {
|
||||
// If we have no supernode and we are now in a view hierarchy, check to see if we can hook up to a supernode.
|
||||
needsSupernodeUpdate = (newSuperview != nil);
|
||||
}
|
||||
|
||||
if (needsSupernodeUpdate) {
|
||||
// -removeFromSupernode is called by -addSubnode:, if it is needed.
|
||||
// FIXME: Needs rethinking if automaticallyManagesSubnodes=YES
|
||||
[newSuperview.asyncdisplaykit_node _addSubnode:node];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didMoveToSuperview
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
UIView *superview = self.superview;
|
||||
if (superview == nil) {
|
||||
// Clearing keepalive_node may cause deallocation of the node. In this case, __exitHierarchy may not have an opportunity (e.g. _node will be cleared
|
||||
// by the time -didMoveToWindow occurs after this) to clear the Visible interfaceState, which we need to do before deallocation to meet an API guarantee.
|
||||
if (node.inHierarchy) {
|
||||
[node __exitHierarchy];
|
||||
}
|
||||
self.keepalive_node = nil;
|
||||
}
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
#if DEBUG
|
||||
// This is only to help detect issues when a root-of-view-controller node is reused separately from its view controller.
|
||||
// Avoid overhead in release.
|
||||
if (superview && node.viewControllerRoot) {
|
||||
UIViewController *vc = [node closestViewController];
|
||||
|
||||
ASDisplayNodeAssert(vc != nil && [vc isKindOfClass:[ASViewController class]] && ((ASViewController*)vc).node == node, @"This node was once used as a view controller's node. You should not reuse it without its view controller.");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ASDisplayNode *supernode = node.supernode;
|
||||
ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for superview's node to be layer-backed.");
|
||||
|
||||
if (supernode) {
|
||||
ASDisplayNodeAssertTrue(node.nodeLoaded);
|
||||
BOOL supernodeLoaded = supernode.nodeLoaded;
|
||||
BOOL needsSupernodeRemoval = NO;
|
||||
|
||||
if (superview) {
|
||||
// If our new superview is not the same as the supernode's view, or the supernode has no view, disconnect.
|
||||
if (supernodeLoaded) {
|
||||
if (supernode.layerBacked) {
|
||||
// As asserted at the top, this shouldn't be possible, but in production with assertions disabled it can happen.
|
||||
// We try to make such code behave as well as feasible because it's not that hard of an error to make if some deep
|
||||
// child node of a layer-backed node happens to be view-backed, but it is not supported and should be avoided.
|
||||
needsSupernodeRemoval = (supernode.layer != superview.layer);
|
||||
} else {
|
||||
needsSupernodeRemoval = (supernode.view != superview);
|
||||
}
|
||||
} else {
|
||||
needsSupernodeRemoval = YES;
|
||||
}
|
||||
} else {
|
||||
// If supernode is loaded but our superview is nil, the user likely manually removed us, so disconnect supernode.
|
||||
// The unlikely alternative: we are in __unloadNode, with shouldRasterizeSubnodes just having been turned on.
|
||||
// In the latter case, we don't want to disassemble the node hierarchy because all views are intentionally being destroyed.
|
||||
BOOL nodeIsRasterized = ((node.hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized);
|
||||
needsSupernodeRemoval = (supernodeLoaded && !nodeIsRasterized);
|
||||
}
|
||||
|
||||
if (needsSupernodeRemoval) {
|
||||
// The node will only disconnect from its supernode, not removeFromSuperview, in this condition.
|
||||
// FIXME: Needs rethinking if automaticallyManagesSubnodes=YES
|
||||
[node _removeFromSupernode];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index {
|
||||
[super insertSubview:view atIndex:index];
|
||||
|
||||
#ifndef ASDK_ACCESSIBILITY_DISABLE
|
||||
self.accessibilityElements = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)addSubview:(UIView *)view
|
||||
{
|
||||
[super addSubview:view];
|
||||
|
||||
#ifndef ASDK_ACCESSIBILITY_DISABLE
|
||||
self.accessibilityElements = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)willRemoveSubview:(UIView *)subview
|
||||
{
|
||||
[super willRemoveSubview:subview];
|
||||
|
||||
#ifndef ASDK_ACCESSIBILITY_DISABLE
|
||||
self.accessibilityElements = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return node ? [node layoutThatFits:ASSizeRangeMake(size)].size : [super sizeThatFits:size];
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
// Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:.
|
||||
[self.layer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (UIViewContentMode)contentMode
|
||||
{
|
||||
return ASDisplayNodeUIContentModeFromCAContentsGravity(self.layer.contentsGravity);
|
||||
}
|
||||
|
||||
- (void)setContentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
ASDisplayNodeAssert(contentMode != UIViewContentModeRedraw, @"Don't do this. Use needsDisplayOnBoundsChange instead.");
|
||||
|
||||
// Do our own mapping so as not to call super and muck up needsDisplayOnBoundsChange. If we're in a production build, fall back to resize if we see redraw
|
||||
self.layer.contentsGravity = (contentMode != UIViewContentModeRedraw) ? ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode) : kCAGravityResize;
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
[super setBounds:bounds];
|
||||
node.threadSafeBounds = bounds;
|
||||
}
|
||||
|
||||
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
[super addGestureRecognizer:gestureRecognizer];
|
||||
[_asyncdisplaykit_node nodeViewDidAddGestureRecognizer];
|
||||
}
|
||||
|
||||
#pragma mark - Event Handling + UIResponder Overrides
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesBegan) {
|
||||
[node touchesBegan:touches withEvent:event];
|
||||
} else {
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesMoved) {
|
||||
[node touchesMoved:touches withEvent:event];
|
||||
} else {
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesEnded) {
|
||||
[node touchesEnded:touches withEvent:event];
|
||||
} else {
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (node.methodOverrides & ASDisplayNodeMethodOverrideTouchesCancelled) {
|
||||
[node touchesCancelled:touches withEvent:event];
|
||||
} else {
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)__forwardTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)__forwardTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
// REVIEW: We should optimize these types of messages by setting a boolean in the associated ASDisplayNode subclass if
|
||||
// they actually override the method. Same goes for -pointInside:withEvent: below. Many UIKit classes use that
|
||||
// pattern for meaningful reductions of message send overhead in hot code (especially event handling).
|
||||
|
||||
// Set boolean so this method can be re-entrant. If the node subclass wants to default to / make use of UIView
|
||||
// hitTest:, it will call it on the view, which is _ASDisplayView. After calling into the node, any additional calls
|
||||
// should use the UIView implementation of hitTest:
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (!_inHitTest) {
|
||||
_inHitTest = YES;
|
||||
UIView *hitView = [node hitTest:point withEvent:event];
|
||||
_inHitTest = NO;
|
||||
return hitView;
|
||||
} else {
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
// See comments in -hitTest:withEvent: for the strategy here.
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
if (!_inPointInside) {
|
||||
_inPointInside = YES;
|
||||
BOOL result = [node pointInside:point withEvent:event];
|
||||
_inPointInside = NO;
|
||||
return result;
|
||||
} else {
|
||||
return [super pointInside:point withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node gestureRecognizerShouldBegin:gestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
[super tintColorDidChange];
|
||||
|
||||
[node tintColorDidChange];
|
||||
}
|
||||
|
||||
#pragma mark UIResponder Handling
|
||||
|
||||
#define IMPLEMENT_RESPONDER_METHOD(__sel, __nodeMethodOverride, __viewMethodOverride) \
|
||||
- (BOOL)__sel\
|
||||
{\
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; /* Create strong reference to weak ivar. */ \
|
||||
/* Check if we can call through to ASDisplayNode subclass directly */ \
|
||||
if (node.methodOverrides & __nodeMethodOverride) { \
|
||||
return [node __sel]; \
|
||||
} else { \
|
||||
/* Prevent an infinite loop in here if [super __sel] was called on a \
|
||||
/ _ASDisplayView subclass */ \
|
||||
if (self->_methodOverrides & __viewMethodOverride) { \
|
||||
/* Call through to views superclass as we expect super was called from the
|
||||
_ASDisplayView subclass and a node subclass does not overwrite __sel */ \
|
||||
return [self __##__sel]; \
|
||||
} else { \
|
||||
/* Call through to internal node __sel method that will consider the view in responding */ \
|
||||
return [node __##__sel]; \
|
||||
} \
|
||||
} \
|
||||
}\
|
||||
/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \
|
||||
are not overridden by a ASDisplayNode subclass */ \
|
||||
- (BOOL)__##__sel \
|
||||
{ \
|
||||
return [super __sel]; \
|
||||
} \
|
||||
|
||||
IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder,
|
||||
ASDisplayNodeMethodOverrideCanBecomeFirstResponder,
|
||||
_ASDisplayViewMethodOverrideCanBecomeFirstResponder);
|
||||
IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder,
|
||||
ASDisplayNodeMethodOverrideBecomeFirstResponder,
|
||||
_ASDisplayViewMethodOverrideBecomeFirstResponder);
|
||||
IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder,
|
||||
ASDisplayNodeMethodOverrideCanResignFirstResponder,
|
||||
_ASDisplayViewMethodOverrideCanResignFirstResponder);
|
||||
IMPLEMENT_RESPONDER_METHOD(resignFirstResponder,
|
||||
ASDisplayNodeMethodOverrideResignFirstResponder,
|
||||
_ASDisplayViewMethodOverrideResignFirstResponder);
|
||||
IMPLEMENT_RESPONDER_METHOD(isFirstResponder,
|
||||
ASDisplayNodeMethodOverrideIsFirstResponder,
|
||||
_ASDisplayViewMethodOverrideIsFirstResponder);
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
// We forward responder-chain actions to our node if we can't handle them ourselves. See -targetForAction:withSender:.
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return ([super canPerformAction:action withSender:sender] || [node respondsToSelector:action]);
|
||||
}
|
||||
|
||||
- (void)layoutMarginsDidChange
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
[super layoutMarginsDidChange];
|
||||
|
||||
[node layoutMarginsDidChange];
|
||||
}
|
||||
|
||||
- (void)safeAreaInsetsDidChange
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
[super safeAreaInsetsDidChange];
|
||||
|
||||
[node safeAreaInsetsDidChange];
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||
{
|
||||
// Ideally, we would implement -targetForAction:withSender: and simply return the node where we don't respond personally.
|
||||
// Unfortunately UIResponder's default implementation of -targetForAction:withSender: doesn't follow its own documentation. It doesn't call -targetForAction:withSender: up the responder chain when -canPerformAction:withSender: fails, but instead merely calls -canPerformAction:withSender: on itself and then up the chain. rdar://20111500.
|
||||
// Consequently, to forward responder-chain actions to our node, we override -canPerformAction:withSender: (used by the chain) to indicate support for responder chain-driven actions that our node supports, and then provide the node as a forwarding target here.
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return node;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
#pragma mark - tvOS
|
||||
- (BOOL)canBecomeFocused
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node canBecomeFocused];
|
||||
}
|
||||
|
||||
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
|
||||
}
|
||||
|
||||
- (void)setNeedsFocusUpdate
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node setNeedsFocusUpdate];
|
||||
}
|
||||
|
||||
- (void)updateFocusIfNeeded
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node updateFocusIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node shouldUpdateFocusInContext:context];
|
||||
}
|
||||
|
||||
- (UIView *)preferredFocusedView
|
||||
{
|
||||
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
|
||||
return [node preferredFocusedView];
|
||||
}
|
||||
#endif
|
||||
@end
|
@ -0,0 +1,17 @@
|
||||
//
|
||||
// _ASDisplayViewAccessiblity.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
|
||||
// WARNING: When dealing with accessibility elements, please use the `accessibilityElements`
|
||||
// property instead of the older methods e.g. `accessibilityElementCount()`. While the older methods
|
||||
// should still work as long as accessibility is enabled, this framework provides no guarantees on
|
||||
// their correctness. For details, see
|
||||
// https://developer.apple.com/documentation/objectivec/nsobject/1615147-accessibilityelements
|
349
submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm
Normal file
349
submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm
Normal file
@ -0,0 +1,349 @@
|
||||
//
|
||||
// _ASDisplayViewAccessiblity.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#ifndef ASDK_ACCESSIBILITY_DISABLE
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
|
||||
#import <queue>
|
||||
|
||||
NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() {
|
||||
return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton;
|
||||
}
|
||||
|
||||
#pragma mark - UIAccessibilityElement
|
||||
|
||||
@protocol ASAccessibilityElementPositioning
|
||||
|
||||
@property (nonatomic, readonly) CGRect accessibilityFrame;
|
||||
|
||||
@end
|
||||
|
||||
typedef NSComparisonResult (^SortAccessibilityElementsComparator)(id<ASAccessibilityElementPositioning>, id<ASAccessibilityElementPositioning>);
|
||||
|
||||
/// Sort accessiblity elements first by y and than by x origin.
|
||||
static void SortAccessibilityElements(NSMutableArray *elements)
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
|
||||
|
||||
static SortAccessibilityElementsComparator comparator = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
comparator = ^NSComparisonResult(id<ASAccessibilityElementPositioning> a, id<ASAccessibilityElementPositioning> b) {
|
||||
CGPoint originA = a.accessibilityFrame.origin;
|
||||
CGPoint originB = b.accessibilityFrame.origin;
|
||||
if (originA.y == originB.y) {
|
||||
if (originA.x == originB.x) {
|
||||
return NSOrderedSame;
|
||||
}
|
||||
return (originA.x < originB.x) ? NSOrderedAscending : NSOrderedDescending;
|
||||
}
|
||||
return (originA.y < originB.y) ? NSOrderedAscending : NSOrderedDescending;
|
||||
};
|
||||
});
|
||||
[elements sortUsingComparator:comparator];
|
||||
}
|
||||
|
||||
@interface ASAccessibilityElement : UIAccessibilityElement<ASAccessibilityElementPositioning>
|
||||
|
||||
@property (nonatomic) ASDisplayNode *node;
|
||||
@property (nonatomic) ASDisplayNode *containerNode;
|
||||
|
||||
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASAccessibilityElement
|
||||
|
||||
+ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode
|
||||
{
|
||||
ASAccessibilityElement *accessibilityElement = [[ASAccessibilityElement alloc] initWithAccessibilityContainer:container];
|
||||
accessibilityElement.node = node;
|
||||
accessibilityElement.containerNode = containerNode;
|
||||
accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier;
|
||||
accessibilityElement.accessibilityLabel = node.accessibilityLabel;
|
||||
accessibilityElement.accessibilityHint = node.accessibilityHint;
|
||||
accessibilityElement.accessibilityValue = node.accessibilityValue;
|
||||
accessibilityElement.accessibilityTraits = node.accessibilityTraits;
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
|
||||
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
|
||||
accessibilityElement.accessibilityAttributedLabel = node.accessibilityAttributedLabel;
|
||||
accessibilityElement.accessibilityAttributedHint = node.accessibilityAttributedHint;
|
||||
accessibilityElement.accessibilityAttributedValue = node.accessibilityAttributedValue;
|
||||
}
|
||||
#endif
|
||||
return accessibilityElement;
|
||||
}
|
||||
|
||||
- (CGRect)accessibilityFrame
|
||||
{
|
||||
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node];
|
||||
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.accessibilityContainer);
|
||||
return accessibilityFrame;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - _ASDisplayView / UIAccessibilityContainer
|
||||
|
||||
@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction<ASAccessibilityElementPositioning>
|
||||
|
||||
@property (nonatomic) UIView *container;
|
||||
@property (nonatomic) ASDisplayNode *node;
|
||||
@property (nonatomic) ASDisplayNode *containerNode;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASAccessibilityCustomAction
|
||||
|
||||
- (CGRect)accessibilityFrame
|
||||
{
|
||||
CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node];
|
||||
accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.container);
|
||||
return accessibilityFrame;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container
|
||||
static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements)
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
|
||||
|
||||
ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) {
|
||||
// For every subnode that is layer backed or it's supernode has subtree rasterization enabled
|
||||
// we have to create a UIAccessibilityElement as no view for this node exists
|
||||
if (currentNode != containerNode && currentNode.isAccessibilityElement) {
|
||||
UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode];
|
||||
[elements addObject:accessibilityElement];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, UIView *view, NSMutableArray *elements) {
|
||||
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container];
|
||||
|
||||
NSMutableArray<ASAccessibilityElement *> *labeledNodes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<ASAccessibilityCustomAction *> *actions = [[NSMutableArray alloc] init];
|
||||
std::queue<ASDisplayNode *> queue;
|
||||
queue.push(container);
|
||||
|
||||
// If the container does not have an accessibility label set, or if the label is meant for custom
|
||||
// actions only, then aggregate its subnodes' labels. Otherwise, treat the label as an overriden
|
||||
// value and do not perform the aggregation.
|
||||
BOOL shouldAggregateSubnodeLabels =
|
||||
(container.accessibilityLabel.length == 0) ||
|
||||
(container.accessibilityTraits & InteractiveAccessibilityTraitsMask());
|
||||
|
||||
ASDisplayNode *node = nil;
|
||||
while (!queue.empty()) {
|
||||
node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
if (node != container && node.isAccessibilityContainer) {
|
||||
CollectAccessibilityElementsForContainer(node, view, elements);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.accessibilityLabel.length > 0) {
|
||||
if (node.accessibilityTraits & InteractiveAccessibilityTraitsMask()) {
|
||||
ASAccessibilityCustomAction *action = [[ASAccessibilityCustomAction alloc] initWithName:node.accessibilityLabel target:node selector:@selector(performAccessibilityCustomAction:)];
|
||||
action.node = node;
|
||||
action.containerNode = node.supernode;
|
||||
action.container = node.supernode.view;
|
||||
[actions addObject:action];
|
||||
} else if (node == container || shouldAggregateSubnodeLabels) {
|
||||
// Even though not surfaced to UIKit, create a non-interactive element for purposes of building sorted aggregated label.
|
||||
ASAccessibilityElement *nonInteractiveElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:node containerNode:container];
|
||||
[labeledNodes addObject:nonInteractiveElement];
|
||||
}
|
||||
}
|
||||
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
queue.push(subnode);
|
||||
}
|
||||
}
|
||||
|
||||
SortAccessibilityElements(labeledNodes);
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
|
||||
if (AS_AVAILABLE_IOS_TVOS(11, 11)) {
|
||||
NSArray *attributedLabels = [labeledNodes valueForKey:@"accessibilityAttributedLabel"];
|
||||
NSMutableAttributedString *attributedLabel = [NSMutableAttributedString new];
|
||||
[attributedLabels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (idx != 0) {
|
||||
[attributedLabel appendAttributedString:[[NSAttributedString alloc] initWithString:@", "]];
|
||||
}
|
||||
[attributedLabel appendAttributedString:(NSAttributedString *)obj];
|
||||
}];
|
||||
accessiblityElement.accessibilityAttributedLabel = attributedLabel;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
NSArray *labels = [labeledNodes valueForKey:@"accessibilityLabel"];
|
||||
accessiblityElement.accessibilityLabel = [labels componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
SortAccessibilityElements(actions);
|
||||
accessiblityElement.accessibilityCustomActions = actions;
|
||||
|
||||
[elements addObject:accessiblityElement];
|
||||
}
|
||||
|
||||
/// Collect all accessibliity elements for a given view and view node
|
||||
static void CollectAccessibilityElementsForView(UIView *view, NSMutableArray *elements)
|
||||
{
|
||||
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
|
||||
|
||||
ASDisplayNode *node = view.asyncdisplaykit_node;
|
||||
|
||||
static Class displayListViewClass = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
displayListViewClass = NSClassFromString(@"Display.ListView");
|
||||
});
|
||||
BOOL anySubNodeIsCollection = (nil != ASDisplayNodeFindFirstNode(node,
|
||||
^BOOL(ASDisplayNode *nodeToCheck) {
|
||||
if (displayListViewClass != nil && [nodeToCheck isKindOfClass:displayListViewClass]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
/*return ASDynamicCast(nodeToCheck, ASCollectionNode) != nil ||
|
||||
ASDynamicCast(nodeToCheck, ASTableNode) != nil;*/
|
||||
}));
|
||||
|
||||
if (node.isAccessibilityContainer && !anySubNodeIsCollection) {
|
||||
CollectAccessibilityElementsForContainer(node, view, elements);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle rasterize case
|
||||
if (node.rasterizesSubtree) {
|
||||
CollectUIAccessibilityElementsForNode(node, node, view, elements);
|
||||
return;
|
||||
}
|
||||
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
if (subnode.isAccessibilityElement) {
|
||||
|
||||
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
|
||||
if (subnode.isLayerBacked) {
|
||||
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
|
||||
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
|
||||
[elements addObject:accessiblityElement];
|
||||
} else {
|
||||
// Accessiblity element is not layer backed just add the view as accessibility element
|
||||
[elements addObject:subnode.view];
|
||||
}
|
||||
} else if (subnode.isLayerBacked) {
|
||||
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
|
||||
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
|
||||
} else if ([subnode accessibilityElementCount] > 0) {
|
||||
// UIView is itself a UIAccessibilityContainer just add it
|
||||
[elements addObject:subnode.view];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@interface _ASDisplayView () {
|
||||
NSArray *_accessibilityElements;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayView (UIAccessibilityContainer)
|
||||
|
||||
- (void)accessibilityElementDidBecomeFocused {
|
||||
ASDisplayNode *viewNode = self.asyncdisplaykit_node;
|
||||
if ([viewNode respondsToSelector:@selector(accessibilityElementDidBecomeFocused)]) {
|
||||
[viewNode accessibilityElementDidBecomeFocused];
|
||||
}
|
||||
}
|
||||
|
||||
/*- (bool)accessibilityActivate {
|
||||
ASDisplayNode *viewNode = self.asyncdisplaykit_node;
|
||||
if ([viewNode respondsToSelector:@selector(accessibilityActivate)]) {
|
||||
return [viewNode accessibilityActivate];
|
||||
}
|
||||
return false;
|
||||
}*/
|
||||
|
||||
#pragma mark - UIAccessibility
|
||||
|
||||
- (void)setAccessibilityElements:(NSArray *)accessibilityElements
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
_accessibilityElements = nil;
|
||||
}
|
||||
|
||||
- (NSArray *)accessibilityElements
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
ASDisplayNode *viewNode = self.asyncdisplaykit_node;
|
||||
if (viewNode == nil) {
|
||||
return @[];
|
||||
}
|
||||
if (true || _accessibilityElements == nil) {
|
||||
_accessibilityElements = [viewNode accessibilityElements];
|
||||
}
|
||||
return _accessibilityElements;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (AccessibilityInternal)
|
||||
|
||||
- (NSArray *)accessibilityElements
|
||||
{
|
||||
if (!self.isNodeLoaded) {
|
||||
ASDisplayNodeFailAssert(@"Cannot access accessibilityElements since node is not loaded");
|
||||
return @[];
|
||||
}
|
||||
NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init];
|
||||
CollectAccessibilityElementsForView(self.view, accessibilityElements);
|
||||
SortAccessibilityElements(accessibilityElements);
|
||||
return accessibilityElements;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayView (UIAccessibilityAction)
|
||||
|
||||
- (BOOL)accessibilityActivate {
|
||||
return [self.asyncdisplaykit_node accessibilityActivate];
|
||||
}
|
||||
|
||||
- (void)accessibilityIncrement {
|
||||
[self.asyncdisplaykit_node accessibilityIncrement];
|
||||
}
|
||||
|
||||
- (void)accessibilityDecrement {
|
||||
[self.asyncdisplaykit_node accessibilityDecrement];
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
|
||||
return [self.asyncdisplaykit_node accessibilityScroll:direction];
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityPerformEscape {
|
||||
return [self.asyncdisplaykit_node accessibilityPerformEscape];
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityPerformMagicTap {
|
||||
return [self.asyncdisplaykit_node accessibilityPerformMagicTap];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
41
submodules/AsyncDisplayKit/Source/_ASPendingState.h
Normal file
41
submodules/AsyncDisplayKit/Source/_ASPendingState.h
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// _ASPendingState.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/UIView+ASConvenience.h>
|
||||
|
||||
/**
|
||||
|
||||
Private header for ASDisplayNode.mm
|
||||
|
||||
_ASPendingState is a proxy for a UIView that has yet to be created.
|
||||
In response to its setters, it sets an internal property and a flag that indicates that that property has been set.
|
||||
|
||||
When you want to configure a view from this pending state information, just call -applyToView:
|
||||
*/
|
||||
|
||||
@interface _ASPendingState : NSObject <ASDisplayNodeViewProperties, ASDisplayProperties>
|
||||
|
||||
// Supports all of the properties included in the ASDisplayNodeViewProperties protocol
|
||||
|
||||
- (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)setFrameDirectly;
|
||||
- (void)applyToLayer:(CALayer *)layer;
|
||||
|
||||
+ (_ASPendingState *)pendingViewStateFromLayer:(CALayer *)layer;
|
||||
+ (_ASPendingState *)pendingViewStateFromView:(UIView *)view;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasSetNeedsLayout;
|
||||
@property (nonatomic, readonly) BOOL hasSetNeedsDisplay;
|
||||
|
||||
@property (nonatomic, readonly) BOOL hasChanges;
|
||||
|
||||
- (void)clearChanges;
|
||||
|
||||
@end
|
1379
submodules/AsyncDisplayKit/Source/_ASPendingState.mm
Normal file
1379
submodules/AsyncDisplayKit/Source/_ASPendingState.mm
Normal file
File diff suppressed because it is too large
Load Diff
56
submodules/AsyncDisplayKit/Source/_ASScopeTimer.h
Normal file
56
submodules/AsyncDisplayKit/Source/_ASScopeTimer.h
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// _ASScopeTimer.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
Must compile as c++ for this to work.
|
||||
|
||||
Usage:
|
||||
// Can be an ivar or local variable
|
||||
NSTimeInterval placeToStoreTiming;
|
||||
|
||||
{
|
||||
// some scope
|
||||
AS::ScopeTimer t(placeToStoreTiming);
|
||||
DoPotentiallySlowWork();
|
||||
MorePotentiallySlowWork();
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
namespace AS {
|
||||
struct ScopeTimer {
|
||||
NSTimeInterval begin;
|
||||
NSTimeInterval &outT;
|
||||
ScopeTimer(NSTimeInterval &outRef) : outT(outRef) {
|
||||
begin = CACurrentMediaTime();
|
||||
}
|
||||
~ScopeTimer() {
|
||||
outT = CACurrentMediaTime() - begin;
|
||||
}
|
||||
};
|
||||
|
||||
// variant where repeated calls are summed
|
||||
struct SumScopeTimer {
|
||||
NSTimeInterval begin;
|
||||
NSTimeInterval &outT;
|
||||
BOOL enable;
|
||||
SumScopeTimer(NSTimeInterval &outRef, BOOL enable = YES) : outT(outRef), enable(enable) {
|
||||
if (enable) {
|
||||
begin = CACurrentMediaTime();
|
||||
}
|
||||
}
|
||||
~SumScopeTimer() {
|
||||
if (enable) {
|
||||
outT += CACurrentMediaTime() - begin;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
104
submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm
Normal file
104
submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// _ASTransitionContext.mm
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/_ASTransitionContext.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
|
||||
NSString * const ASTransitionContextFromLayoutKey = @"org.asyncdisplaykit.ASTransitionContextFromLayoutKey";
|
||||
NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransitionContextToLayoutKey";
|
||||
|
||||
@interface _ASTransitionContext ()
|
||||
|
||||
@property (weak, nonatomic) id<_ASTransitionContextLayoutDelegate> layoutDelegate;
|
||||
@property (weak, nonatomic) id<_ASTransitionContextCompletionDelegate> completionDelegate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASTransitionContext
|
||||
|
||||
- (instancetype)initWithAnimation:(BOOL)animated
|
||||
layoutDelegate:(id<_ASTransitionContextLayoutDelegate>)layoutDelegate
|
||||
completionDelegate:(id<_ASTransitionContextCompletionDelegate>)completionDelegate
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_animated = animated;
|
||||
_layoutDelegate = layoutDelegate;
|
||||
_completionDelegate = completionDelegate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - ASContextTransitioning Protocol Implementation
|
||||
|
||||
- (ASLayout *)layoutForKey:(NSString *)key
|
||||
{
|
||||
return [_layoutDelegate transitionContext:self layoutForKey:key];
|
||||
}
|
||||
|
||||
- (ASSizeRange)constrainedSizeForKey:(NSString *)key
|
||||
{
|
||||
return [_layoutDelegate transitionContext:self constrainedSizeForKey:key];
|
||||
}
|
||||
|
||||
- (CGRect)initialFrameForNode:(ASDisplayNode *)node
|
||||
{
|
||||
return [[self layoutForKey:ASTransitionContextFromLayoutKey] frameForElement:node];
|
||||
}
|
||||
|
||||
- (CGRect)finalFrameForNode:(ASDisplayNode *)node
|
||||
{
|
||||
return [[self layoutForKey:ASTransitionContextToLayoutKey] frameForElement:node];
|
||||
}
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key
|
||||
{
|
||||
NSMutableArray<ASDisplayNode *> *subnodes = [[NSMutableArray alloc] init];
|
||||
for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) {
|
||||
[subnodes addObject:(ASDisplayNode *)sublayout.layoutElement];
|
||||
}
|
||||
return subnodes;
|
||||
}
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)insertedSubnodes
|
||||
{
|
||||
return [_layoutDelegate insertedSubnodesWithTransitionContext:self];
|
||||
}
|
||||
|
||||
- (NSArray<ASDisplayNode *> *)removedSubnodes
|
||||
{
|
||||
return [_layoutDelegate removedSubnodesWithTransitionContext:self];
|
||||
}
|
||||
|
||||
- (void)completeTransition:(BOOL)didComplete
|
||||
{
|
||||
[_completionDelegate transitionContext:self didComplete:didComplete];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface _ASAnimatedTransitionContext ()
|
||||
@property (nonatomic) ASDisplayNode *node;
|
||||
@property (nonatomic) CGFloat alpha;
|
||||
@end
|
||||
|
||||
@implementation _ASAnimatedTransitionContext
|
||||
|
||||
+ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alpha NS_RETURNS_RETAINED
|
||||
{
|
||||
_ASAnimatedTransitionContext *context = [[_ASAnimatedTransitionContext alloc] init];
|
||||
context.node = node;
|
||||
context.alpha = alpha;
|
||||
return context;
|
||||
}
|
||||
|
||||
@end
|
@ -281,5 +281,6 @@ open class ASButtonNode: ASControlNode {
|
||||
}
|
||||
|
||||
func f2() {
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user