mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-04-02 11:52:53 +00:00
207 lines
8.6 KiB
Swift
207 lines
8.6 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import ComponentFlow
|
|
|
|
@inline(__always)
|
|
private func getMethod<T>(object: NSObject, selector: String) -> T? {
|
|
guard let method = object.method(for: NSSelectorFromString(selector)) else {
|
|
return nil
|
|
}
|
|
return unsafeBitCast(method, to: T.self)
|
|
}
|
|
|
|
private var cachedClasses: [String: NSObject] = [:]
|
|
private func getAndCacheClass(name: String) -> NSObject? {
|
|
if let value = cachedClasses[name] {
|
|
return value
|
|
} else {
|
|
if let value = NSClassFromString(name as String) as AnyObject as? NSObject {
|
|
cachedClasses[name] = value
|
|
return value
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private var cachedAllocMethods: [String: (@convention(c) (AnyObject, Selector) -> NSObject?, Selector)] = [:]
|
|
private func invokeAllocMethod(className: String) -> NSObject? {
|
|
guard let classObject = getAndCacheClass(name: className) else {
|
|
return nil
|
|
}
|
|
if let cachedMethod = cachedAllocMethods[className] {
|
|
return cachedMethod.0(classObject, cachedMethod.1)
|
|
} else {
|
|
let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: classObject, selector: "alloc")
|
|
if let method {
|
|
let selector = NSSelectorFromString("alloc")
|
|
cachedAllocMethods[className] = (method, selector)
|
|
return method(classObject, selector)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private var cachedInitMethods: [String: (@convention(c) (AnyObject, Selector) -> NSObject?, Selector)] = [:]
|
|
private func invokeInitMethod(className: String, object: NSObject) -> NSObject? {
|
|
if let cachedInitMethod = cachedInitMethods[className] {
|
|
return cachedInitMethod.0(object, cachedInitMethod.1)
|
|
} else {
|
|
let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: object, selector: "init")
|
|
if let method {
|
|
let selector = NSSelectorFromString("init")
|
|
cachedInitMethods[className] = (method, selector)
|
|
return method(object, selector)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private func createObject(className: String) -> NSObject? {
|
|
if let object = invokeAllocMethod(className: className) {
|
|
return invokeInitMethod(className: className, object: object)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func setFilterName(object: NSObject, name: String) {
|
|
object.perform(NSSelectorFromString("setName:"), with: name)
|
|
}
|
|
|
|
private final class EmptyLayerDelegate: NSObject, CALayerDelegate {
|
|
func action(for layer: CALayer, forKey event: String) -> CAAction? {
|
|
return nullAction
|
|
}
|
|
}
|
|
|
|
public final class LensTransitionContainer: UIView {
|
|
public enum State {
|
|
case animatedOut
|
|
case animatedIn
|
|
}
|
|
|
|
private let emptyLayerDelegate = EmptyLayerDelegate()
|
|
|
|
private let sdfElementLayer: CALayer?
|
|
private let sdfLayer: CALayer?
|
|
private let displacementEffect: NSObject?
|
|
|
|
private(set) var state: State = .animatedOut
|
|
|
|
override public init(frame: CGRect) {
|
|
self.sdfElementLayer = createObject(className: ("CAS" as NSString).appending("DFElementLayer") as String) as? CALayer
|
|
self.sdfLayer = createObject(className: ("CAS" as NSString).appending("DFLayer")) as? CALayer
|
|
self.displacementEffect = createObject(className: ("CAS" as NSString).appending("DFGlassDisplacementEffect"))
|
|
|
|
super.init(frame: frame)
|
|
|
|
let curvature: CGFloat = 1.0
|
|
|
|
if let displacementEffect = self.displacementEffect {
|
|
displacementEffect.setValue(curvature, forKey: "curvature")
|
|
displacementEffect.setValue(0.0 as NSNumber, forKey: "angle")
|
|
}
|
|
|
|
if let sdfLayer = self.sdfLayer, let displacementEffect = self.displacementEffect {
|
|
sdfLayer.name = "sdfLayer"
|
|
sdfLayer.setValue(3.0, forKey: "scale")
|
|
sdfLayer.setValue(displacementEffect, forKey: "effect")
|
|
sdfLayer.delegate = self.emptyLayerDelegate
|
|
}
|
|
|
|
if let sdfLayer = self.sdfLayer, let sdfElementLayer = self.sdfElementLayer {
|
|
sdfElementLayer.setValue(0.0 as NSNumber, forKey: "gradientOvalization")
|
|
sdfElementLayer.isOpaque = true
|
|
sdfElementLayer.allowsEdgeAntialiasing = true
|
|
let sdfLayerDelegate = unsafeBitCast(sdfLayer, to: CALayerDelegate.self)
|
|
sdfElementLayer.delegate = sdfLayerDelegate
|
|
sdfElementLayer.setValue(UIScreenScale, forKey: "scale")
|
|
sdfLayer.addSublayer(sdfElementLayer)
|
|
}
|
|
}
|
|
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
private func setIsFilterActive(isFilterActive: Bool) {
|
|
if isFilterActive {
|
|
if self.layer.filters == nil {
|
|
if let sdfLayer = self.sdfLayer {
|
|
self.layer.insertSublayer(sdfLayer, at: 0)
|
|
}
|
|
if let displacementFilter = CALayer.displacementMap(), let blurFilter = CALayer.blur() {
|
|
setFilterName(object: blurFilter, name: "gaussianBlur")
|
|
blurFilter.setValue(true, forKey: "inputNormalizeEdgesTransparent")
|
|
|
|
setFilterName(object: displacementFilter, name: "displacementMap")
|
|
displacementFilter.setValue("sdfLayer", forKey: "inputSourceSublayerName")
|
|
|
|
self.layer.filters = [
|
|
blurFilter,
|
|
displacementFilter
|
|
]
|
|
}
|
|
}
|
|
} else if self.layer.filters != nil {
|
|
self.layer.filters = nil
|
|
if let sdfLayer = self.sdfLayer {
|
|
sdfLayer.removeFromSuperlayer()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func update(size: CGSize, cornerRadius: CGFloat, state: State, transition: ComponentTransition) {
|
|
let minHeight: CGFloat = 60.0
|
|
let fullHeight: CGFloat = 30.0
|
|
let fullAmount: CGFloat = -40.0
|
|
let fullBlur: CGFloat = 2.0
|
|
|
|
let previousState = self.state
|
|
self.state = state
|
|
if let sdfLayer = self.sdfLayer, let sdfElementLayer = self.sdfElementLayer, sdfLayer.bounds.size != size || previousState != state {
|
|
let previousSize = sdfLayer.bounds.size
|
|
|
|
self.setIsFilterActive(isFilterActive: true)
|
|
|
|
transition.setFrame(layer: sdfLayer, frame: CGRect(origin: CGPoint(), size: size), completion: { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.setIsFilterActive(isFilterActive: false)
|
|
})
|
|
transition.setCornerRadius(layer: sdfLayer, cornerRadius: cornerRadius)
|
|
transition.setFrame(layer: sdfElementLayer, frame: CGRect(origin: CGPoint(), size: size))
|
|
|
|
transition.setCornerRadius(layer: sdfElementLayer, cornerRadius: cornerRadius)
|
|
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius)
|
|
|
|
let height: CGFloat = state == .animatedIn ? minHeight : fullHeight
|
|
let amount: CGFloat = state == .animatedIn ? 0.0 : fullAmount
|
|
let blur: CGFloat = state == .animatedIn ? 0.0 : fullBlur
|
|
self.layer.setValue(height as NSNumber, forKeyPath: "sublayers.sdfLayer.effect.height")
|
|
self.layer.setValue(amount as NSNumber, forKeyPath: "filters.displacementMap.inputAmount")
|
|
self.layer.setValue(blur as NSNumber, forKeyPath: "filters.gaussianBlur.inputRadius")
|
|
|
|
if !transition.animation.isImmediate && previousSize != .zero {
|
|
if previousState != state {
|
|
let previousHeight: CGFloat = previousState == .animatedIn ? minHeight : fullHeight
|
|
let previousAmount: CGFloat = previousState == .animatedIn ? 0.0 : fullAmount
|
|
let previousBlur: CGFloat = previousState == .animatedIn ? 0.0 : fullBlur
|
|
|
|
let glassTransition: ComponentTransition = transition
|
|
|
|
glassTransition.animateScalarFloat(layer: self.layer, keyPath: "sublayers.sdfLayer.effect.height", from: previousHeight, to: height, delay: 0.0)
|
|
glassTransition.animateScalarFloat(layer: self.layer, keyPath: "filters.displacementMap.inputAmount", from: previousAmount, to: amount, delay: 0.0)
|
|
|
|
transition.animateScalarFloat(layer: self.layer, keyPath: "filters.gaussianBlur.inputRadius", from: previousBlur, to: blur)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|