Perf improvements for CVC rendering
Fixes some low-hanging expensive renders in CVC. Namely: - There are a few places where shadow paths can be trivially calculated. Explicitly setting the shadow path saves an offscreen pass. - Adopts CALayer corner masking where feasible (e.g. layers with consistent corner radii).
This commit is contained in:
parent
a0d542657b
commit
01a4ee4cc7
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -120,6 +120,9 @@ class CallButton: UIButton {
|
||||
currentConstraints.removeAll()
|
||||
|
||||
currentConstraints += circleView.autoSetDimensions(to: CGSize(square: currentIconSize))
|
||||
circleView.layer.shadowPath = UIBezierPath(
|
||||
ovalIn: CGRect(origin: .zero, size: .square(currentIconSize))
|
||||
).cgPath
|
||||
currentConstraints += iconView.autoPinEdgesToSuperviewEdges(with: currentIconInsets)
|
||||
if let dropdownIconView = dropdownIconView {
|
||||
currentConstraints.append(dropdownIconView.autoPinEdge(.leading, to: .trailing, of: iconView, withOffset: isSmall ? 0 : 2))
|
||||
|
||||
@ -250,6 +250,9 @@ class ProfileSettingsViewController: OWSTableViewController2 {
|
||||
cameraImageContainer.layer.shadowOpacity = 0.2
|
||||
cameraImageContainer.layer.shadowRadius = 4
|
||||
cameraImageContainer.layer.shadowOffset = CGSize(width: 0, height: 2)
|
||||
cameraImageContainer.layer.shadowPath = UIBezierPath(
|
||||
ovalIn: CGRect(origin: .zero, size: .square(32))
|
||||
).cgPath
|
||||
|
||||
cell.contentView.addSubview(cameraImageContainer)
|
||||
cameraImageContainer.autoPinTrailing(toEdgeOf: avatarImageView)
|
||||
@ -260,6 +263,9 @@ class ProfileSettingsViewController: OWSTableViewController2 {
|
||||
secondaryShadowView.layer.shadowOpacity = 0.12
|
||||
secondaryShadowView.layer.shadowRadius = 16
|
||||
secondaryShadowView.layer.shadowOffset = CGSize(width: 0, height: 4)
|
||||
secondaryShadowView.layer.shadowPath = UIBezierPath(
|
||||
ovalIn: CGRect(origin: .zero, size: .square(32))
|
||||
).cgPath
|
||||
|
||||
cameraImageContainer.addSubview(secondaryShadowView)
|
||||
secondaryShadowView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -167,6 +167,42 @@ public class QuotedMessageView: UIView {
|
||||
: conversationStyle.quotingSelfHighlightColor())
|
||||
}
|
||||
|
||||
func createInnerBubbleView(sharpCorners: OWSDirectionalRectCorner) -> UIView {
|
||||
let sharpCornerRadius: CGFloat = 4
|
||||
let wideCornerRadius: CGFloat = 12
|
||||
|
||||
let innerBubbleView: UIView
|
||||
if sharpCorners.isEmpty || sharpCorners.contains(.allCorners) {
|
||||
innerBubbleView = UIView()
|
||||
innerBubbleView.layer.maskedCorners = .all
|
||||
innerBubbleView.layer.cornerRadius = sharpCorners.isEmpty ? wideCornerRadius : sharpCornerRadius
|
||||
|
||||
} else {
|
||||
// Slow path. CA isn't optimized to handle corners of multiple radii
|
||||
// Let's do it by hand with a CAShapeLayer
|
||||
let maskLayer = CAShapeLayer()
|
||||
innerBubbleView = OWSLayerView(frame: .zero) { (layerView: UIView) in
|
||||
let layerFrame = layerView.bounds
|
||||
|
||||
let bubbleLeft: CGFloat = 0
|
||||
let bubbleRight = layerFrame.width
|
||||
let bubbleTop: CGFloat = 0
|
||||
let bubbleBottom = layerFrame.height
|
||||
let bezierPath = OWSBubbleView.roundedBezierRect(withBubbleTop: bubbleTop,
|
||||
bubbleLeft: bubbleLeft,
|
||||
bubbleBottom: bubbleBottom,
|
||||
bubbleRight: bubbleRight,
|
||||
sharpCornerRadius: sharpCornerRadius,
|
||||
wideCornerRadius: wideCornerRadius,
|
||||
sharpCorners: sharpCorners)
|
||||
maskLayer.path = bezierPath.cgPath
|
||||
}
|
||||
innerBubbleView.layer.mask = maskLayer
|
||||
}
|
||||
innerBubbleView.backgroundColor = conversationStyle.quotedReplyBubbleColor
|
||||
return innerBubbleView
|
||||
}
|
||||
|
||||
func createContents(rootView: UIView,
|
||||
quotedAuthorLabel: UILabel,
|
||||
quotedTextLabel: UILabel,
|
||||
@ -179,32 +215,7 @@ public class QuotedMessageView: UIView {
|
||||
rootView.layoutMargins = .zero
|
||||
rootView.clipsToBounds = true
|
||||
|
||||
let maskLayer = CAShapeLayer()
|
||||
|
||||
let innerBubbleView = OWSLayerView(frame: .zero) { (layerView: UIView) in
|
||||
let layerFrame = layerView.bounds
|
||||
|
||||
let bubbleLeft: CGFloat = 0
|
||||
let bubbleRight = layerFrame.width
|
||||
let bubbleTop: CGFloat = 0
|
||||
let bubbleBottom = layerFrame.height
|
||||
|
||||
let sharpCornerRadius: CGFloat = 4
|
||||
let wideCornerRadius: CGFloat = 12
|
||||
|
||||
let bezierPath = OWSBubbleView.roundedBezierRect(withBubbleTop: bubbleTop,
|
||||
bubbleLeft: bubbleLeft,
|
||||
bubbleBottom: bubbleBottom,
|
||||
bubbleRight: bubbleRight,
|
||||
sharpCornerRadius: sharpCornerRadius,
|
||||
wideCornerRadius: wideCornerRadius,
|
||||
sharpCorners: sharpCorners)
|
||||
|
||||
maskLayer.path = bezierPath.cgPath
|
||||
}
|
||||
|
||||
innerBubbleView.layer.mask = maskLayer
|
||||
innerBubbleView.backgroundColor = conversationStyle.quotedReplyBubbleColor
|
||||
let innerBubbleView = createInnerBubbleView(sharpCorners: sharpCorners)
|
||||
rootView.addSubview(innerBubbleView)
|
||||
innerBubbleView.autoPinLeadingToSuperviewMargin(withInset: bubbleHMargin)
|
||||
innerBubbleView.autoPinTrailingToSuperviewMargin(withInset: bubbleHMargin)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ConversationScrollButton.h"
|
||||
@ -67,6 +67,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
iconView.userInteractionEnabled = NO;
|
||||
|
||||
const CGFloat circleSize = self.class.circleSize;
|
||||
CGRect shadowRect = CGRectMake(0, 0, circleSize, circleSize);
|
||||
|
||||
UIView *shadowView = [[OWSCircleView alloc] initWithDiameter:circleSize];
|
||||
self.shadowView = shadowView;
|
||||
@ -75,6 +76,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
shadowView.layer.shadowRadius = 4;
|
||||
shadowView.layer.shadowOpacity = 0.05f;
|
||||
shadowView.layer.shadowColor = UIColor.blackColor.CGColor;
|
||||
shadowView.layer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:shadowRect].CGPath;
|
||||
|
||||
UIView *circleView = [[OWSCircleView alloc] initWithDiameter:circleSize];
|
||||
self.circleView = circleView;
|
||||
@ -83,6 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
circleView.layer.shadowRadius = 12.f;
|
||||
circleView.layer.shadowOpacity = 0.3f;
|
||||
circleView.layer.shadowColor = UIColor.blackColor.CGColor;
|
||||
circleView.layer.shadowPath = [UIBezierPath bezierPathWithOvalInRect:shadowRect].CGPath;
|
||||
|
||||
UIView *unreadBadge = [UIView new];
|
||||
self.unreadBadge = unreadBadge;
|
||||
|
||||
@ -339,13 +339,9 @@ public protocol LinkPreviewViewDraftDelegate {
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
public class LinkPreviewImageView: UIImageView {
|
||||
private let maskLayer = CAShapeLayer()
|
||||
|
||||
@objc
|
||||
public enum Rounding: UInt {
|
||||
case none
|
||||
private class LinkPreviewImageView: UIImageView {
|
||||
fileprivate enum Rounding: UInt {
|
||||
case standard
|
||||
case asymmetrical
|
||||
case circular
|
||||
}
|
||||
@ -353,34 +349,38 @@ public class LinkPreviewImageView: UIImageView {
|
||||
private let rounding: Rounding
|
||||
fileprivate var isHero = false
|
||||
|
||||
@objc
|
||||
public init(rounding: Rounding) {
|
||||
self.rounding = rounding
|
||||
// We only need to use a more complicated corner mask if we're
|
||||
// drawing asymmetric corners. This is an exceptional case to match
|
||||
// the input toolbar curve.
|
||||
private let asymmetricCornerMask = CAShapeLayer()
|
||||
|
||||
init(rounding: Rounding) {
|
||||
self.rounding = rounding
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.layer.mask = maskLayer
|
||||
if rounding == .asymmetrical {
|
||||
layer.mask = asymmetricCornerMask
|
||||
}
|
||||
}
|
||||
|
||||
public required init?(coder aDecoder: NSCoder) {
|
||||
self.rounding = .none
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
self.rounding = .standard
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
public override var bounds: CGRect {
|
||||
override var bounds: CGRect {
|
||||
didSet {
|
||||
updateMaskLayer()
|
||||
}
|
||||
}
|
||||
|
||||
public override var frame: CGRect {
|
||||
override var frame: CGRect {
|
||||
didSet {
|
||||
updateMaskLayer()
|
||||
}
|
||||
}
|
||||
|
||||
public override var center: CGPoint {
|
||||
override var center: CGPoint {
|
||||
didSet {
|
||||
updateMaskLayer()
|
||||
}
|
||||
@ -388,62 +388,62 @@ public class LinkPreviewImageView: UIImageView {
|
||||
|
||||
private func updateMaskLayer() {
|
||||
let layerBounds = self.bounds
|
||||
|
||||
guard rounding != .circular else {
|
||||
maskLayer.path = UIBezierPath(ovalIn: layerBounds).cgPath
|
||||
return
|
||||
}
|
||||
|
||||
// One of the corners has assymetrical rounding to match the input toolbar border.
|
||||
// This is somewhat inconvenient.
|
||||
let upperLeft = CGPoint(x: 0, y: 0)
|
||||
let upperRight = CGPoint(x: layerBounds.size.width, y: 0)
|
||||
let lowerRight = CGPoint(x: layerBounds.size.width, y: layerBounds.size.height)
|
||||
let lowerLeft = CGPoint(x: 0, y: layerBounds.size.height)
|
||||
|
||||
let bigRounding: CGFloat = 14
|
||||
let smallRounding: CGFloat = 6
|
||||
|
||||
let upperLeftRounding: CGFloat
|
||||
let upperRightRounding: CGFloat
|
||||
if rounding == .asymmetrical {
|
||||
upperLeftRounding = CurrentAppContext().isRTL ? smallRounding : bigRounding
|
||||
upperRightRounding = CurrentAppContext().isRTL ? bigRounding : smallRounding
|
||||
} else {
|
||||
upperLeftRounding = smallRounding
|
||||
upperRightRounding = smallRounding
|
||||
switch rounding {
|
||||
case .standard:
|
||||
layer.cornerRadius = smallRounding
|
||||
layer.maskedCorners = isHero ? .top : .all
|
||||
case .circular:
|
||||
layer.cornerRadius = bounds.size.smallerAxis
|
||||
layer.maskedCorners = .all
|
||||
case .asymmetrical:
|
||||
// This uses a more expensive layer mask to clip corners
|
||||
// with different radii.
|
||||
// This should only be used in the input toolbar so perf is
|
||||
// less of a concern here.
|
||||
owsAssertDebug(!isHero, "Link preview drafts never use hero images")
|
||||
|
||||
let upperLeft = CGPoint(x: 0, y: 0)
|
||||
let upperRight = CGPoint(x: layerBounds.size.width, y: 0)
|
||||
let lowerRight = CGPoint(x: layerBounds.size.width, y: layerBounds.size.height)
|
||||
let lowerLeft = CGPoint(x: 0, y: layerBounds.size.height)
|
||||
|
||||
let upperLeftRounding: CGFloat = CurrentAppContext().isRTL ? smallRounding : bigRounding
|
||||
let upperRightRounding: CGFloat = CurrentAppContext().isRTL ? bigRounding : smallRounding
|
||||
let lowerRightRounding = smallRounding
|
||||
let lowerLeftRounding = smallRounding
|
||||
|
||||
let path = UIBezierPath()
|
||||
|
||||
// It's sufficient to "draw" the rounded corners and not the edges that connect them.
|
||||
path.addArc(withCenter: upperLeft.offsetBy(dx: +upperLeftRounding).offsetBy(dy: +upperLeftRounding),
|
||||
radius: upperLeftRounding,
|
||||
startAngle: CGFloat.pi * 1.0,
|
||||
endAngle: CGFloat.pi * 1.5,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: upperRight.offsetBy(dx: -upperRightRounding).offsetBy(dy: +upperRightRounding),
|
||||
radius: upperRightRounding,
|
||||
startAngle: CGFloat.pi * 1.5,
|
||||
endAngle: CGFloat.pi * 0.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: lowerRight.offsetBy(dx: -lowerRightRounding).offsetBy(dy: -lowerRightRounding),
|
||||
radius: lowerRightRounding,
|
||||
startAngle: CGFloat.pi * 0.0,
|
||||
endAngle: CGFloat.pi * 0.5,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: lowerLeft.offsetBy(dx: +lowerLeftRounding).offsetBy(dy: -lowerLeftRounding),
|
||||
radius: lowerLeftRounding,
|
||||
startAngle: CGFloat.pi * 0.5,
|
||||
endAngle: CGFloat.pi * 1.0,
|
||||
clockwise: true)
|
||||
|
||||
asymmetricCornerMask.path = path.cgPath
|
||||
}
|
||||
let lowerRightRounding = isHero ? 0 : smallRounding
|
||||
let lowerLeftRounding = isHero ? 0 : smallRounding
|
||||
|
||||
let path = UIBezierPath()
|
||||
|
||||
// It's sufficient to "draw" the rounded corners and not the edges that connect them.
|
||||
path.addArc(withCenter: upperLeft.offsetBy(dx: +upperLeftRounding).offsetBy(dy: +upperLeftRounding),
|
||||
radius: upperLeftRounding,
|
||||
startAngle: CGFloat.pi * 1.0,
|
||||
endAngle: CGFloat.pi * 1.5,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: upperRight.offsetBy(dx: -upperRightRounding).offsetBy(dy: +upperRightRounding),
|
||||
radius: upperRightRounding,
|
||||
startAngle: CGFloat.pi * 1.5,
|
||||
endAngle: CGFloat.pi * 0.0,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: lowerRight.offsetBy(dx: -lowerRightRounding).offsetBy(dy: -lowerRightRounding),
|
||||
radius: lowerRightRounding,
|
||||
startAngle: CGFloat.pi * 0.0,
|
||||
endAngle: CGFloat.pi * 0.5,
|
||||
clockwise: true)
|
||||
|
||||
path.addArc(withCenter: lowerLeft.offsetBy(dx: +lowerLeftRounding).offsetBy(dy: -lowerLeftRounding),
|
||||
radius: lowerLeftRounding,
|
||||
startAngle: CGFloat.pi * 0.5,
|
||||
endAngle: CGFloat.pi * 1.0,
|
||||
clockwise: true)
|
||||
|
||||
maskLayer.path = path.cgPath
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,6 +472,7 @@ public class LinkPreviewView: UIStackView {
|
||||
public var hasAsymmetricalRounding: Bool = false {
|
||||
didSet {
|
||||
AssertIsOnMainThread()
|
||||
owsAssertDebug(isDraft)
|
||||
|
||||
if hasAsymmetricalRounding != oldValue {
|
||||
updateContents()
|
||||
@ -1016,13 +1017,7 @@ public class LinkPreviewView: UIStackView {
|
||||
owsFailDebug("Could not load image.")
|
||||
return nil
|
||||
}
|
||||
let rounding: LinkPreviewImageView.Rounding = {
|
||||
if let roundingParam = roundingParam {
|
||||
return roundingParam
|
||||
}
|
||||
return self.hasAsymmetricalRounding ? .asymmetrical : .none
|
||||
}()
|
||||
let imageView = LinkPreviewImageView(rounding: rounding)
|
||||
let imageView = LinkPreviewImageView(rounding: roundingParam ?? .standard)
|
||||
imageView.image = image
|
||||
imageView.isHero = Self.sentIsHero(state: state)
|
||||
return imageView
|
||||
@ -1041,7 +1036,7 @@ public class LinkPreviewView: UIStackView {
|
||||
owsFailDebug("Could not load image.")
|
||||
return nil
|
||||
}
|
||||
let rounding: LinkPreviewImageView.Rounding = hasAsymmetricalRounding ? .asymmetrical : .none
|
||||
let rounding: LinkPreviewImageView.Rounding = hasAsymmetricalRounding ? .asymmetrical : .standard
|
||||
let imageView = LinkPreviewImageView(rounding: rounding)
|
||||
imageView.image = image
|
||||
return imageView
|
||||
@ -1268,29 +1263,6 @@ public class LinkPreviewView: UIStackView {
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func addBorderViews(bubbleView: OWSBubbleView) {
|
||||
if let heroImageView = self.heroImageView {
|
||||
let borderView = OWSBubbleShapeView(draw: ())
|
||||
borderView.strokeColor = Theme.primaryTextColor
|
||||
borderView.strokeThickness = CGHairlineWidthFraction(1.8)
|
||||
heroImageView.addSubview(borderView)
|
||||
bubbleView.addPartnerView(borderView)
|
||||
borderView.autoPinEdgesToSuperviewEdges()
|
||||
}
|
||||
if let sentBodyView = self.sentBodyView {
|
||||
let borderView = OWSBubbleShapeView(draw: ())
|
||||
let borderColor = (Theme.isDarkThemeEnabled ? UIColor.ows_gray60 : UIColor.ows_gray15)
|
||||
borderView.strokeColor = borderColor
|
||||
borderView.strokeThickness = CGHairlineWidthFraction(1.8)
|
||||
sentBodyView.addSubview(borderView)
|
||||
bubbleView.addPartnerView(borderView)
|
||||
borderView.autoPinEdgesToSuperviewEdges()
|
||||
} else {
|
||||
owsFailDebug("Missing sentBodyView")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func didTapCancel(sender: UIButton) {
|
||||
self.draftDelegate?.linkPreviewDidCancel()
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBubbleShapeView.h"
|
||||
@ -255,9 +255,6 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
|
||||
self.layer.mask = self.maskLayer;
|
||||
break;
|
||||
case OWSBubbleShapeViewMode_InnerShadow: {
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
self.layer.mask = self.maskLayer;
|
||||
|
||||
// Inner shadow.
|
||||
// This should usually not be visible; it is used to distinguish
|
||||
// profile pics from the background if they are similar.
|
||||
@ -267,16 +264,21 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) {
|
||||
UIBezierPath *shadowPath = [bezierPath copy];
|
||||
// This can be any value large enough to cast a sufficiently large shadow.
|
||||
CGFloat shadowInset = -(self.innerShadowRadius * 4.f);
|
||||
[shadowPath
|
||||
appendPath:[UIBezierPath bezierPathWithRect:CGRectInset(shadowBounds, shadowInset, shadowInset)]];
|
||||
|
||||
CGRect outerRect = CGRectInset(shadowBounds, shadowInset, shadowInset);
|
||||
UIBezierPath *outerPath = [UIBezierPath bezierPathWithRect:outerRect];
|
||||
// -[CALayer shadowPath] uses the non-zero winding rule
|
||||
// Reverse the path to make the directions line up correctly.
|
||||
[shadowPath appendPath:[outerPath bezierPathByReversingPath]];
|
||||
|
||||
// This can be any color since the fill should be clipped.
|
||||
self.shapeLayer.fillColor = UIColor.blackColor.CGColor;
|
||||
self.shapeLayer.path = shadowPath.CGPath;
|
||||
self.shapeLayer.fillRule = kCAFillRuleEvenOdd;
|
||||
self.shapeLayer.shadowColor = self.innerShadowColor.CGColor;
|
||||
self.shapeLayer.shadowRadius = self.innerShadowRadius;
|
||||
self.shapeLayer.shadowOpacity = self.innerShadowOpacity;
|
||||
self.shapeLayer.shadowOffset = CGSizeZero;
|
||||
self.shapeLayer.shadowPath = shadowPath.CGPath;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@ -12,7 +12,9 @@ typedef NS_OPTIONS(NSUInteger, OWSDirectionalRectCorner) {
|
||||
OWSDirectionalRectCornerTopTrailing = 1 << 1,
|
||||
OWSDirectionalRectCornerBottomLeading = 1 << 2,
|
||||
OWSDirectionalRectCornerBottomTrailing = 1 << 3,
|
||||
OWSDirectionalRectCornerAllCorners = ~0UL
|
||||
|
||||
OWSDirectionalRectCornerAllCorners = OWSDirectionalRectCornerTopLeading | OWSDirectionalRectCornerTopTrailing
|
||||
| OWSDirectionalRectCornerBottomLeading | OWSDirectionalRectCornerBottomTrailing
|
||||
};
|
||||
|
||||
@class OWSBubbleView;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBubbleView.h"
|
||||
@ -73,8 +73,8 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
[self.layer addSublayer:self.gradientLayer];
|
||||
self.gradientLayer.hidden = YES;
|
||||
|
||||
self.layer.masksToBounds = YES;
|
||||
self.maskLayer = [CAShapeLayer new];
|
||||
self.layer.mask = self.maskLayer;
|
||||
|
||||
_partnerViews = [NSMutableArray new];
|
||||
|
||||
@ -211,6 +211,24 @@ const CGFloat kOWSMessageCellCornerRadius_Small = 4;
|
||||
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
|
||||
BOOL noSharpCorners = (self.sharpCorners == 0);
|
||||
BOOL allSharpCorners
|
||||
= (self.sharpCorners & OWSDirectionalRectCornerAllCorners) == OWSDirectionalRectCornerAllCorners;
|
||||
|
||||
if (noSharpCorners || allSharpCorners) {
|
||||
// Although we have a bubble bezier path, it's just a simple rounded rect
|
||||
// It's more performant to use the CA corner mask property than a layer mask
|
||||
CGFloat sharpRadius = kOWSMessageCellCornerRadius_Small;
|
||||
CGFloat wideRadius = kOWSMessageCellCornerRadius_Large;
|
||||
|
||||
self.layer.cornerRadius = (self.sharpCorners == 0) ? wideRadius : sharpRadius;
|
||||
self.layer.mask = nil;
|
||||
} else {
|
||||
self.layer.cornerRadius = 0;
|
||||
self.layer.mask = self.maskLayer;
|
||||
}
|
||||
|
||||
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -117,6 +117,7 @@ open class TooltipView: UIView {
|
||||
bezierPath.addLine(to: tailPoint)
|
||||
|
||||
shapeLayer.path = bezierPath.cgPath
|
||||
shapeLayer.shadowPath = bezierPath.cgPath
|
||||
shapeLayer.frame = view.bounds
|
||||
}
|
||||
|
||||
|
||||
@ -621,6 +621,15 @@ public extension UIBezierPath {
|
||||
}
|
||||
}
|
||||
|
||||
public extension CACornerMask {
|
||||
static let top: CACornerMask = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
static let bottom: CACornerMask = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
||||
static let left: CACornerMask = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
|
||||
static let right: CACornerMask = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
|
||||
|
||||
static let all: CACornerMask = top.union(bottom)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@objc
|
||||
|
||||
Loading…
Reference in New Issue
Block a user