2025-10-16 05:30:06 +04:00

212 lines
7.6 KiB
Swift

import Foundation
import UIKit
import Display
import ComponentFlow
public final class EdgeEffectView: UIView {
public enum Edge {
case top
case bottom
}
private let contentView: UIView
private let contentMaskView: UIImageView
private var blurView: VariableBlurView?
public override init(frame: CGRect) {
self.contentView = UIView()
self.contentMaskView = UIImageView()
self.contentView.mask = self.contentMaskView
super.init(frame: frame)
self.addSubview(self.contentView)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) {
transition.setBackgroundColor(view: self.contentView, color: content)
switch edge {
case .top:
self.contentMaskView.transform = CGAffineTransformMakeScale(1.0, -1.0)
case .bottom:
self.contentMaskView.transform = .identity
}
let bounds = CGRect(origin: CGPoint(), size: rect.size)
transition.setFrame(view: self.contentView, frame: bounds)
transition.setFrame(view: self.contentMaskView, frame: bounds)
if self.contentMaskView.image?.size.height != edgeSize {
let baseGradientAlpha: CGFloat = alpha
let numSteps = 8
let firstStep = 1
let firstLocation = 0.0
let colors: [UIColor] = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value)
}
}
let locations: [CGFloat] = (0 ..< numSteps).map { i in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
if edgeSize > 0.0 {
self.contentMaskView.image = generateGradientImage(
size: CGSize(width: 8.0, height: edgeSize),
colors: colors,
locations: locations
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edgeSize))
} else {
self.contentMaskView.image = nil
}
}
if blur {
let gradientMaskLayer = SimpleGradientLayer()
let baseGradientAlpha: CGFloat = 1.0
let numSteps = 8
let firstStep = 1
let firstLocation = 0.8
gradientMaskLayer.colors = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0).cgColor
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor
}
}
gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in
if i < firstStep {
return 0.0 as NSNumber
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step) as NSNumber
}
}
let blurView: VariableBlurView
if let current = self.blurView {
blurView = current
} else {
blurView = VariableBlurView(gradientMask: self.contentMaskView.image ?? UIImage(), maxBlurRadius: 8.0)
blurView.layer.mask = gradientMaskLayer
self.insertSubview(blurView, at: 0)
self.blurView = blurView
}
transition.setFrame(view: blurView, frame: bounds)
if let maskLayer = blurView.layer.mask {
transition.setFrame(layer: maskLayer, frame: bounds)
maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
}
blurView.transform = self.contentMaskView.transform
} else if let blurView = self.blurView {
self.blurView = nil
blurView.removeFromSuperview()
}
}
}
public final class VariableBlurView: UIVisualEffectView {
public let maxBlurRadius: CGFloat
public var gradientMask: UIImage {
didSet {
if self.gradientMask !== oldValue {
self.resetEffect()
}
}
}
public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) {
self.gradientMask = gradientMask
self.maxBlurRadius = maxBlurRadius
super.init(effect: UIBlurEffect(style: .regular))
self.resetEffect()
if self.subviews.indices.contains(1) {
let tintOverlayView = subviews[1]
tintOverlayView.alpha = 0
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
self.resetEffect()
}
}
private func resetEffect() {
let filterClassStringEncoded = "Q0FGaWx0ZXI="
let filterClassString: String = {
if
let data = Data(base64Encoded: filterClassStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6"
let filterWithTypeString: String = {
if
let data = Data(base64Encoded: filterWithTypeStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeSelector = Selector(filterWithTypeString)
guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else {
return
}
guard filterClass.responds(to: filterWithTypeSelector) else {
return
}
let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue()
guard let variableBlur = variableBlur as? NSObject else {
return
}
guard let gradientImageRef = self.gradientMask.cgImage else {
return
}
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
let backdropLayer = self.subviews.first?.layer
backdropLayer?.filters = [variableBlur]
backdropLayer?.setValue(UIScreenScale, forKey: "scale")
}
}