diff --git a/Telegram/Tests/Sources/UITests.swift b/Telegram/Tests/Sources/UITests.swift index 44f46c58f3..7b9083117e 100644 --- a/Telegram/Tests/Sources/UITests.swift +++ b/Telegram/Tests/Sources/UITests.swift @@ -14,12 +14,10 @@ class UITests: XCTestCase { // Put teardown code here. This method is called after the invocation of each test method in the class. } - func testExample() throws { - // UI tests must launch the application that they test. + func testLogin() throws { let app = XCUIApplication() + app.launchArguments.append("--ui-test") app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. + XCTAssert(app.buttons["Continue"].waitForExistence(timeout: 5.0)) } } diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index f57a9d888d..d3b4bd3af6 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -94,7 +94,6 @@ swift_library( "//submodules/TelegramUI/Components/ActionPanelComponent", "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", "//submodules/TelegramUI/Components/Stories/StoryPeerListComponent", - "//submodules/TelegramUI/Components/FullScreenEffectView", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen", "//submodules/TelegramUI/Components/Settings/ArchiveInfoScreen", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 24ef20cdf6..9dc0423319 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -46,7 +46,6 @@ import ChatListTitleView import InviteLinksUI import ChatFolderLinkPreviewScreen import StoryContainerScreen -import FullScreenEffectView import PeerInfoStoryGridScreen import ArchiveInfoScreen import BirthdayPickerScreen @@ -220,8 +219,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var currentTooltipUpdateTimer: Foundation.Timer? - private var fullScreenEffectView: RippleEffectView? - let globalControlPanelsContext: GlobalControlPanelsContext private(set) var globalControlPanelsContextState: GlobalControlPanelsContext.State? private var globalControlPanelsContextStateDisposable: Disposable? @@ -2807,11 +2804,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.clearHighlightAnimated(true) - if let fullScreenEffectView = self.fullScreenEffectView { - self.fullScreenEffectView = nil - fullScreenEffectView.removeFromSuperview() - } - self.sharedOpenStoryProgressDisposable.set(nil) if let storyPeerListView = self.chatListHeaderView()?.storyPeerListView() { @@ -3441,37 +3433,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return nil } - public func animateStoryUploadRipple() { - if let componentView = self.chatListHeaderView() { - if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) { - let localRect = transitionView.convert(transitionView.bounds, to: self.view) - self.animateRipple(centerLocation: localRect.center) - } - } - } - - public func animateRipple(centerLocation: CGPoint) { - if let fullScreenEffectView = self.fullScreenEffectView { - self.fullScreenEffectView = nil - fullScreenEffectView.removeFromSuperview() - } - - if let value = RippleEffectView(centerLocation: centerLocation, completion: { [weak self] in - guard let self else { - return - } - if let fullScreenEffectView = self.fullScreenEffectView { - self.fullScreenEffectView = nil - fullScreenEffectView.removeFromSuperview() - } - }) { - self.fullScreenEffectView = value - value.sourceView = self.view - self.view.addSubview(value) - value.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) - } - } - private(set) var storyUploadProgress: [PeerId: Float] = [:] private func updateStoryUploadProgress(_ progress: [PeerId: Float]) { self.storyUploadProgress = progress.mapValues { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 7170d356e7..77199b3430 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -320,7 +320,6 @@ swift_library( "//submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton", "//submodules/TelegramUI/Components/ChatSendButtonRadialStatusNode", "//submodules/TelegramUI/Components/LegacyInstantVideoController", - "//submodules/TelegramUI/Components/FullScreenEffectView", "//submodules/TelegramUI/Components/ShareWithPeersScreen", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen", diff --git a/submodules/TelegramUI/Components/FullScreenEffectView/BUILD b/submodules/TelegramUI/Components/FullScreenEffectView/BUILD deleted file mode 100644 index afc8189acc..0000000000 --- a/submodules/TelegramUI/Components/FullScreenEffectView/BUILD +++ /dev/null @@ -1,69 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -load( - "@build_bazel_rules_apple//apple:resources.bzl", - "apple_resource_bundle", - "apple_resource_group", -) -load("//build-system/bazel-utils:plist_fragment.bzl", - "plist_fragment", -) - -filegroup( - name = "FullScreenEffectViewMetalResources", - srcs = glob([ - "MetalResources/**/*.*", - ]), - visibility = ["//visibility:public"], -) - -plist_fragment( - name = "FullScreenEffectViewBundleInfoPlist", - extension = "plist", - template = - """ - CFBundleIdentifier - org.telegram.FullScreenEffectView - CFBundleDevelopmentRegion - en - CFBundleName - FullScreenEffectView - """ -) - -apple_resource_bundle( - name = "FullScreenEffectViewBundle", - infoplists = [ - ":FullScreenEffectViewBundleInfoPlist", - ], - resources = [ - ":FullScreenEffectViewMetalResources", - ], -) - -filegroup( - name = "FullScreenEffectViewResources", - srcs = glob([ - "Resources/**/*", - ], allow_empty=True, exclude = ["Resources/**/.*"]), - visibility = ["//visibility:public"], -) - -swift_library( - name = "FullScreenEffectView", - module_name = "FullScreenEffectView", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/ComponentFlow", - ], - data = [ - ":FullScreenEffectViewBundle", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/TelegramUI/Components/FullScreenEffectView/MetalResources/RippleEffect.metal b/submodules/TelegramUI/Components/FullScreenEffectView/MetalResources/RippleEffect.metal deleted file mode 100644 index 5f1c50fb96..0000000000 --- a/submodules/TelegramUI/Components/FullScreenEffectView/MetalResources/RippleEffect.metal +++ /dev/null @@ -1,159 +0,0 @@ -#include - -using namespace metal; - -typedef struct { - packed_float2 position; -} Vertex; - -typedef struct { - float4 position [[position]]; - float2 texCoord [[user(texture_coord)]]; - float visibilityFraction; -} RasterizerData; - -constant float2 vertices[6] = { - float2(1, -1), - float2(-1, -1), - float2(-1, 1), - float2(1, -1), - float2(-1, 1), - float2(1, 1) -}; - -float doubleStep(float value, float lowerBound, float upperBound) { - return step(lowerBound, value) * (1.0 - step(upperBound, value)); -} - -float fieldFunction(float2 center, float contentScale, float2 position, float2 dimensions, float time) { - float maxDimension = max(dimensions.x, dimensions.y); - - float currentDistance = time * maxDimension; - float waveWidth = 100.0f * contentScale; - - float d = distance(center, position); - - float stepFactor = doubleStep(d, currentDistance, currentDistance + waveWidth); - float value = abs(sin((-currentDistance + d) * M_PI_F / (waveWidth))); - - return value * stepFactor * 1.0f; -} - -float linearDecay(float parameter, float maxParameter) { - float decay = clamp(1.0 - parameter / maxParameter, 0.0, 1.0); - return decay; -} - -vertex RasterizerData rippleVertex -( - uint vid [[ vertex_id ]], - device const uint2 ¢er [[buffer(0)]], - device const uint2 &gridResolution [[buffer(1)]], - device const uint2 &resolution [[buffer(2)]], - device const float &time [[buffer(3)]], - device const float &contentScale [[buffer(4)]] -) { - uint triangleIndex = vid / 6; - uint vertexIndex = vid % 6; - float2 in = vertices[vertexIndex]; - in.x = (in.x + 1.0) * 0.5; - in.y = (in.y + 1.0) * 0.5; - - float2 dimensions = float2(resolution.x, resolution.y); - - float2 gridStep = float2(1.0 / (float)(gridResolution.x), 1.0 / (float)(gridResolution.y)); - uint2 positionInGrid = uint2(triangleIndex % gridResolution.x, triangleIndex / gridResolution.x); - - float2 position = float2( - float(positionInGrid.x) * gridStep.x + in.x * gridStep.x, - float(positionInGrid.y) * gridStep.y + in.y * gridStep.y - ); - float2 texCoord = float2(position.x, 1.0 - position.y); - - float zPosition = fieldFunction(float2(center), contentScale, float2(position.x * dimensions.x, (1.0 - position.y) * dimensions.y), dimensions, time); - zPosition *= 0.5f; - - float leftEdgeDistance = abs(position.x); - float rightEdgeDistance = abs(1.0 - position.x); - float topEdgeDistance = abs(position.y); - float bottomEdgeDistance = abs(1.0 - position.y); - float minEdgeDistance = min(leftEdgeDistance, rightEdgeDistance); - minEdgeDistance = min(minEdgeDistance, topEdgeDistance); - minEdgeDistance = min(minEdgeDistance, bottomEdgeDistance); - float edgeNorm = 0.1f; - float edgeDistance = min(minEdgeDistance / edgeNorm, 1.0); - zPosition *= edgeDistance; - - zPosition *= max(0.0, min(1.0, linearDecay(time, 0.7))); - - float3 camPosition = float3(0.0, 0.0f, 1.0f); - float3 camTarget = float3(0.0, 0.0, 0.0); - float3 forwardVector = normalize(camPosition - camTarget); - float3 rightVector = normalize(cross(float3(0.0, 1.0, 0.0), forwardVector)); - float3 upVector = normalize(cross(forwardVector, rightVector)); - - float translationX = dot(camPosition, rightVector); - float translationY = dot(camPosition, upVector); - float translationZ = dot(camPosition, forwardVector); - - float4x4 viewTransform = float4x4( - rightVector.x, upVector.x, forwardVector.x, 0.0, - rightVector.y, upVector.y, forwardVector.y, 0.0, - rightVector.z, upVector.z, forwardVector.z, 0.0, - -translationX, -translationY, -translationZ, 1.0 - ); - - float4x4 projectionTransform = float4x4( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, -1.0 / 500.0, 1.0 - ); - - float4x4 mvp = projectionTransform * viewTransform; - - float zNorm = 0.1; - - float4 transformedPosition = float4(float2(-1.0 + position.x * 2.0, -1.0 + position.y * 2.0), -zPosition * zNorm, 1.0) * mvp; - transformedPosition.x /= transformedPosition.w; - transformedPosition.y /= transformedPosition.w; - transformedPosition.z /= transformedPosition.w; - - position.x = transformedPosition.x; - position.y = transformedPosition.y; - - RasterizerData out; - out.position = vector_float4(0.0, 0.0, 0.0, 1.0); - out.position.x = transformedPosition.x; - out.position.y = transformedPosition.y; - out.position.z = transformedPosition.z + zNorm; - - out.visibilityFraction = zPosition == 0.0 ? 0.0 : 1.0; - - out.texCoord = texCoord; - - return out; -} - -fragment half4 rippleFragment( - RasterizerData in[[stage_in]], - texture2d texture[[ texture(0) ]] -) { - constexpr sampler textureSampler(min_filter::linear, mag_filter::linear, mip_filter::linear, address::clamp_to_edge); - - float2 texCoord = in.texCoord; - float4 rgb = float4(texture.sample(textureSampler, texCoord)); - - float4 out = float4(rgb.xyz, 1.0); - /*out.r = 0.0; - out.g = 0.0; - out.b = 1.0;*/ - - out.a = 1.0 - step(in.visibilityFraction, 0.5); - - out.r *= out.a; - out.g *= out.a; - out.b *= out.a; - - return half4(out); -} diff --git a/submodules/TelegramUI/Components/FullScreenEffectView/Sources/RippleEffectView.swift b/submodules/TelegramUI/Components/FullScreenEffectView/Sources/RippleEffectView.swift deleted file mode 100644 index b8f93536ac..0000000000 --- a/submodules/TelegramUI/Components/FullScreenEffectView/Sources/RippleEffectView.swift +++ /dev/null @@ -1,212 +0,0 @@ -import Foundation -import Metal -import MetalKit -import simd -import ComponentFlow - -public final class RippleEffectView: MTKView { - private let centerLocation: CGPoint - private let completion: () -> Void - - private let textureLoader: MTKTextureLoader - private let commandQueue: MTLCommandQueue - private let drawPassthroughPipelineState: MTLRenderPipelineState - private var texture: MTLTexture? - - private var viewportDimensions = CGSize(width: 1, height: 1) - - private var startTime: Double? - - private var lastUpdateTimestamp: Double? - - public weak var sourceView: UIView? { - didSet { - self.updateImageFromSourceView() - } - } - - public init?(centerLocation: CGPoint, completion: @escaping () -> Void) { - self.centerLocation = centerLocation - self.completion = completion - - let mainBundle = Bundle(for: RippleEffectView.self) - - guard let path = mainBundle.path(forResource: "FullScreenEffectViewBundle", ofType: "bundle") else { - return nil - } - guard let bundle = Bundle(path: path) else { - return nil - } - - guard let device = MTLCreateSystemDefaultDevice() else { - return nil - } - - guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else { - return nil - } - - guard let commandQueue = device.makeCommandQueue() else { - return nil - } - self.commandQueue = commandQueue - - guard let loadedVertexProgram = defaultLibrary.makeFunction(name: "rippleVertex") else { - return nil - } - - guard let loadedFragmentProgram = defaultLibrary.makeFunction(name: "rippleFragment") else { - return nil - } - - self.textureLoader = MTKTextureLoader(device: device) - - let pipelineStateDescriptor = MTLRenderPipelineDescriptor() - pipelineStateDescriptor.vertexFunction = loadedVertexProgram - pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram - pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true - pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha - - self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor) - - super.init(frame: CGRect(), device: device) - - self.isOpaque = false - self.backgroundColor = nil - - self.framebufferOnly = true - - self.isPaused = false - - self.isUserInteractionEnabled = false - } - - public func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { - self.viewportDimensions = size - } - - required public init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - } - - override public func draw(_ rect: CGRect) { - self.redraw(drawable: self.currentDrawable!) - } - - private func updateImageFromSourceView() { - guard let sourceView = self.sourceView else { - return - } - - let unscaledSize = sourceView.bounds.size - - UIGraphicsBeginImageContextWithOptions(sourceView.bounds.size, true, 0.0) - let context = UIGraphicsGetCurrentContext()! - UIGraphicsPushContext(context) - - var unhideSelf = false - if self.isDescendant(of: sourceView) { - self.isHidden = true - unhideSelf = true - } - - sourceView.drawHierarchy(in: CGRect(origin: CGPoint(), size: unscaledSize), afterScreenUpdates: false) - - if unhideSelf { - self.isHidden = false - } - - UIGraphicsPopContext() - let image = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - - if let image { - self.updateImage(image: image) - } - - self.lastUpdateTimestamp = CACurrentMediaTime() - } - - private func updateImage(image: UIImage) { - guard let cgImage = image.cgImage else { - return - } - self.texture = try? self.textureLoader.newTexture(cgImage: cgImage) - } - - private func redraw(drawable: MTLDrawable) { - /*if let lastUpdateTimestamp = self.lastUpdateTimestamp { - if lastUpdateTimestamp + 1.0 < CACurrentMediaTime() { - self.updateImageFromSourceView() - } - } else { - self.updateImageFromSourceView() - }*/ - - let relativeTime: Double - let timestamp = CACurrentMediaTime() - if let startTime = self.startTime { - relativeTime = (timestamp - startTime) * (1.0 / UIView.animationDurationFactor) - } else { - self.startTime = timestamp - relativeTime = 0.0 - } - - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { - return - } - - let renderPassDescriptor = self.currentRenderPassDescriptor! - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0.0) - - guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - return - } - - let viewportDimensions = CGSize(width: self.bounds.size.width * self.contentScaleFactor, height: self.bounds.size.height * self.contentScaleFactor) - - renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState) - - let gridSize = 1000 - var time: Float = Float(min(relativeTime, 0.7)) - - var gridResolution = simd_uint2(UInt32(gridSize), UInt32(gridSize)) - var resolution = simd_uint2(UInt32(viewportDimensions.width), UInt32(viewportDimensions.height)) - - var center = simd_uint2(UInt32(self.centerLocation.x * self.contentScaleFactor), UInt32(self.centerLocation.y * self.contentScaleFactor)); - - if let texture = self.texture { - var contentScale: Float = Float(self.contentScaleFactor) - renderEncoder.setVertexBytes(¢er, length: MemoryLayout.size, index: 0) - renderEncoder.setVertexBytes(&gridResolution, length: MemoryLayout.size, index: 1) - renderEncoder.setVertexBytes(&resolution, length: MemoryLayout.size, index: 2) - renderEncoder.setVertexBytes(&time, length: MemoryLayout.size, index: 3) - renderEncoder.setVertexBytes(&contentScale, length: MemoryLayout.size, index: 4) - - renderEncoder.setFragmentTexture(texture, index: 0) - - renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6 * gridSize * gridSize, instanceCount: 1) - } - - renderEncoder.endEncoding() - - commandBuffer.present(drawable) - commandBuffer.commit() - - if relativeTime >= 0.7 { - //self.startTime = nil - self.isPaused = true - self.completion() - } - } -} diff --git a/submodules/TelegramUI/Components/SpaceWarpView/BUILD b/submodules/TelegramUI/Components/SpaceWarpView/BUILD index 2118beea58..1bca92ffc7 100644 --- a/submodules/TelegramUI/Components/SpaceWarpView/BUILD +++ b/submodules/TelegramUI/Components/SpaceWarpView/BUILD @@ -13,7 +13,7 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/ComponentFlow", - "//submodules/TelegramUI/Components/SpaceWarpView/STCMeshView", + "//submodules/TelegramUI/Components/MeshTransform", "//submodules/UIKitRuntimeUtils", ], visibility = [ diff --git a/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift b/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift index c2769c54d5..1e8149a9bf 100644 --- a/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift +++ b/submodules/TelegramUI/Components/SpaceWarpView/Sources/SpaceWarpView.swift @@ -3,8 +3,82 @@ import UIKit import Display import AsyncDisplayKit import ComponentFlow -import STCMeshView -import UIKitRuntimeUtils +import MeshTransform + +private let backdropLayerClass: NSObject? = { + let name = ("CA" as NSString).appendingFormat("BackdropLayer") + if let cls = NSClassFromString(name as String) as AnyObject as? NSObject { + return cls + } + return nil +}() + +@inline(__always) +private func getMethod(object: NSObject, selector: String) -> T? { + guard let method = object.method(for: NSSelectorFromString(selector)) else { + return nil + } + return unsafeBitCast(method, to: T.self) +} + +private var cachedBackdropLayerAllocMethod: (@convention(c) (AnyObject, Selector) -> NSObject?, Selector)? +private func invokeBackdropLayerCreateMethod() -> NSObject? { + guard let backdropLayerClass = backdropLayerClass else { + return nil + } + if let cachedBackdropLayerAllocMethod { + return cachedBackdropLayerAllocMethod.0(backdropLayerClass, cachedBackdropLayerAllocMethod.1) + } else { + let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: backdropLayerClass, selector: "alloc") + if let method { + let selector = NSSelectorFromString("alloc") + cachedBackdropLayerAllocMethod = (method, selector) + return method(backdropLayerClass, selector) + } else { + return nil + } + } +} + +private var cachedBackdropLayerInitMethod: (@convention(c) (NSObject, Selector) -> NSObject?, Selector)? +private func invokeBackdropLayerInitMethod(object: NSObject) -> NSObject? { + if let cachedBackdropLayerInitMethod { + return cachedBackdropLayerInitMethod.0(object, cachedBackdropLayerInitMethod.1) + } else { + let method: (@convention(c) (AnyObject, Selector) -> NSObject?)? = getMethod(object: object, selector: "init") + if let method { + let selector = NSSelectorFromString("init") + cachedBackdropLayerInitMethod = (method, selector) + return method(object, selector) + } else { + return nil + } + } +} + +private func createBackdropLayer() -> CALayer? { + return invokeBackdropLayerCreateMethod().flatMap(invokeBackdropLayerInitMethod) as? CALayer +} + +private var cachedBackdropLayerSetScaleMethod: (@convention(c) (NSObject, Selector, Double) -> Void, Selector)? +private func invokeBackdropLayerSetScaleMethod(object: NSObject, scale: Double) { + if let cachedBackdropLayerSetScaleMethod { + cachedBackdropLayerSetScaleMethod.0(object, cachedBackdropLayerSetScaleMethod.1, scale) + } else { + let method: (@convention(c) (AnyObject, Selector, Double) -> Void)? = getMethod(object: object, selector: "setScale:") + if let method { + let selector = NSSelectorFromString("setScale:") + cachedBackdropLayerSetScaleMethod = (method, selector) + method(object, selector, scale) + } + } +} + +private final class BackdropLayerDelegate: NSObject, CALayerDelegate { + func action(for layer: CALayer, forKey event: String) -> CAAction? { + return nullAction + } +} private extension CGPoint { static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint { @@ -90,110 +164,6 @@ private func rippleOffset( return n * (-rippleAmount) } -func transformToFitQuad2(frame: CGRect, topLeft tl: CGPoint, topRight tr: CGPoint, bottomLeft bl: CGPoint, bottomRight br: CGPoint) -> (frame: CGRect, transform: CATransform3D) { - let frameTopLeft = frame.origin - - let transform = rectToQuad( - rect: CGRect(origin: CGPoint(), size: frame.size), - quadTL: CGPoint(x: tl.x - frameTopLeft.x, y: tl.y - frameTopLeft.y), - quadTR: CGPoint(x: tr.x - frameTopLeft.x, y: tr.y - frameTopLeft.y), - quadBL: CGPoint(x: bl.x - frameTopLeft.x, y: bl.y - frameTopLeft.y), - quadBR: CGPoint(x: br.x - frameTopLeft.x, y: br.y - frameTopLeft.y) - ) - - let anchorPoint = frame.origin - let anchorOffset = CGPoint(x: anchorPoint.x - frame.origin.x, y: anchorPoint.y - frame.origin.y) - let transPos = CATransform3DMakeTranslation(anchorOffset.x, anchorOffset.y, 0) - let transNeg = CATransform3DMakeTranslation(-anchorOffset.x, -anchorOffset.y, 0) - let fullTransform = CATransform3DConcat(CATransform3DConcat(transPos, transform), transNeg) - - return (frame, fullTransform) -} - -func transformToFitQuad(frame: CGRect, topLeft tl: CGPoint, topRight tr: CGPoint, bottomLeft bl: CGPoint, bottomRight br: CGPoint) -> (frame: CGRect, transform: CATransform3D) { - let boundingBox = boundingBox(forQuadWithTR: tr, tl: tl, bl: bl, br: br) - - let frameTopLeft = boundingBox.origin - let transform = rectToQuad( - rect: CGRect(origin: CGPoint(), size: frame.size), - quadTL: CGPoint(x: tl.x - frameTopLeft.x, y: tl.y - frameTopLeft.y), - quadTR: CGPoint(x: tr.x - frameTopLeft.x, y: tr.y - frameTopLeft.y), - quadBL: CGPoint(x: bl.x - frameTopLeft.x, y: bl.y - frameTopLeft.y), - quadBR: CGPoint(x: br.x - frameTopLeft.x, y: br.y - frameTopLeft.y) - ) - - // To account for anchor point, we must translate, transform, translate - let anchorPoint = frame.center - let anchorOffset = CGPoint(x: anchorPoint.x - boundingBox.origin.x, y: anchorPoint.y - boundingBox.origin.y) - let transPos = CATransform3DMakeTranslation(anchorOffset.x, anchorOffset.y, 0) - let transNeg = CATransform3DMakeTranslation(-anchorOffset.x, -anchorOffset.y, 0) - let fullTransform = CATransform3DConcat(CATransform3DConcat(transPos, transform), transNeg) - - // Now we set our transform - return (boundingBox, fullTransform) -} - -private func boundingBox(forQuadWithTR tr: CGPoint, tl: CGPoint, bl: CGPoint, br: CGPoint) -> CGRect { - var boundingBox = CGRect.zero - - let xmin = min(min(min(tr.x, tl.x), bl.x), br.x) - let ymin = min(min(min(tr.y, tl.y), bl.y), br.y) - let xmax = max(max(max(tr.x, tl.x), bl.x), br.x) - let ymax = max(max(max(tr.y, tl.y), bl.y), br.y) - - boundingBox.origin.x = xmin - boundingBox.origin.y = ymin - boundingBox.size.width = xmax - xmin - boundingBox.size.height = ymax - ymin - - return boundingBox -} - -func rectToQuad(rect: CGRect, quadTL topLeft: CGPoint, quadTR topRight: CGPoint, quadBL bottomLeft: CGPoint, quadBR bottomRight: CGPoint) -> CATransform3D { - return rectToQuad(rect: rect, quadTLX: topLeft.x, quadTLY: topLeft.y, quadTRX: topRight.x, quadTRY: topRight.y, quadBLX: bottomLeft.x, quadBLY: bottomLeft.y, quadBRX: bottomRight.x, quadBRY: bottomRight.y) -} - -private func rectToQuad(rect: CGRect, quadTLX x1a: CGFloat, quadTLY y1a: CGFloat, quadTRX x2a: CGFloat, quadTRY y2a: CGFloat, quadBLX x3a: CGFloat, quadBLY y3a: CGFloat, quadBRX x4a: CGFloat, quadBRY y4a: CGFloat) -> CATransform3D { - let X = rect.origin.x - let Y = rect.origin.y - let W = rect.size.width - let H = rect.size.height - - let y21 = y2a - y1a - let y32 = y3a - y2a - let y43 = y4a - y3a - let y14 = y1a - y4a - let y31 = y3a - y1a - let y42 = y4a - y2a - - let a = -H * (x2a * x3a * y14 + x2a * x4a * y31 - x1a * x4a * y32 + x1a * x3a * y42) - let b = W * (x2a * x3a * y14 + x3a * x4a * y21 + x1a * x4a * y32 + x1a * x2a * y43) - let c = H * X * (x2a * x3a * y14 + x2a * x4a * y31 - x1a * x4a * y32 + x1a * x3a * y42) - H * W * x1a * (x4a * y32 - x3a * y42 + x2a * y43) - W * Y * (x2a * x3a * y14 + x3a * x4a * y21 + x1a * x4a * y32 + x1a * x2a * y43) - - let d = H * (-x4a * y21 * y3a + x2a * y1a * y43 - x1a * y2a * y43 - x3a * y1a * y4a + x3a * y2a * y4a) - let e = W * (x4a * y2a * y31 - x3a * y1a * y42 - x2a * y31 * y4a + x1a * y3a * y42) - let f = -(W * (x4a * (Y * y2a * y31 + H * y1a * y32) - x3a * (H + Y) * y1a * y42 + H * x2a * y1a * y43 + x2a * Y * (y1a - y3a) * y4a + x1a * Y * y3a * (-y2a + y4a)) - H * X * (x4a * y21 * y3a - x2a * y1a * y43 + x3a * (y1a - y2a) * y4a + x1a * y2a * (-y3a + y4a))) - - let g = H * (x3a * y21 - x4a * y21 + (-x1a + x2a) * y43) - let h = W * (-x2a * y31 + x4a * y31 + (x1a - x3a) * y42) - var i = W * Y * (x2a * y31 - x4a * y31 - x1a * y42 + x3a * y42) + H * (X * (-(x3a * y21) + x4a * y21 + x1a * y43 - x2a * y43) + W * (-(x3a * y2a) + x4a * y2a + x2a * y3a - x4a * y3a - x2a * y4a + x3a * y4a)) - - let kEpsilon = 0.0001 - - if abs(i) < kEpsilon { - i = kEpsilon * (i > 0 ? 1.0 : -1.0) - } - - let transform = CATransform3D( - m11: a / i, m12: d / i, m13: 0, m14: g / i, - m21: b / i, m22: e / i, m23: 0, m24: h / i, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: c / i, m42: f / i, m43: 0, m44: 1.0 - ) - - return transform -} - public protocol SpaceWarpNode: ASDisplayNode { var contentNode: ASDisplayNode { get } @@ -201,126 +171,6 @@ public protocol SpaceWarpNode: ASDisplayNode { func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) } -private final class MaskGridLayer: SimpleLayer { - private var itemLayers: [SimpleLayer] = [] - - private var resolution: (x: Int, y: Int)? - - func updateGrid(size: CGSize, resolutionX: Int, resolutionY: Int, cornerRadius: CGFloat) { - if let resolution = self.resolution, resolution.x == resolutionX, resolution.y == resolutionY { - return - } - self.resolution = (resolutionX, resolutionY) - - for itemLayer in self.itemLayers { - itemLayer.removeFromSuperlayer() - } - self.itemLayers.removeAll() - - let itemSize = CGSize(width: size.width / CGFloat(resolutionX), height: size.height / CGFloat(resolutionY)) - - let topLeftCorner = CGRect(origin: CGPoint(), size: CGSize(width: cornerRadius, height: cornerRadius)) - let topRightCorner = CGRect(origin: CGPoint(x: size.width - cornerRadius, y: 0.0), size: CGSize(width: cornerRadius, height: cornerRadius)) - let bottomLeftCorner = CGRect(origin: CGPoint(x: 0.0, y: size.height - cornerRadius), size: CGSize(width: cornerRadius, height: cornerRadius)) - let bottomRightCorner = CGRect(origin: CGPoint(x: size.width - cornerRadius, y: size.height - cornerRadius), size: CGSize(width: cornerRadius, height: cornerRadius)) - - var cornersImage: UIImage? - if cornerRadius > 0.0 { - cornersImage = generateImage(CGSize(width: cornerRadius * 2.0 + 200.0, height: cornerRadius * 2.0 + 200.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.black.cgColor) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: cornerRadius).cgPath) - context.fillPath() - }) - } - - for y in 0 ..< resolutionY { - for x in 0 ..< resolutionX { - let itemLayer = SimpleLayer() - itemLayer.backgroundColor = UIColor.black.cgColor - itemLayer.isOpaque = true - itemLayer.opacity = 1.0 - itemLayer.anchorPoint = CGPoint() - self.addSublayer(itemLayer) - self.itemLayers.append(itemLayer) - - if cornerRadius > 0.0, let cornersImage { - let gridPosition = CGPoint(x: CGFloat(x) / CGFloat(resolutionX), y: CGFloat(y) / CGFloat(resolutionY)) - let sourceRect = CGRect(origin: CGPoint(x: gridPosition.x * (size.width), y: gridPosition.y * (size.height)), size: itemSize) - if sourceRect.intersects(topLeftCorner) || sourceRect.intersects(topRightCorner) || sourceRect.intersects(bottomLeftCorner) || sourceRect.intersects(bottomRightCorner) { - var clippedCornersRect = sourceRect - if clippedCornersRect.maxX > cornersImage.size.width { - clippedCornersRect.origin.x -= size.width - cornersImage.size.width - } - if clippedCornersRect.maxY > cornersImage.size.height { - clippedCornersRect.origin.y -= size.height - cornersImage.size.height - } - - itemLayer.contents = cornersImage.cgImage - itemLayer.contentsRect = CGRect(origin: CGPoint(x: clippedCornersRect.minX / cornersImage.size.width, y: clippedCornersRect.minY / cornersImage.size.height), size: CGSize(width: clippedCornersRect.width / cornersImage.size.width, height: clippedCornersRect.height / cornersImage.size.height)) - itemLayer.backgroundColor = nil - itemLayer.isOpaque = false - } - } - } - } - } - - func update(positions: [CGPoint], bounds: [CGRect], transforms: [CATransform3D]) { - for i in 0 ..< self.itemLayers.count { - if i < positions.count && i < bounds.count && i < transforms.count { - let itemLayer = self.itemLayers[i] - itemLayer.position = positions[i] - itemLayer.bounds = bounds[i] - itemLayer.transform = transforms[i] - } - } - } -} - -private final class PrivateContentLayerRestoreContext { - final class Reference { - weak var layer: CALayer? - - init(layer: CALayer) { - self.layer = layer - } - } - - private static func collectPrivateContentLayers(layer: CALayer, into references: inout [Reference]) { - if getLayerDisableScreenshots(layer) { - references.append(Reference(layer: layer)) - } - if let sublayers = layer.sublayers { - for sublayer in sublayers { - collectPrivateContentLayers(layer: sublayer, into: &references) - } - } - } - - private let references: [Reference] - - init(rootLayer: CALayer) { - var references: [Reference] = [] - PrivateContentLayerRestoreContext.collectPrivateContentLayers(layer: rootLayer, into: &references) - self.references = references - - for reference in self.references { - if let layer = reference.layer { - setLayerDisableScreenshots(layer, false) - } - } - } - - func restore() { - for reference in self.references { - if let layer = reference.layer { - setLayerDisableScreenshots(layer, true) - } - } - } -} - open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { private final class Shockwave { let startPoint: CGPoint @@ -337,13 +187,11 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { private let contentNodeSource: ASDisplayNode private let backgroundView: UIView - private var currentCloneView: UIView? - private var meshView: STCMeshView? - - private var privateContentRestoreContext: PrivateContentLayerRestoreContext? + private let backdropLayer: CALayer? + private let cornerOverlayView: UIImageView + private let backdropLayerDelegate: BackdropLayerDelegate private var gradientLayer: SimpleGradientLayer? - private var gradientMaskLayer: MaskGridLayer? private var link: SharedDisplayLinkDriver.Link? @@ -351,30 +199,43 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { private var resolution: (x: Int, y: Int)? private var layoutParams: (size: CGSize, cornerRadius: CGFloat)? + private var cornerOverlayImageRadius: CGFloat? + + private let cornerOverlayInset: CGFloat = 48.0 override public init() { + self.backdropLayerDelegate = BackdropLayerDelegate() + self.backdropLayer = createBackdropLayer() + self.cornerOverlayView = UIImageView() + self.contentNodeSource = ASDisplayNode() + self.contentNodeSource.layer.rasterizationScale = UIScreenScale self.backgroundView = UIView() self.backgroundView.backgroundColor = .black + self.backgroundView.isHidden = true super.init() self.addSubnode(self.contentNodeSource) - self.view.addSubview(self.backgroundView) + self.view.insertSubview(self.backgroundView, belowSubview: self.contentNodeSource.view) + + if let backdropLayer = self.backdropLayer { + self.layer.addSublayer(backdropLayer) + backdropLayer.delegate = self.backdropLayerDelegate + backdropLayer.isHidden = true + + invokeBackdropLayerSetScaleMethod(object: backdropLayer, scale: UIScreenScale) + backdropLayer.rasterizationScale = UIScreenScale + } + + self.cornerOverlayView.isUserInteractionEnabled = false + self.cornerOverlayView.isHidden = true + self.cornerOverlayView.layer.rasterizationScale = UIScreenScale + self.view.addSubview(self.cornerOverlayView) } public static func supportsHierarchy(layer: CALayer) -> Bool { - if getLayerDisableScreenshots(layer) { - return false - } - if let sublayers = layer.sublayers { - for sublayer in sublayers { - if !supportsHierarchy(layer: sublayer) { - return false - } - } - } return true } @@ -415,17 +276,92 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { return } self.resolution = (resolutionX, resolutionY) + } + + private func makeRippleMeshTransform(size: CGSize, resolution: (x: Int, y: Int), params: RippleParams) -> MeshTransform.Value? { + let vertexWidth = resolution.x + 1 + let vertexHeight = resolution.y + 1 + let vertexCount = vertexWidth * vertexHeight - if let meshView = self.meshView { - self.meshView = nil - meshView.removeFromSuperview() + guard size.width > 0.0, size.height > 0.0, vertexCount > 0 else { + return nil } - let meshView = STCMeshView(frame: CGRect()) - self.meshView = meshView - self.view.insertSubview(meshView, aboveSubview: self.backgroundView) + func vertexIndex(_ x: Int, _ y: Int) -> Int { + return y * vertexWidth + x + } - meshView.instanceCount = resolutionX * resolutionY + var positions = Array(repeating: CGPoint(), count: vertexCount) + for y in 0 ..< vertexHeight { + let normalizedY = CGFloat(y) / CGFloat(resolution.y) + for x in 0 ..< vertexWidth { + let normalizedX = CGFloat(x) / CGFloat(resolution.x) + let initialPosition = CGPoint(x: normalizedX * size.width, y: normalizedY * size.height) + + var displacedPosition = initialPosition + for shockwave in self.shockwaves { + displacedPosition = displacedPosition + rippleOffset(position: initialPosition, origin: shockwave.startPoint, time: shockwave.timeValue, params: params) + } + + positions[vertexIndex(x, y)] = displacedPosition + } + } + + let mesh = MeshTransform() + for y in 0 ..< vertexHeight { + let normalizedY = CGFloat(y) / CGFloat(resolution.y) + for x in 0 ..< vertexWidth { + let normalizedX = CGFloat(x) / CGFloat(resolution.x) + let source = CGPoint(x: normalizedX, y: normalizedY) + let displacedPosition = positions[vertexIndex(x, y)] + let destination = MeshTransform.Point3D( + x: displacedPosition.x / size.width, + y: displacedPosition.y / size.height, + z: 0.0 + ) + mesh.add(MeshTransform.Vertex(from: source, to: destination)) + } + } + + for y in 0 ..< resolution.y { + for x in 0 ..< resolution.x { + let topLeft = UInt32(vertexIndex(x, y)) + let topRight = UInt32(vertexIndex(x + 1, y)) + let bottomRight = UInt32(vertexIndex(x + 1, y + 1)) + let bottomLeft = UInt32(vertexIndex(x, y + 1)) + + mesh.add(MeshTransform.Face(indices: (topLeft, topRight, bottomRight, bottomLeft), w: (0.0, 0.0, 0.0, 0.0))) + } + } + + return mesh.makeValue() + } + + private func updateCornerOverlayImage(cornerRadius: CGFloat) { + let cornerRadius = max(0.0, cornerRadius) + if let currentRadius = self.cornerOverlayImageRadius, abs(currentRadius - cornerRadius) < 0.001 { + return + } + self.cornerOverlayImageRadius = cornerRadius + + let cornerExtent = max(1.0, ceil(cornerRadius + self.cornerOverlayInset)) + let imageSize = CGSize(width: cornerExtent * 2.0 + 1.0, height: cornerExtent * 2.0 + 1.0) + + self.cornerOverlayView.image = generateImage(imageSize, opaque: false, rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + context.setFillColor(UIColor.black.cgColor) + context.fill(bounds) + + let innerRect = bounds.insetBy(dx: self.cornerOverlayInset, dy: self.cornerOverlayInset) + context.setBlendMode(.clear) + context.addPath(UIBezierPath(roundedRect: innerRect, cornerRadius: cornerRadius).cgPath) + context.fillPath() + context.setBlendMode(.normal) + })?.resizableImage( + withCapInsets: UIEdgeInsets(top: cornerExtent, left: cornerExtent, bottom: cornerExtent, right: cornerExtent), + resizingMode: .stretch + ) } public func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) { @@ -438,12 +374,7 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) - let params = RippleParams(amplitude: 10.0, frequency: 15.0, decay: 5.5, speed: 1400.0) - - if let currentCloneView = self.currentCloneView { - currentCloneView.removeFromSuperview() - self.currentCloneView = nil - } + let params = RippleParams(amplitude: 15.0, frequency: 15.0, decay: 5.5, speed: 1400.0) let maxEdge = (max(size.width, size.height) * 0.5) * 2.0 let maxDistance = sqrt(maxEdge * maxEdge + maxEdge * maxEdge) @@ -461,10 +392,14 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { link.invalidate() } - if let meshView = self.meshView { - self.meshView = nil - meshView.removeFromSuperview() + if let backdropLayer = self.backdropLayer { + backdropLayer.removeAnimation(forKey: "meshTransform") + backdropLayer.setValue(nil, forKey: "meshTransform") + backdropLayer.isHidden = true } + self.cornerOverlayView.layer.removeAnimation(forKey: "meshTransform") + self.cornerOverlayView.layer.setValue(nil, forKey: "meshTransform") + self.cornerOverlayView.isHidden = true self.resolution = nil self.backgroundView.isHidden = true @@ -475,50 +410,42 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { self.gradientLayer = nil gradientLayer.removeFromSuperlayer() } - if let gradientMaskLayer = self.gradientMaskLayer { - self.gradientMaskLayer = nil - gradientMaskLayer.removeFromSuperlayer() - } - - if let privateContentRestoreContext = self.privateContentRestoreContext { - self.privateContentRestoreContext = nil - privateContentRestoreContext.restore() - } return } - if self.privateContentRestoreContext == nil { - self.privateContentRestoreContext = PrivateContentLayerRestoreContext(rootLayer: self.contentNodeSource.view.layer) - } - self.backgroundView.isHidden = false self.contentNodeSource.clipsToBounds = true self.contentNodeSource.layer.cornerRadius = cornerRadius - let resolutionX = max(2, Int(size.width / 50.0)) - let resolutionY = max(2, Int(size.height / 50.0)) + if let backdropLayer = self.backdropLayer { + backdropLayer.isHidden = false + transition.setFrame(layer: backdropLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setCornerRadius(layer: backdropLayer, cornerRadius: cornerRadius) + + self.cornerOverlayView.isHidden = false + self.updateCornerOverlayImage(cornerRadius: cornerRadius) + transition.setFrame( + view: self.cornerOverlayView, + frame: CGRect( + x: -self.cornerOverlayInset, + y: -self.cornerOverlayInset, + width: size.width + self.cornerOverlayInset * 2.0, + height: size.height + self.cornerOverlayInset * 2.0 + ) + ) + } else { + self.cornerOverlayView.isHidden = true + } + + let resolutionX = max(2, Int(size.width / 48.0)) + let resolutionY = max(2, Int(size.height / 48.0)) self.updateGrid(resolutionX: resolutionX, resolutionY: resolutionY) - guard let resolution = self.resolution, let meshView = self.meshView else { + guard let resolution = self.resolution else { return } - if let cloneView = self.contentNodeSource.view.resizableSnapshotView(from: CGRect(origin: CGPoint(), size: size), afterScreenUpdates: false, withCapInsets: UIEdgeInsets()) { - self.currentCloneView = cloneView - meshView.contentView.addSubview(cloneView) - } - - meshView.frame = CGRect(origin: CGPoint(), size: size) - if let shockwave = self.shockwaves.first { - let gradientMaskLayer: MaskGridLayer - if let current = self.gradientMaskLayer { - gradientMaskLayer = current - } else { - gradientMaskLayer = MaskGridLayer() - self.gradientMaskLayer = gradientMaskLayer - } - let gradientLayer: SimpleGradientLayer if let current = self.gradientLayer { gradientLayer = current @@ -529,12 +456,9 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { gradientLayer.type = .radial gradientLayer.colors = [UIColor(white: 1.0, alpha: 0.0).cgColor, UIColor(white: 1.0, alpha: 0.0).cgColor, UIColor(white: 1.0, alpha: 0.2).cgColor, UIColor(white: 1.0, alpha: 0.0).cgColor] - - gradientLayer.mask = gradientMaskLayer } gradientLayer.frame = CGRect(origin: CGPoint(), size: size) - gradientMaskLayer.frame = CGRect(origin: CGPoint(), size: size) gradientLayer.startPoint = CGPoint(x: shockwave.startPoint.x / size.width, y: shockwave.startPoint.y / size.height) @@ -564,73 +488,20 @@ open class SpaceWarpNodeImpl: ASDisplayNode, SpaceWarpNode { } } - let itemSize = CGSize(width: size.width / CGFloat(resolution.x), height: size.height / CGFloat(resolution.y)) - - var instanceBounds: [CGRect] = [] - var instancePositions: [CGPoint] = [] - var instanceTransforms: [CATransform3D] = [] - - for y in 0 ..< resolution.y { - for x in 0 ..< resolution.x { - let gridPosition = CGPoint(x: CGFloat(x) / CGFloat(resolution.x), y: CGFloat(y) / CGFloat(resolution.y)) - - let sourceRect = CGRect(origin: CGPoint(x: gridPosition.x * (size.width), y: gridPosition.y * (size.height)), size: itemSize) - - let initialTopLeft = CGPoint(x: sourceRect.minX, y: sourceRect.minY) - let initialTopRight = CGPoint(x: sourceRect.maxX, y: sourceRect.minY) - let initialBottomLeft = CGPoint(x: sourceRect.minX, y: sourceRect.maxY) - let initialBottomRight = CGPoint(x: sourceRect.maxX, y: sourceRect.maxY) - - var topLeft = initialTopLeft - var topRight = initialTopRight - var bottomLeft = initialBottomLeft - var bottomRight = initialBottomRight - - for shockwave in self.shockwaves { - topLeft = topLeft + rippleOffset(position: initialTopLeft, origin: shockwave.startPoint, time: shockwave.timeValue, params: params) - topRight = topRight + rippleOffset(position: initialTopRight, origin: shockwave.startPoint, time: shockwave.timeValue, params: params) - bottomLeft = bottomLeft + rippleOffset(position: initialBottomLeft, origin: shockwave.startPoint, time: shockwave.timeValue, params: params) - bottomRight = bottomRight + rippleOffset(position: initialBottomRight, origin: shockwave.startPoint, time: shockwave.timeValue, params: params) + if let backdropLayer = self.backdropLayer { + if let meshTransform = self.makeRippleMeshTransform(size: size, resolution: resolution, params: params) { + if !transition.animation.isImmediate { + backdropLayer.removeAnimation(forKey: "meshTransform") + self.cornerOverlayView.layer.removeAnimation(forKey: "meshTransform") } - /*topLeft = transformCoordinate(position: topLeft, origin: startPoint, time: self.timeValue, params: params) - topRight = transformCoordinate(position: topRight, origin: startPoint, time: self.timeValue, params: params) - bottomLeft = transformCoordinate(position: bottomLeft, origin: startPoint, time: self.timeValue, params: params) - bottomRight = transformCoordinate(position: bottomRight, origin: startPoint, time: self.timeValue, params: params)*/ - - let distanceTopLeft = length(topLeft - initialTopLeft) - let distanceTopRight = length(topRight - initialTopRight) - let distanceBottomLeft = length(bottomLeft - initialBottomLeft) - let distanceBottomRight = length(bottomRight - initialBottomRight) - var maxDistance = max(distanceTopLeft, distanceTopRight) - maxDistance = max(maxDistance, distanceBottomLeft) - maxDistance = max(maxDistance, distanceBottomRight) - - var (frame, transform) = transformToFitQuad2(frame: sourceRect, topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight) - - if maxDistance <= 0.005 { - transform = CATransform3DIdentity - } - - instanceBounds.append(frame) - instancePositions.append(frame.origin) - - instanceTransforms.append(transform) + backdropLayer.setValue(meshTransform, forKey: "meshTransform") + self.cornerOverlayView.layer.setValue(meshTransform, forKey: "meshTransform") + } else { + backdropLayer.setValue(nil, forKey: "meshTransform") + self.cornerOverlayView.layer.setValue(nil, forKey: "meshTransform") } - } - - instanceBounds.withUnsafeMutableBufferPointer { buffer in - meshView.instanceBounds = buffer.baseAddress! - } - instancePositions.withUnsafeMutableBufferPointer { buffer in - meshView.instancePositions = buffer.baseAddress! - } - instanceTransforms.withUnsafeMutableBufferPointer { buffer in - meshView.instanceTransforms = buffer.baseAddress! - } - - if let gradientMaskLayer = self.gradientMaskLayer { - gradientMaskLayer.updateGrid(size: size, resolutionX: resolutionX, resolutionY: resolutionY, cornerRadius: cornerRadius) - gradientMaskLayer.update(positions: instancePositions, bounds: instanceBounds, transforms: instanceTransforms) + } else { + self.cornerOverlayView.layer.setValue(nil, forKey: "meshTransform") } }