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