mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-03-30 10:45:01 +00:00
147 lines
5.5 KiB
Swift
147 lines
5.5 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import ShimmerEffect
|
|
|
|
public final class ShimmeringLinkNode: ASDisplayNode {
|
|
private let shimmerEffectNode: ShimmerEffectForegroundNode
|
|
private let borderShimmerEffectNode: ShimmerEffectForegroundNode
|
|
|
|
private let maskNode: ASImageNode
|
|
private let borderMaskNode: ASImageNode
|
|
|
|
private(set) var rects: [CGRect] = []
|
|
public var color: UIColor {
|
|
didSet {
|
|
self.backgroundColor = color
|
|
}
|
|
}
|
|
private let isSkeleton: Bool
|
|
|
|
public var innerRadius: CGFloat = 4.0
|
|
public var outerRadius: CGFloat = 4.0
|
|
public var inset: CGFloat = 2.0
|
|
|
|
public init(color: UIColor, isSkeleton: Bool = false) {
|
|
self.color = color
|
|
self.isSkeleton = isSkeleton
|
|
|
|
self.shimmerEffectNode = ShimmerEffectForegroundNode()
|
|
self.borderShimmerEffectNode = ShimmerEffectForegroundNode()
|
|
|
|
self.maskNode = ASImageNode()
|
|
self.maskNode.isLayerBacked = true
|
|
self.maskNode.displaysAsynchronously = false
|
|
self.maskNode.displayWithoutProcessing = true
|
|
|
|
self.borderMaskNode = ASImageNode()
|
|
self.borderMaskNode.displaysAsynchronously = false
|
|
self.borderMaskNode.displayWithoutProcessing = true
|
|
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
|
|
self.shimmerEffectNode.backgroundColor = color.withAlphaComponent(color.alpha * 0.6)
|
|
|
|
self.addSubnode(self.shimmerEffectNode)
|
|
//self.addSubnode(self.borderShimmerEffectNode)
|
|
}
|
|
|
|
override public func didLoad() {
|
|
super.didLoad()
|
|
|
|
if self.isSkeleton {
|
|
self.shimmerEffectNode.removeFromSupernode()
|
|
self.addSubnode(self.maskNode)
|
|
} else {
|
|
self.shimmerEffectNode.layer.mask = self.maskNode.layer
|
|
self.borderShimmerEffectNode.layer.mask = self.borderMaskNode.layer
|
|
}
|
|
}
|
|
|
|
public func updateRects(_ rects: [CGRect], color: UIColor? = nil) {
|
|
var updated = false
|
|
if self.rects != rects {
|
|
updated = true
|
|
self.rects = rects
|
|
}
|
|
|
|
if let color, !color.isEqual(self.color) {
|
|
updated = true
|
|
self.color = color
|
|
}
|
|
|
|
if updated {
|
|
self.updateMasks()
|
|
}
|
|
}
|
|
|
|
private func updateMasks() {
|
|
if self.isSkeleton {
|
|
let (offset, image) = generateSkeletonRectsImage(color: self.color, rects: self.rects)
|
|
if let image = image {
|
|
self.maskNode.frame = CGRect(origin: offset, size: image.size)
|
|
self.maskNode.image = image
|
|
}
|
|
} else {
|
|
let (offset, image) = generateRectsImage(color: .white, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius, useModernPathCalculation: true)
|
|
|
|
let (borderOffset, borderImage) = generateRectsImage(color: .white, rects: self.rects.map { $0.insetBy(dx: 0.0, dy: 0.0) }, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius, stroke: true, useModernPathCalculation: true)
|
|
|
|
if let image = image {
|
|
self.maskNode.frame = CGRect(origin: offset, size: image.size)
|
|
self.maskNode.image = image
|
|
}
|
|
|
|
if let borderImage = borderImage {
|
|
self.borderMaskNode.frame = CGRect(origin: borderOffset, size: borderImage.size)
|
|
self.borderMaskNode.image = borderImage
|
|
}
|
|
}
|
|
}
|
|
|
|
public func updateLayout(_ size: CGSize) {
|
|
self.shimmerEffectNode.frame = CGRect(origin: .zero, size: size)
|
|
self.borderShimmerEffectNode.frame = CGRect(origin: .zero, size: size)
|
|
|
|
self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size)
|
|
self.borderShimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: size), within: size)
|
|
|
|
self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withMultipliedAlpha(1.75), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
|
|
self.borderShimmerEffectNode.update(backgroundColor: .clear, foregroundColor: self.color.withMultipliedAlpha(2.0), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
|
|
}
|
|
}
|
|
|
|
private func generateSkeletonRectsImage(color: UIColor, rects: [CGRect]) -> (CGPoint, UIImage?) {
|
|
if rects.isEmpty {
|
|
return (CGPoint(), nil)
|
|
}
|
|
|
|
var topLeft = rects[0].origin
|
|
var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY)
|
|
for i in 1 ..< rects.count {
|
|
topLeft.x = min(topLeft.x, rects[i].origin.x)
|
|
topLeft.y = min(topLeft.y, rects[i].origin.y)
|
|
bottomRight.x = max(bottomRight.x, rects[i].maxX)
|
|
bottomRight.y = max(bottomRight.y, rects[i].maxY)
|
|
}
|
|
|
|
let drawingInset: CGFloat = 0.0
|
|
|
|
topLeft.x -= drawingInset
|
|
topLeft.y -= drawingInset
|
|
bottomRight.x += drawingInset * 2.0
|
|
bottomRight.y += drawingInset * 2.0
|
|
|
|
return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: .zero, size: size))
|
|
context.setFillColor(color.cgColor)
|
|
for rect in rects {
|
|
context.addPath(UIBezierPath(roundedRect: rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y), cornerRadius: rect.height * 0.5).cgPath)
|
|
}
|
|
context.fillPath()
|
|
}))
|
|
}
|