Swiftgram/submodules/Display/Source/Navigation/NavigationModalFrame.swift
2022-06-28 22:54:05 +02:00

149 lines
7.7 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
private enum CornerType {
case topLeft
case topRight
case bottomLeft
case bottomRight
}
private func generateCornerImage(radius: CGFloat, type: CornerType) -> UIImage? {
return generateImage(CGSize(width: radius, height: radius), rotatedContext: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
UIGraphicsPushContext(context)
let origin: CGPoint
switch type {
case .topLeft:
origin = CGPoint()
case .topRight:
origin = CGPoint(x: -radius, y: 0.0)
case .bottomLeft:
origin = CGPoint(x: 0.0, y: -radius)
case .bottomRight:
origin = CGPoint(x: -radius, y: -radius)
}
UIBezierPath(roundedRect: CGRect(origin: origin, size: CGSize(width: radius * 2.0, height: radius * 2.0)), cornerRadius: radius).fill()
UIGraphicsPopContext()
})
}
public final class NavigationModalFrame: ASDisplayNode {
private let topShade: ASDisplayNode
private let leftShade: ASDisplayNode
private let rightShade: ASDisplayNode
private let bottomShade: ASDisplayNode
private let topLeftCorner: ASImageNode
private let topRightCorner: ASImageNode
private let bottomLeftCorner: ASImageNode
private let bottomRightCorner: ASImageNode
private var currentMaxCornerRadius: CGFloat?
private var progress: CGFloat = 1.0
private var additionalProgress: CGFloat = 0.0
private var validLayout: ContainerViewLayout?
override public init() {
self.topShade = ASDisplayNode()
self.topShade.backgroundColor = .black
self.leftShade = ASDisplayNode()
self.leftShade.backgroundColor = .black
self.rightShade = ASDisplayNode()
self.rightShade.backgroundColor = .black
self.bottomShade = ASDisplayNode()
self.bottomShade.backgroundColor = .black
self.topLeftCorner = ASImageNode()
self.topLeftCorner.displaysAsynchronously = false
self.topRightCorner = ASImageNode()
self.topRightCorner.displaysAsynchronously = false
self.bottomLeftCorner = ASImageNode()
self.bottomLeftCorner.displaysAsynchronously = false
self.bottomRightCorner = ASImageNode()
self.bottomRightCorner.displaysAsynchronously = false
super.init()
self.addSubnode(self.topShade)
self.addSubnode(self.leftShade)
self.addSubnode(self.rightShade)
self.addSubnode(self.bottomShade)
self.addSubnode(self.topLeftCorner)
self.addSubnode(self.topRightCorner)
self.addSubnode(self.bottomLeftCorner)
self.addSubnode(self.bottomRightCorner)
}
public func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
self.updateShades(layout: layout, progress: 1.0 - self.progress, additionalProgress: self.additionalProgress, transition: transition, completion: {})
}
public func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, additionalProgress: CGFloat, completion: @escaping () -> Void) {
self.progress = progress
self.additionalProgress = additionalProgress
if let layout = self.validLayout {
self.updateShades(layout: layout, progress: 1.0 - progress, additionalProgress: additionalProgress, transition: transition, completion: completion)
} else {
completion()
}
}
private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
let sideInset: CGFloat = 16.0
var topInset: CGFloat = 0.0
if let statusBarHeight = layout.statusBarHeight {
topInset += statusBarHeight
}
let additionalTopInset: CGFloat = 10.0
let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width
let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset
let cornerRadius: CGFloat = 9.0
let initialCornerRadius: CGFloat
if !layout.safeInsets.top.isZero {
initialCornerRadius = layout.deviceMetrics.screenCornerRadius
} else {
initialCornerRadius = 0.0
}
if self.currentMaxCornerRadius != cornerRadius {
self.topLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .topLeft)
self.topRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .topRight)
self.bottomLeftCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .bottomLeft)
self.bottomRightCorner.image = generateCornerImage(radius: max(initialCornerRadius, cornerRadius), type: .bottomRight)
}
let cornerSize = progress * cornerRadius + (1.0 - progress) * initialCornerRadius
let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
let cornerTopOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
let cornerBottomOffset: CGFloat = progress * bottomInset
transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true)
transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true)
transition.updateFrame(node: self.bottomLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true)
transition.updateFrame(node: self.bottomRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true)
let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset
let bottomShadeOffset: CGFloat = progress * bottomInset
let leftShadeOffset: CGFloat = progress * sideInset + additionalProgress * sideInset
let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset
let rightShadeOffset: CGFloat = layout.size.width - rightShadeWidth
transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset)), beginWithCurrentState: true)
transition.updateFrame(node: self.bottomShade, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomShadeOffset), size: CGSize(width: layout.size.width, height: bottomShadeOffset)))
transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height)), beginWithCurrentState: true)
transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), beginWithCurrentState: true, completion: { _ in
completion()
})
}
}