mirror of
https://github.com/TextureGroup/Texture.git
synced 2026-04-07 19:17:39 +00:00
Add support for basic ASTextNode2 link highlighting (#1518)
* Add support for basic ASTextNode2 link highlighting * Add approach to fix line highlighting * Enable proper highlighting for first row * Fix snapshot highlighting test for ASTextNode2 * Update screenshot again * Address comments
This commit is contained in:
committed by
Adlai Holler
parent
1b4b6a3f02
commit
ab8ea069ea
@@ -766,6 +766,7 @@ static NSArray *DefaultLinkAttributeNames() {
|
||||
- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASLockScopeSelf();
|
||||
|
||||
_highlightedLinkAttributeName = highlightedAttributeName;
|
||||
_highlightedLinkAttributeValue = highlightedAttributeValue;
|
||||
@@ -825,7 +826,6 @@ static NSArray *DefaultLinkAttributeNames() {
|
||||
}
|
||||
|
||||
if (highlightTargetLayer != nil) {
|
||||
ASLockScopeSelf();
|
||||
ASTextKitRenderer *renderer = [self _locked_renderer];
|
||||
|
||||
NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock];
|
||||
|
||||
+109
-4
@@ -137,6 +137,8 @@ static NS_RETURNS_RETAINED ASTextLayout *ASTextNodeCompatibleLayoutWithContainer
|
||||
return layout;
|
||||
}
|
||||
|
||||
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
|
||||
static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1;
|
||||
static const CGFloat ASTextNodeHighlightLightOpacity = 0.11;
|
||||
static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22;
|
||||
static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute";
|
||||
@@ -778,17 +780,114 @@ static NSArray *DefaultLinkAttributeNames() {
|
||||
|
||||
- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASLockScopeSelf(); // Protect usage of _highlight* ivars.
|
||||
|
||||
// Set these so that link tapping works.
|
||||
_highlightedLinkAttributeName = highlightedAttributeName;
|
||||
_highlightedLinkAttributeValue = highlightedAttributeValue;
|
||||
_highlightRange = highlightRange;
|
||||
|
||||
AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();
|
||||
// Much of the code from original ASTextNode is probably usable here.
|
||||
if (!NSEqualRanges(highlightRange, _highlightRange) && ((0 != highlightRange.length) || (0 != _highlightRange.length))) {
|
||||
|
||||
return;
|
||||
_highlightRange = highlightRange;
|
||||
|
||||
if (_activeHighlightLayer) {
|
||||
if (animated) {
|
||||
__weak CALayer *weakHighlightLayer = _activeHighlightLayer;
|
||||
_activeHighlightLayer = nil;
|
||||
|
||||
weakHighlightLayer.opacity = 0.0;
|
||||
|
||||
CFTimeInterval beginTime = CACurrentMediaTime();
|
||||
CABasicAnimation *possibleFadeIn = (CABasicAnimation *)[weakHighlightLayer animationForKey:@"opacity"];
|
||||
if (possibleFadeIn) {
|
||||
// Calculate when we should begin fading out based on the end of the fade in animation,
|
||||
// Also check to make sure that the new begin time hasn't already passed
|
||||
CGFloat newBeginTime = (possibleFadeIn.beginTime + possibleFadeIn.duration);
|
||||
if (newBeginTime > beginTime) {
|
||||
beginTime = newBeginTime;
|
||||
}
|
||||
}
|
||||
|
||||
CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
fadeOut.fromValue = possibleFadeIn.toValue ? : @(((CALayer *)weakHighlightLayer.presentationLayer).opacity);
|
||||
fadeOut.toValue = @0.0;
|
||||
fadeOut.fillMode = kCAFillModeBoth;
|
||||
fadeOut.duration = ASTextNodeHighlightFadeOutDuration;
|
||||
fadeOut.beginTime = beginTime;
|
||||
|
||||
dispatch_block_t prev = [CATransaction completionBlock];
|
||||
[CATransaction setCompletionBlock:^{
|
||||
[weakHighlightLayer removeFromSuperlayer];
|
||||
}];
|
||||
|
||||
[weakHighlightLayer addAnimation:fadeOut forKey:fadeOut.keyPath];
|
||||
|
||||
[CATransaction setCompletionBlock:prev];
|
||||
|
||||
} else {
|
||||
[_activeHighlightLayer removeFromSuperlayer];
|
||||
_activeHighlightLayer = nil;
|
||||
}
|
||||
}
|
||||
if (0 != highlightRange.length) {
|
||||
// Find layer in hierarchy that allows us to draw highlighting on.
|
||||
CALayer *highlightTargetLayer = self.layer;
|
||||
while (highlightTargetLayer != nil) {
|
||||
if (highlightTargetLayer.as_allowsHighlightDrawing) {
|
||||
break;
|
||||
}
|
||||
highlightTargetLayer = highlightTargetLayer.superlayer;
|
||||
}
|
||||
|
||||
if (highlightTargetLayer != nil) {
|
||||
// TODO: The copy and application of size shouldn't be required, but it is currently.
|
||||
// See discussion in https://github.com/TextureGroup/Texture/pull/396
|
||||
ASTextContainer *textContainerCopy = [_textContainer copy];
|
||||
textContainerCopy.size = self.calculatedSize;
|
||||
ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(textContainerCopy, _attributedText);
|
||||
|
||||
NSArray<ASTextSelectionRect *> *highlightRects = [layout selectionRectsWithoutStartAndEndForRange:[ASTextRange rangeWithRange:highlightRange]];
|
||||
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
|
||||
|
||||
CALayer *layer = self.layer;
|
||||
UIEdgeInsets shadowPadding = self.shadowPadding;
|
||||
for (ASTextSelectionRect *rectValue in highlightRects) {
|
||||
// Adjust shadow padding
|
||||
CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.rect, shadowPadding);
|
||||
CGRect highlightedRect = [layer convertRect:rendererRect toLayer:highlightTargetLayer];
|
||||
|
||||
// We set our overlay layer's frame to the bounds of the highlight target layer.
|
||||
// Offset highlight rects to avoid double-counting target layer's bounds.origin.
|
||||
highlightedRect.origin.x -= highlightTargetLayer.bounds.origin.x;
|
||||
highlightedRect.origin.y -= highlightTargetLayer.bounds.origin.y;
|
||||
[converted addObject:[NSValue valueWithCGRect:highlightedRect]];
|
||||
}
|
||||
|
||||
ASHighlightOverlayLayer *overlayLayer = [[ASHighlightOverlayLayer alloc] initWithRects:converted];
|
||||
overlayLayer.highlightColor = [[self class] _highlightColorForStyle:self.highlightStyle];
|
||||
overlayLayer.frame = highlightTargetLayer.bounds;
|
||||
overlayLayer.masksToBounds = NO;
|
||||
overlayLayer.opacity = [[self class] _highlightOpacityForStyle:self.highlightStyle];
|
||||
[highlightTargetLayer addSublayer:overlayLayer];
|
||||
|
||||
if (animated) {
|
||||
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"];
|
||||
fadeIn.fromValue = @0.0;
|
||||
fadeIn.toValue = @(overlayLayer.opacity);
|
||||
fadeIn.duration = ASTextNodeHighlightFadeInDuration;
|
||||
fadeIn.beginTime = CACurrentMediaTime();
|
||||
|
||||
[overlayLayer addAnimation:fadeIn forKey:fadeIn.keyPath];
|
||||
}
|
||||
|
||||
[overlayLayer setNeedsDisplay];
|
||||
|
||||
_activeHighlightLayer = overlayLayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_clearHighlightIfNecessary
|
||||
@@ -812,6 +911,12 @@ static NSArray *DefaultLinkAttributeNames() {
|
||||
|
||||
#pragma mark - Text rects
|
||||
|
||||
static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) {
|
||||
rendererRect.origin.x -= shadowPadding.left;
|
||||
rendererRect.origin.y -= shadowPadding.top;
|
||||
return rendererRect;
|
||||
}
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
{
|
||||
AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();
|
||||
|
||||
@@ -2146,7 +2146,9 @@ dispatch_semaphore_signal(_lock);
|
||||
if (isVertical) {
|
||||
topRect.rect = CGRectMake(startLine.left, topOffset, startLine.width, (_container.path ? startLine.bottom : _container.size.height - _container.insets.bottom) - topOffset);
|
||||
} else {
|
||||
topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset, startLine.height);
|
||||
// TODO: Fixes highlighting first row only to the end of the text and not highlight
|
||||
// the while line to the end. Needs to brought over to multiline support
|
||||
topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset - (_container.size.width - _container.insets.right - startLine.right), startLine.height);
|
||||
}
|
||||
}
|
||||
[rects addObject:topRect];
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.5 KiB |
Reference in New Issue
Block a user