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