mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 16:29:55 +00:00
[Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. (#469)
* [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. * Add new "version" parameter to Yoga initialization of ASDisplayNodeLayout C++ struct.
This commit is contained in:
parent
dcaca529b4
commit
cfc48679ba
@ -1,6 +1,7 @@
|
||||
## master
|
||||
|
||||
* Add your own contributions to the next release on the line below this with your name.
|
||||
- [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [Scott Goodson](https://github.com/appleguy)
|
||||
- [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [#522](https://github.com/TextureGroup/Texture/pull/522) [Scott Goodson](https://github.com/appleguy)
|
||||
- [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][Scott Goodson](https://github.com/appleguy)
|
||||
- [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466)
|
||||
|
||||
@ -175,12 +175,15 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child;
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child;
|
||||
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index;
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
|
||||
|
||||
@property (nonatomic, assign) BOOL yogaLayoutInProgress;
|
||||
@property (nonatomic, strong, nullable) ASLayout *yogaCalculatedLayout;
|
||||
// These methods should not normally be called directly.
|
||||
|
||||
// These methods are intended to be used internally to Texture, and should not be called directly.
|
||||
- (BOOL)shouldHaveYogaMeasureFunc;
|
||||
- (void)invalidateCalculatedYogaLayout;
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
|
||||
|
||||
|
||||
@ -59,20 +59,7 @@
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
if (_yogaChildren == nil) {
|
||||
_yogaChildren = [NSMutableArray array];
|
||||
}
|
||||
|
||||
// Clean up state in case this child had another parent.
|
||||
[self removeYogaChild:child];
|
||||
|
||||
[_yogaChildren addObject:child];
|
||||
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
[self insertYogaChild:child atIndex:_yogaChildren.count];
|
||||
}
|
||||
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child
|
||||
@ -87,6 +74,24 @@
|
||||
child.yogaParent = nil;
|
||||
}
|
||||
|
||||
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
|
||||
{
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
if (_yogaChildren == nil) {
|
||||
_yogaChildren = [NSMutableArray array];
|
||||
}
|
||||
|
||||
// Clean up state in case this child had another parent.
|
||||
[self removeYogaChild:child];
|
||||
|
||||
[_yogaChildren insertObject:child atIndex:index];
|
||||
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
}
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
|
||||
{
|
||||
if (AS_AT_LEAST_IOS9) {
|
||||
@ -168,28 +173,72 @@
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
|
||||
// Assert that the sublayout is already flattened.
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) {
|
||||
ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now.
|
||||
layout = [layout filteredNodeLayoutTree];
|
||||
|
||||
if ([self.yogaCalculatedLayout isEqual:layout] == NO) {
|
||||
self.yogaCalculatedLayout = layout;
|
||||
} else {
|
||||
layout = self.yogaCalculatedLayout;
|
||||
ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout);
|
||||
}
|
||||
|
||||
// Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node.
|
||||
// Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode).
|
||||
// Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits:
|
||||
|
||||
// For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set?
|
||||
// When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout
|
||||
// has a size which matches our current bounds size. If it does, that layout will be used without recomputing it.
|
||||
|
||||
// NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves).
|
||||
// Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits:
|
||||
// These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's
|
||||
// own internal cache.
|
||||
|
||||
if ([self shouldHaveYogaMeasureFunc] == NO) {
|
||||
YGNodeRef parentNode = YGNodeGetParent(yogaNode);
|
||||
CGSize parentSize = CGSizeZero;
|
||||
if (parentNode) {
|
||||
parentSize.width = YGNodeLayoutGetWidth(parentNode);
|
||||
parentSize.height = YGNodeLayoutGetHeight(parentNode);
|
||||
}
|
||||
_pendingDisplayNodeLayout = std::make_shared<ASDisplayNodeLayout>(layout, ASSizeRangeUnconstrained, parentSize, 0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateYogaMeasureFuncIfNeeded
|
||||
- (BOOL)shouldHaveYogaMeasureFunc
|
||||
{
|
||||
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
|
||||
// This will be used for ASTextNode, as well as any other node that has no Yoga children
|
||||
BOOL isLeafNode = (self.yogaChildren.count == 0);
|
||||
BOOL definesCustomLayout = [self implementsLayoutMethod];
|
||||
return (isLeafNode && definesCustomLayout);
|
||||
}
|
||||
|
||||
- (void)updateYogaMeasureFuncIfNeeded
|
||||
{
|
||||
// We set the measure func only during layout. Otherwise, a cycle is created:
|
||||
// The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef.
|
||||
BOOL shouldHaveMeasureFunc = (isLeafNode && definesCustomLayout && checkFlag(YogaLayoutInProgress));
|
||||
BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress));
|
||||
|
||||
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil);
|
||||
}
|
||||
|
||||
- (void)invalidateCalculatedYogaLayout
|
||||
{
|
||||
// Yoga internally asserts that this method may only be called on nodes with a measurement function.
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
if (yogaNode && YGNodeGetMeasureFunc(yogaNode)) {
|
||||
// Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function.
|
||||
YGNodeMarkDirty(yogaNode);
|
||||
}
|
||||
self.yogaCalculatedLayout = nil;
|
||||
@ -200,7 +249,7 @@
|
||||
ASDisplayNode *yogaParent = self.yogaParent;
|
||||
|
||||
if (yogaParent) {
|
||||
ASYogaLog(@"ESCALATING to Yoga root: %@", self);
|
||||
ASYogaLog("ESCALATING to Yoga root: %@", self);
|
||||
// TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually.
|
||||
[yogaParent calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained];
|
||||
return;
|
||||
@ -217,7 +266,7 @@
|
||||
rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||
}
|
||||
|
||||
ASYogaLog(@"CALCULATING at Yoga root with constraint = {%@, %@}: %@",
|
||||
ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@",
|
||||
NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self);
|
||||
|
||||
YGNodeRef rootYogaNode = self.style.yogaNode;
|
||||
|
||||
@ -967,23 +967,30 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren.
|
||||
// - This node is a Yoga tree node: it has both a yogaParent and yogaChildren.
|
||||
// - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren.
|
||||
// If we're a leaf node, we are probably being called by a measure function and proceed as normal.
|
||||
// If we're a yoga root or tree node, initiate a new Yoga calculation pass from root.
|
||||
YGNodeRef yogaNode = _style.yogaNode;
|
||||
BOOL hasYogaParent = (_yogaParent != nil);
|
||||
BOOL hasYogaChildren = (_yogaChildren.count > 0);
|
||||
BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren));
|
||||
if (usesYoga && (_yogaParent == nil || _yogaChildren.count > 0)) {
|
||||
if (usesYoga) {
|
||||
// This node has some connection to a Yoga tree.
|
||||
if ([self shouldHaveYogaMeasureFunc] == NO) {
|
||||
// If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then
|
||||
// initiate a new Yoga calculation pass from root.
|
||||
ASDN::MutexUnlocker ul(__instanceLock__);
|
||||
|
||||
as_activity_create_for_scope("Yoga layout calculation");
|
||||
if (self.yogaLayoutInProgress == NO) {
|
||||
ASYogaLog("Calculating yoga layout from root %@, %@", self, NSStringFromASSizeRange(constrainedSize));
|
||||
[self calculateLayoutFromYogaRoot:constrainedSize];
|
||||
} else {
|
||||
ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout);
|
||||
}
|
||||
ASDisplayNodeAssert(_yogaCalculatedLayout, @"Yoga node should have a non-nil layout at this stage: %@", self);
|
||||
return _yogaCalculatedLayout;
|
||||
} else {
|
||||
// If we're a yoga leaf node with custom measurement function, proceed with normal layout so layoutSpecs can run (e.g. ASButtonNode).
|
||||
ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self);
|
||||
}
|
||||
}
|
||||
ASYogaLog(@"PROCEEDING past Yoga check to calculate ASLayout for: %@", self);
|
||||
#endif /* YOGA */
|
||||
|
||||
// Manual size calculation via calculateSizeThatFits:
|
||||
|
||||
@ -271,6 +271,27 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
|
||||
return layout;
|
||||
}
|
||||
|
||||
#pragma mark - Equality Checking
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
ASLayout *layout = ASDynamicCast(object, ASLayout);
|
||||
if (layout == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!CGSizeEqualToSize(_size, layout.size)) return NO;
|
||||
if (!CGPointEqualToPoint(_position, layout.position)) return NO;
|
||||
if (_layoutElement != layout.layoutElement) return NO;
|
||||
|
||||
NSArray *sublayouts = layout.sublayouts;
|
||||
if (sublayouts != _sublayouts && (sublayouts == nil || _sublayouts == nil || ![_sublayouts isEqual:sublayouts])) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Accessors
|
||||
|
||||
- (ASLayoutElementType)type
|
||||
|
||||
@ -15,9 +15,11 @@
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
|
||||
#define ASYogaLog(...) //NSLog(__VA_ARGS__)
|
||||
// Should pass a string literal, not an NSString as the first argument to ASYogaLog
|
||||
#define ASYogaLog(x, ...) as_log_verbose(ASLayoutLog(), x, ##__VA_ARGS__);
|
||||
|
||||
@interface ASDisplayNode (YogaHelpers)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user