Files
Swiftgram/submodules/Display/Source/NavigationBackgroundView.swift
2025-11-26 23:05:30 +08:00

333 lines
14 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
private var sharedIsReduceTransparencyEnabled = UIAccessibility.isReduceTransparencyEnabled
public final class NavigationBackgroundNode: ASDisplayNode {
private var _color: UIColor
public var color: UIColor {
return self._color
}
private var enableBlur: Bool
private var enableSaturation: Bool
private var customBlurRadius: CGFloat?
public var effectView: UIVisualEffectView?
private let backgroundNode: ASDisplayNode
public var backgroundView: UIView {
return self.backgroundNode.view
}
private var validLayout: (CGSize, CGFloat)?
public var backgroundCornerRadius: CGFloat {
if let (_, cornerRadius) = self.validLayout {
return cornerRadius
} else {
return 0.0
}
}
public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true, customBlurRadius: CGFloat? = nil) {
self._color = .clear
self.enableBlur = enableBlur
self.enableSaturation = enableSaturation
self.customBlurRadius = customBlurRadius
self.backgroundNode = ASDisplayNode()
super.init()
self.addSubnode(self.backgroundNode)
self.updateColor(color: color, transition: .immediate)
}
public override func didLoad() {
super.didLoad()
if self.scheduledUpdate {
self.scheduledUpdate = false
self.updateBackgroundBlur(forceKeepBlur: false)
}
}
private var scheduledUpdate = false
private func updateBackgroundBlur(forceKeepBlur: Bool) {
guard self.isNodeLoaded else {
self.scheduledUpdate = true
return
}
if self.enableBlur && !sharedIsReduceTransparencyEnabled && ((self._color.alpha > .ulpOfOne && self._color.alpha < 0.95) || forceKeepBlur) {
if self.effectView == nil {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
for subview in effectView.subviews {
if subview.description.contains("VisualEffectSubview") {
subview.isHidden = true
}
}
if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters {
sublayer.backgroundColor = nil
sublayer.isOpaque = false
var allowedKeys: [String] = [
"gaussianBlur"
]
if self.enableSaturation {
allowedKeys.append("colorSaturate")
}
sublayer.filters = filters.filter { filter in
guard let filter = filter as? NSObject else {
return true
}
let filterName = String(describing: filter)
if !allowedKeys.contains(filterName) {
return false
}
if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" {
filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius")
}
return true
}
}
if let (size, cornerRadius) = self.validLayout {
effectView.frame = CGRect(origin: CGPoint(), size: size)
ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
effectView.clipsToBounds = !cornerRadius.isZero
}
self.effectView = effectView
self.view.insertSubview(effectView, at: 0)
}
} else if let effectView = self.effectView {
self.effectView = nil
effectView.removeFromSuperview()
}
}
public func updateColor(color: UIColor, enableBlur: Bool? = nil, enableSaturation: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) {
let effectiveEnableBlur = enableBlur ?? self.enableBlur
let effectiveEnableSaturation = enableSaturation ?? self.enableSaturation
if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur && self.enableSaturation == effectiveEnableSaturation {
return
}
self._color = color
self.enableBlur = effectiveEnableBlur
self.enableSaturation = effectiveEnableSaturation
if sharedIsReduceTransparencyEnabled {
transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0))
} else {
transition.updateBackgroundColor(node: self.backgroundNode, color: self._color)
}
self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur)
}
public func update(size: CGSize, cornerRadius: CGFloat = 0.0, transition: ContainedViewLayoutTransition, beginWithCurrentState: Bool = true) {
self.validLayout = (size, cornerRadius)
let contentFrame = CGRect(origin: CGPoint(), size: size)
transition.updateFrame(node: self.backgroundNode, frame: contentFrame, beginWithCurrentState: true)
if let effectView = self.effectView, effectView.frame != contentFrame {
transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true)
if let sublayers = effectView.layer.sublayers {
for sublayer in sublayers {
transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true)
}
}
}
transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: cornerRadius)
if let effectView = self.effectView {
transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
effectView.clipsToBounds = !cornerRadius.isZero
}
}
public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) {
self.validLayout = (size, cornerRadius)
let contentFrame = CGRect(origin: CGPoint(), size: size)
animator.updateFrame(layer: self.backgroundNode.layer, frame: contentFrame, completion: nil)
if let effectView = self.effectView, effectView.frame != contentFrame {
animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil)
if let sublayers = effectView.layer.sublayers {
for sublayer in sublayers {
animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil)
}
}
}
animator.updateCornerRadius(layer: self.backgroundNode.layer, cornerRadius: cornerRadius, completion: nil)
if let effectView = self.effectView {
animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil)
effectView.clipsToBounds = !cornerRadius.isZero
}
}
}
open class BlurredBackgroundView: UIView {
private var _color: UIColor?
private var enableBlur: Bool
private var customBlurRadius: CGFloat?
public private(set) var effectView: UIVisualEffectView?
private let backgroundView: UIView
private var validLayout: (CGSize, CGFloat)?
public var backgroundCornerRadius: CGFloat {
if let (_, cornerRadius) = self.validLayout {
return cornerRadius
} else {
return 0.0
}
}
public init(color: UIColor?, enableBlur: Bool = true, customBlurRadius: CGFloat? = nil) {
self._color = nil
self.enableBlur = enableBlur
self.customBlurRadius = customBlurRadius
self.backgroundView = UIView()
super.init(frame: CGRect())
self.addSubview(self.backgroundView)
if let color = color {
self.updateColor(color: color, transition: .immediate)
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateBackgroundBlur(forceKeepBlur: Bool) {
if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) {
if self.effectView == nil {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
for subview in effectView.subviews {
if subview.description.contains("VisualEffectSubview") {
subview.isHidden = true
}
}
if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters {
sublayer.backgroundColor = nil
sublayer.isOpaque = false
//sublayer.setValue(true as NSNumber, forKey: "allowsInPlaceFiltering")
let allowedKeys: [String] = [
"colorSaturate",
"gaussianBlur"
]
sublayer.filters = filters.filter { filter in
guard let filter = filter as? NSObject else {
return true
}
let filterName = String(describing: filter)
if !allowedKeys.contains(filterName) {
return false
}
if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" {
filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius")
}
return true
}
}
if let (size, cornerRadius) = self.validLayout {
effectView.frame = CGRect(origin: CGPoint(), size: size)
ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
effectView.clipsToBounds = !cornerRadius.isZero
}
self.effectView = effectView
self.insertSubview(effectView, at: 0)
}
} else if let effectView = self.effectView {
self.effectView = nil
effectView.removeFromSuperview()
}
}
public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) {
let effectiveEnableBlur = enableBlur ?? self.enableBlur
if self._color == color && self.enableBlur == effectiveEnableBlur {
return
}
self._color = color
self.enableBlur = effectiveEnableBlur
if sharedIsReduceTransparencyEnabled {
transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color.withAlphaComponent(1.0))
} else {
transition.updateBackgroundColor(layer: self.backgroundView.layer, color: color)
}
self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur)
}
public func update(size: CGSize, cornerRadius: CGFloat = 0.0, maskedCorners: CACornerMask = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner], transition: ContainedViewLayoutTransition) {
self.validLayout = (size, cornerRadius)
let contentFrame = CGRect(origin: CGPoint(), size: size)
transition.updateFrame(view: self.backgroundView, frame: contentFrame, beginWithCurrentState: true)
if let effectView = self.effectView, effectView.frame != contentFrame {
transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true)
if let sublayers = effectView.layer.sublayers {
for sublayer in sublayers {
transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true)
}
}
}
if #available(iOS 11.0, *) {
self.backgroundView.layer.maskedCorners = maskedCorners
}
transition.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius)
if let effectView = self.effectView {
transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
effectView.clipsToBounds = !cornerRadius.isZero
if #available(iOS 11.0, *) {
effectView.layer.maskedCorners = maskedCorners
}
}
}
public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) {
self.validLayout = (size, cornerRadius)
let contentFrame = CGRect(origin: CGPoint(), size: size)
animator.updateFrame(layer: self.backgroundView.layer, frame: contentFrame, completion: nil)
if let effectView = self.effectView, effectView.frame != contentFrame {
animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil)
if let sublayers = effectView.layer.sublayers {
for sublayer in sublayers {
animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil)
}
}
}
animator.updateCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius, completion: nil)
if let effectView = self.effectView {
animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil)
effectView.clipsToBounds = !cornerRadius.isZero
}
}
}