mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +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 --swiftcopt='-w'
|
||||||
build --spawn_strategy=local
|
build --spawn_strategy=local
|
||||||
build --strategy=SwiftCompile=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"]
|
[submodule "submodules/TgVoip/libtgvoip"]
|
||||||
path = submodules/TgVoip/libtgvoip
|
path = submodules/TgVoip/libtgvoip
|
||||||
url = https://github.com/telegramdesktop/libtgvoip.git
|
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"
|
rm -rf "$GEN_DIRECTORY"
|
||||||
mkdir -p "$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=$(sysctl -n hw.logicalcpu)
|
||||||
CORE_COUNT_MINUS_ONE=$(expr ${CORE_COUNT} \- 1)
|
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')")
|
BAZEL_OPTIONS=("${BAZEL_OPTIONS[@]}" --disk_cache="$(echo $BAZEL_CACHE_DIR | sed -e 's/[\/&]/\\&/g')")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$HOME/Applications/Tulsi.app/Contents/MacOS/Tulsi -- \
|
"$TULSI" -- \
|
||||||
--verbose \
|
--verbose \
|
||||||
--create-tulsiproj Telegram \
|
--create-tulsiproj Telegram \
|
||||||
--workspaceroot ./ \
|
--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"
|
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 \
|
--verbose \
|
||||||
--genconfig "$GEN_DIRECTORY/Telegram.tulsiproj:Telegram" \
|
--genconfig "$GEN_DIRECTORY/Telegram.tulsiproj:Telegram" \
|
||||||
--bazel "$BAZEL" \
|
--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;
|
@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface CALayer (ASDisplayNodeInternal)
|
||||||
|
@property (nullable, weak) ASDisplayNode *asyncdisplaykit_node;
|
||||||
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_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() {
|
func f2() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user