mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
379 lines
11 KiB
Plaintext
379 lines
11 KiB
Plaintext
//
|
|
// 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 <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
|
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
|
|
|
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
|
#import "Private/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;
|
|
}
|