import Foundation import UIKit import Display import ComponentFlow import TelegramPresentationData final class WarpView: UIView { private final class WarpPartView: UIView { let cloneView: PortalView init?(contentView: PortalSourceView) { guard let cloneView = PortalView(matchPosition: false) else { return nil } self.cloneView = cloneView super.init(frame: CGRect()) self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) self.clipsToBounds = true self.addSubview(cloneView.view) contentView.addPortal(view: cloneView) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(containerSize: CGSize, rect: CGRect, transition: Transition) { transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height))) } } let contentView: PortalSourceView private let clippingView: UIView private var warpViews: [WarpPartView] = [] private let warpMaskContainer: UIView private let warpMaskGradientLayer: SimpleGradientLayer override init(frame: CGRect) { self.contentView = PortalSourceView() self.clippingView = UIView() self.warpMaskContainer = UIView() self.warpMaskGradientLayer = SimpleGradientLayer() self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer super.init(frame: frame) self.clippingView.addSubview(self.contentView) self.clippingView.clipsToBounds = true self.addSubview(self.clippingView) self.addSubview(self.warpMaskContainer) for _ in 0 ..< 8 { if let warpView = WarpPartView(contentView: self.contentView) { self.warpViews.append(warpView) self.warpMaskContainer.addSubview(warpView) } } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) { transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) let allItemsHeight = warpHeight * 0.5 for i in 0 ..< self.warpViews.count { let itemHeight = warpHeight / CGFloat(self.warpViews.count) let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count) let _ = itemHeight let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count) let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5 let endPoint = CGPoint(x: cos(alpha), y: sin(alpha)) let prevAngle = alpha + da let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle)) var angle: CGFloat angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x) let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y) let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5 let _ = itemLength var transform: CATransform3D transform = CATransform3DIdentity transform.m34 = 1.0 / 240.0 transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight) transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0) let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength)) transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0)) transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength))) transition.setTransform(view: self.warpViews[i], transform: transform) self.warpViews[i].update(containerSize: size, rect: rect, transition: transition) } let clippingTopInset: CGFloat = topInset let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0)) transition.setPosition(view: self.clippingView, position: frame.center) transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size)) self.clippingView.clipsToBounds = true transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight))) var locations: [NSNumber] = [] var colors: [CGColor] = [] let numStops = 6 for i in 0 ..< numStops { let step = CGFloat(i) / CGFloat(numStops - 1) locations.append(step as NSNumber) colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor) } let gradientHeight: CGFloat = 6.0 self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight) self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) self.warpMaskGradientLayer.locations = locations self.warpMaskGradientLayer.colors = colors self.warpMaskGradientLayer.type = .axial transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight))) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return self.contentView.hitTest(point, with: event) } }