Files
2026-02-19 21:53:26 +04:00

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()
}))
}