mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP]
This commit is contained in:
parent
169fece59c
commit
857bf2190e
BIN
Tests/CallUITest/Resources/test.png
Normal file
BIN
Tests/CallUITest/Resources/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 463 KiB |
BIN
Tests/CallUITest/Resources/test2.mp4
Normal file
BIN
Tests/CallUITest/Resources/test2.mp4
Normal file
Binary file not shown.
63
submodules/MetalEngine/BUILD
Normal file
63
submodules/MetalEngine/BUILD
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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 = "MetalSources",
|
||||||
|
srcs = glob([
|
||||||
|
"Metal/**/*.metal",
|
||||||
|
]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
plist_fragment(
|
||||||
|
name = "MetalEngineMetalSourcesBundleInfoPlist",
|
||||||
|
extension = "plist",
|
||||||
|
template =
|
||||||
|
"""
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>org.telegram.MetalEngineMetalSources</string>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>MetalEngine</string>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
apple_resource_bundle(
|
||||||
|
name = "MetalEngineMetalSourcesBundle",
|
||||||
|
infoplists = [
|
||||||
|
":MetalEngineMetalSourcesBundleInfoPlist",
|
||||||
|
],
|
||||||
|
resources = [
|
||||||
|
":MetalSources",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
swift_library(
|
||||||
|
name = "MetalEngine",
|
||||||
|
module_name = "MetalEngine",
|
||||||
|
srcs = glob([
|
||||||
|
"Sources/**/*.swift",
|
||||||
|
]),
|
||||||
|
copts = [
|
||||||
|
"-warnings-as-errors",
|
||||||
|
],
|
||||||
|
data = [
|
||||||
|
":MetalEngineMetalSourcesBundle",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//submodules/Display",
|
||||||
|
"//submodules/Utils/ShelfPack",
|
||||||
|
],
|
||||||
|
visibility = [
|
||||||
|
"//visibility:public",
|
||||||
|
],
|
||||||
|
)
|
11
submodules/MetalEngine/Metal/MetalEngineShaders.metal
Normal file
11
submodules/MetalEngine/Metal/MetalEngineShaders.metal
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
vertex float4 clearVertex(const device float2* vertexArray [[ buffer(0) ]], unsigned int vid [[ vertex_id ]]) {
|
||||||
|
return float4(vertexArray[vid], 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 clearFragment(const device float4 &color [[ buffer(0) ]]) {
|
||||||
|
return half4(color);
|
||||||
|
}
|
@ -2,15 +2,9 @@ import Foundation
|
|||||||
import Metal
|
import Metal
|
||||||
import UIKit
|
import UIKit
|
||||||
import IOSurface
|
import IOSurface
|
||||||
|
import Display
|
||||||
import ShelfPack
|
import ShelfPack
|
||||||
|
|
||||||
private func alignUp(size: Int, align: Int) -> Int {
|
|
||||||
precondition(((align - 1) & align) == 0, "Align must be a power of two")
|
|
||||||
|
|
||||||
let alignmentMask = align - 1
|
|
||||||
return (size + alignmentMask) & ~alignmentMask
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class Placeholder<Resolved> {
|
public final class Placeholder<Resolved> {
|
||||||
var contents: Resolved?
|
var contents: Resolved?
|
||||||
}
|
}
|
||||||
@ -110,16 +104,16 @@ public struct RenderLayerPlacement: Equatable {
|
|||||||
public protocol RenderToLayerState: AnyObject {
|
public protocol RenderToLayerState: AnyObject {
|
||||||
var pipelineState: MTLRenderPipelineState { get }
|
var pipelineState: MTLRenderPipelineState { get }
|
||||||
|
|
||||||
init?(device: MTLDevice, library: MTLLibrary)
|
init?(device: MTLDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ComputeState: AnyObject {
|
public protocol ComputeState: AnyObject {
|
||||||
init?(device: MTLDevice, library: MTLLibrary)
|
init?(device: MTLDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class MetalSubjectLayer: SimpleLayer {
|
open class MetalEngineSubjectLayer: SimpleLayer {
|
||||||
fileprivate var internalId: Int = -1
|
fileprivate var internalId: Int = -1
|
||||||
fileprivate var surfaceAllocation: MetalContext.SurfaceAllocation?
|
fileprivate var surfaceAllocation: MetalEngine.SurfaceAllocation?
|
||||||
|
|
||||||
public override init() {
|
public override init() {
|
||||||
super.init()
|
super.init()
|
||||||
@ -136,7 +130,7 @@ open class MetalSubjectLayer: SimpleLayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func setNeedsDisplay() {
|
override public func setNeedsDisplay() {
|
||||||
if let subject = self as? MetalSubject {
|
if let subject = self as? MetalEngineSubject {
|
||||||
subject.setNeedsUpdate()
|
subject.setNeedsUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +165,7 @@ public final class PooledTexture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get(context: MetalSubjectContext) -> TexturePlaceholder? {
|
public func get(context: MetalEngineSubjectContext) -> TexturePlaceholder? {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if context.freeResourcesOnCompletion.contains(where: { $0 === self }) {
|
if context.freeResourcesOnCompletion.contains(where: { $0 === self }) {
|
||||||
assertionFailure("Trying to get PooledTexture more than once per update cycle")
|
assertionFailure("Trying to get PooledTexture more than once per update cycle")
|
||||||
@ -193,7 +187,7 @@ public final class PooledTexture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class MetalSubjectContext {
|
public final class MetalEngineSubjectContext {
|
||||||
fileprivate final class ComputeOperation {
|
fileprivate final class ComputeOperation {
|
||||||
let commands: (MTLCommandBuffer) -> Void
|
let commands: (MTLCommandBuffer) -> Void
|
||||||
|
|
||||||
@ -205,13 +199,13 @@ public final class MetalSubjectContext {
|
|||||||
fileprivate final class RenderToLayerOperation {
|
fileprivate final class RenderToLayerOperation {
|
||||||
let spec: RenderLayerSpec
|
let spec: RenderLayerSpec
|
||||||
let state: RenderToLayerState
|
let state: RenderToLayerState
|
||||||
weak var layer: MetalSubjectLayer?
|
weak var layer: MetalEngineSubjectLayer?
|
||||||
let commands: (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
let commands: (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
spec: RenderLayerSpec,
|
spec: RenderLayerSpec,
|
||||||
state: RenderToLayerState,
|
state: RenderToLayerState,
|
||||||
layer: MetalSubjectLayer,
|
layer: MetalEngineSubjectLayer,
|
||||||
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
||||||
) {
|
) {
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
@ -222,13 +216,13 @@ public final class MetalSubjectContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let device: MTLDevice
|
private let device: MTLDevice
|
||||||
private let impl: MetalContext.Impl
|
private let impl: MetalEngine.Impl
|
||||||
|
|
||||||
fileprivate var computeOperations: [ComputeOperation] = []
|
fileprivate var computeOperations: [ComputeOperation] = []
|
||||||
fileprivate var renderToLayerOperationsGroupedByState: [ObjectIdentifier: [RenderToLayerOperation]] = [:]
|
fileprivate var renderToLayerOperationsGroupedByState: [ObjectIdentifier: [RenderToLayerOperation]] = [:]
|
||||||
fileprivate var freeResourcesOnCompletion: [PooledTexture.Texture] = []
|
fileprivate var freeResourcesOnCompletion: [PooledTexture.Texture] = []
|
||||||
|
|
||||||
fileprivate init(device: MTLDevice, impl: MetalContext.Impl) {
|
fileprivate init(device: MTLDevice, impl: MetalEngine.Impl) {
|
||||||
self.device = device
|
self.device = device
|
||||||
self.impl = impl
|
self.impl = impl
|
||||||
}
|
}
|
||||||
@ -236,7 +230,7 @@ public final class MetalSubjectContext {
|
|||||||
public func renderToLayer<RenderToLayerStateType: RenderToLayerState, each Resolved>(
|
public func renderToLayer<RenderToLayerStateType: RenderToLayerState, each Resolved>(
|
||||||
spec: RenderLayerSpec,
|
spec: RenderLayerSpec,
|
||||||
state: RenderToLayerStateType.Type,
|
state: RenderToLayerStateType.Type,
|
||||||
layer: MetalSubjectLayer,
|
layer: MetalEngineSubjectLayer,
|
||||||
inputs: repeat Placeholder<each Resolved>,
|
inputs: repeat Placeholder<each Resolved>,
|
||||||
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement, repeat each Resolved) -> Void
|
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement, repeat each Resolved) -> Void
|
||||||
) {
|
) {
|
||||||
@ -245,7 +239,7 @@ public final class MetalSubjectContext {
|
|||||||
if let current = self.impl.renderStates[stateTypeId] as? RenderToLayerStateType {
|
if let current = self.impl.renderStates[stateTypeId] as? RenderToLayerStateType {
|
||||||
resolvedState = current
|
resolvedState = current
|
||||||
} else {
|
} else {
|
||||||
guard let value = RenderToLayerStateType(device: self.device, library: self.impl.library) else {
|
guard let value = RenderToLayerStateType(device: self.device) else {
|
||||||
assertionFailure("Could not initialize render state \(state)")
|
assertionFailure("Could not initialize render state \(state)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -278,7 +272,7 @@ public final class MetalSubjectContext {
|
|||||||
public func renderToLayer<RenderToLayerStateType: RenderToLayerState>(
|
public func renderToLayer<RenderToLayerStateType: RenderToLayerState>(
|
||||||
spec: RenderLayerSpec,
|
spec: RenderLayerSpec,
|
||||||
state: RenderToLayerStateType.Type,
|
state: RenderToLayerStateType.Type,
|
||||||
layer: MetalSubjectLayer,
|
layer: MetalEngineSubjectLayer,
|
||||||
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
commands: @escaping (MTLRenderCommandEncoder, RenderLayerPlacement) -> Void
|
||||||
) {
|
) {
|
||||||
self.renderToLayer(spec: spec, state: state, layer: layer, inputs: noInputPlaceholder, commands: { encoder, placement, _ in
|
self.renderToLayer(spec: spec, state: state, layer: layer, inputs: noInputPlaceholder, commands: { encoder, placement, _ in
|
||||||
@ -296,7 +290,7 @@ public final class MetalSubjectContext {
|
|||||||
if let current = self.impl.computeStates[stateTypeId] as? ComputeStateType {
|
if let current = self.impl.computeStates[stateTypeId] as? ComputeStateType {
|
||||||
resolvedState = current
|
resolvedState = current
|
||||||
} else {
|
} else {
|
||||||
guard let value = ComputeStateType(device: self.device, library: self.impl.library) else {
|
guard let value = ComputeStateType(device: self.device) else {
|
||||||
assertionFailure("Could not initialize compute state \(state)")
|
assertionFailure("Could not initialize compute state \(state)")
|
||||||
return Placeholder()
|
return Placeholder()
|
||||||
}
|
}
|
||||||
@ -338,24 +332,24 @@ public final class MetalSubjectContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class MetalSubjectInternalData {
|
public final class MetalEngineSubjectInternalData {
|
||||||
var internalId: Int = -1
|
var internalId: Int = -1
|
||||||
var renderSurfaceAllocation: MetalContext.SurfaceAllocation?
|
var renderSurfaceAllocation: MetalEngine.SurfaceAllocation?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol MetalSubject: AnyObject {
|
public protocol MetalEngineSubject: AnyObject {
|
||||||
var internalData: MetalSubjectInternalData? { get set }
|
var internalData: MetalEngineSubjectInternalData? { get set }
|
||||||
|
|
||||||
func setNeedsUpdate()
|
func setNeedsUpdate()
|
||||||
func update(context: MetalSubjectContext)
|
func update(context: MetalEngineSubjectContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension MetalSubject {
|
public extension MetalEngineSubject {
|
||||||
func setNeedsUpdate() {
|
func setNeedsUpdate() {
|
||||||
MetalContext.shared.impl.addSubjectNeedsUpdate(subject: self)
|
MetalEngine.shared.impl.addSubjectNeedsUpdate(subject: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +364,7 @@ private final class MetalEventLayer: CAMetalLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class MetalContext {
|
public final class MetalEngine {
|
||||||
struct SurfaceAllocation {
|
struct SurfaceAllocation {
|
||||||
struct Phase {
|
struct Phase {
|
||||||
let subRect: CGRect
|
let subRect: CGRect
|
||||||
@ -509,9 +503,9 @@ public final class MetalContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class SubjectReference {
|
private final class SubjectReference {
|
||||||
weak var subject: MetalSubject?
|
weak var subject: MetalEngineSubject?
|
||||||
|
|
||||||
init(subject: MetalSubject) {
|
init(subject: MetalEngineSubject) {
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -548,7 +542,7 @@ public final class MetalContext {
|
|||||||
|
|
||||||
init?(device: MTLDevice) {
|
init?(device: MTLDevice) {
|
||||||
let mainBundle = Bundle(for: Impl.self)
|
let mainBundle = Bundle(for: Impl.self)
|
||||||
guard let path = mainBundle.path(forResource: "MetalSourcesBundle", ofType: "bundle") else {
|
guard let path = mainBundle.path(forResource: "MetalEngineMetalSourcesBundle", ofType: "bundle") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let bundle = Bundle(path: path) else {
|
guard let bundle = Bundle(path: path) else {
|
||||||
@ -624,15 +618,15 @@ public final class MetalContext {
|
|||||||
let surfaceId = self.nextSurfaceId
|
let surfaceId = self.nextSurfaceId
|
||||||
self.nextSurfaceId += 1
|
self.nextSurfaceId += 1
|
||||||
|
|
||||||
let surfaceWidth = max(1024, alignUp(size: Int(minSize.width), align: 64))
|
let surfaceWidth = max(1024, alignUp(Int(minSize.width), alignment: 64))
|
||||||
let surfaceHeight = max(512, alignUp(size: Int(minSize.height), align: 64))
|
let surfaceHeight = max(512, alignUp(Int(minSize.height), alignment: 64))
|
||||||
let surface = Surface(id: surfaceId, device: self.device, width: surfaceWidth, height: surfaceHeight)
|
let surface = Surface(id: surfaceId, device: self.device, width: surfaceWidth, height: surfaceHeight)
|
||||||
self.surfaces[surfaceId] = surface
|
self.surfaces[surfaceId] = surface
|
||||||
|
|
||||||
return surface
|
return surface
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshLayerAllocation(layer: MetalSubjectLayer, renderSpec: RenderLayerSpec) {
|
private func refreshLayerAllocation(layer: MetalEngineSubjectLayer, renderSpec: RenderLayerSpec) {
|
||||||
var previousSurfaceId: Int?
|
var previousSurfaceId: Int?
|
||||||
var updatedSurfaceId: Int?
|
var updatedSurfaceId: Int?
|
||||||
|
|
||||||
@ -689,12 +683,12 @@ public final class MetalContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSubjectNeedsUpdate(subject: MetalSubject) {
|
func addSubjectNeedsUpdate(subject: MetalEngineSubject) {
|
||||||
let internalData: MetalSubjectInternalData
|
let internalData: MetalEngineSubjectInternalData
|
||||||
if let current = subject.internalData {
|
if let current = subject.internalData {
|
||||||
internalData = current
|
internalData = current
|
||||||
} else {
|
} else {
|
||||||
internalData = MetalSubjectInternalData()
|
internalData = MetalEngineSubjectInternalData()
|
||||||
subject.internalData = internalData
|
subject.internalData = internalData
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,7 +745,7 @@ public final class MetalContext {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let subjectContext = MetalSubjectContext(device: device, impl: self)
|
let subjectContext = MetalEngineSubjectContext(device: device, impl: self)
|
||||||
|
|
||||||
for subjectReference in self.updatedSubjects {
|
for subjectReference in self.updatedSubjects {
|
||||||
guard let subject = subjectReference.subject else {
|
guard let subject = subjectReference.subject else {
|
||||||
@ -899,7 +893,7 @@ public final class MetalContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static let shared = MetalContext()
|
public static let shared = MetalEngine()
|
||||||
|
|
||||||
fileprivate let impl: Impl
|
fileprivate let impl: Impl
|
||||||
|
|
@ -105,6 +105,7 @@ swift_library(
|
|||||||
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
|
"//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer",
|
||||||
"//submodules/PeerInfoUI/CreateExternalMediaStreamScreen:CreateExternalMediaStreamScreen",
|
"//submodules/PeerInfoUI/CreateExternalMediaStreamScreen:CreateExternalMediaStreamScreen",
|
||||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||||
|
"//submodules/TelegramUI/Components/Calls/CallScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -135,11 +135,11 @@ public final class CallController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
if self.call.isVideoPossible {
|
#if DEBUG
|
||||||
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
self.displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
} else {
|
#else
|
||||||
self.displayNode = LegacyCallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
}
|
#endif
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
self.controllerNode.toggleMute = { [weak self] in
|
self.controllerNode.toggleMute = { [weak self] in
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
import Foundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import TelegramAudio
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import SwiftSignalKit
|
||||||
|
import CallScreen
|
||||||
|
|
||||||
|
final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeProtocol {
|
||||||
|
private let sharedContext: SharedAccountContext
|
||||||
|
private let account: Account
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let statusBar: StatusBar
|
||||||
|
private let call: PresentationCall
|
||||||
|
|
||||||
|
private let callScreen: PrivateCallScreen
|
||||||
|
|
||||||
|
var isMuted: Bool = false
|
||||||
|
|
||||||
|
var toggleMute: (() -> Void)?
|
||||||
|
var setCurrentAudioOutput: ((AudioSessionOutput) -> Void)?
|
||||||
|
var beginAudioOuputSelection: ((Bool) -> Void)?
|
||||||
|
var acceptCall: (() -> Void)?
|
||||||
|
var endCall: (() -> Void)?
|
||||||
|
var back: (() -> Void)?
|
||||||
|
var presentCallRating: ((CallId, Bool) -> Void)?
|
||||||
|
var present: ((ViewController) -> Void)?
|
||||||
|
var callEnded: ((Bool) -> Void)?
|
||||||
|
var dismissedInteractively: (() -> Void)?
|
||||||
|
var dismissAllTooltips: (() -> Void)?
|
||||||
|
|
||||||
|
init(
|
||||||
|
sharedContext: SharedAccountContext,
|
||||||
|
account: Account,
|
||||||
|
presentationData: PresentationData,
|
||||||
|
statusBar: StatusBar,
|
||||||
|
debugInfo: Signal<(String, String), NoError>,
|
||||||
|
shouldStayHiddenUntilConnection: Bool = false,
|
||||||
|
easyDebugAccess: Bool,
|
||||||
|
call: PresentationCall
|
||||||
|
) {
|
||||||
|
self.sharedContext = sharedContext
|
||||||
|
self.account = account
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.statusBar = statusBar
|
||||||
|
self.call = call
|
||||||
|
|
||||||
|
self.callScreen = PrivateCallScreen()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.view.addSubview(self.callScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCallState(_ callState: PresentationCallState) {
|
||||||
|
if case let .terminated(id, _, reportRating) = callState.state, let callId = id {
|
||||||
|
if reportRating {
|
||||||
|
self.presentCallRating?(callId, self.call.isVideo)
|
||||||
|
}
|
||||||
|
self.callEnded?(reportRating)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandFromPipIfPossible() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
self.callScreen.update(size: layout.size, insets: layout.insets(options: [.statusBar]))
|
||||||
|
}
|
||||||
|
}
|
@ -410,6 +410,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorScreen",
|
"//submodules/TelegramUI/Components/Settings/PeerNameColorScreen",
|
||||||
"//submodules/TelegramUI/Components/ContextMenuScreen",
|
"//submodules/TelegramUI/Components/ContextMenuScreen",
|
||||||
"//submodules/TelegramUI/Components/PeerAllowedReactionsScreen",
|
"//submodules/TelegramUI/Components/PeerAllowedReactionsScreen",
|
||||||
|
"//submodules/MetalEngine",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
@ -10,7 +10,7 @@ load("//build-system/bazel-utils:plist_fragment.bzl",
|
|||||||
)
|
)
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "MetalSources",
|
name = "CallScreenMetalSources",
|
||||||
srcs = glob([
|
srcs = glob([
|
||||||
"Metal/**/*.metal",
|
"Metal/**/*.metal",
|
||||||
]),
|
]),
|
||||||
@ -18,7 +18,7 @@ filegroup(
|
|||||||
)
|
)
|
||||||
|
|
||||||
plist_fragment(
|
plist_fragment(
|
||||||
name = "MetalSourcesBundleInfoPlist",
|
name = "CallScreenMetalSourcesBundleInfoPlist",
|
||||||
extension = "plist",
|
extension = "plist",
|
||||||
template =
|
template =
|
||||||
"""
|
"""
|
||||||
@ -27,20 +27,26 @@ plist_fragment(
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>AnimationCompression</string>
|
<string>CallScreen</string>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
apple_resource_bundle(
|
apple_resource_bundle(
|
||||||
name = "MetalSourcesBundle",
|
name = "CallScreenMetalSourcesBundle",
|
||||||
infoplists = [
|
infoplists = [
|
||||||
":MetalSourcesBundleInfoPlist",
|
":CallScreenMetalSourcesBundleInfoPlist",
|
||||||
],
|
],
|
||||||
resources = [
|
resources = [
|
||||||
":MetalSources",
|
":CallScreenMetalSources",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "Assets",
|
||||||
|
srcs = glob(["CallScreenAssets.xcassets/**"]),
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
swift_library(
|
swift_library(
|
||||||
name = "CallScreen",
|
name = "CallScreen",
|
||||||
module_name = "CallScreen",
|
module_name = "CallScreen",
|
||||||
@ -51,13 +57,14 @@ swift_library(
|
|||||||
"-warnings-as-errors",
|
"-warnings-as-errors",
|
||||||
],
|
],
|
||||||
data = [
|
data = [
|
||||||
":MetalSourcesBundle",
|
":CallScreenMetalSourcesBundle",
|
||||||
|
":Assets",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//submodules/Display",
|
"//submodules/Display",
|
||||||
|
"//submodules/MetalEngine",
|
||||||
"//submodules/ComponentFlow",
|
"//submodules/ComponentFlow",
|
||||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||||
"//submodules/Utils/ShelfPack",
|
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_audioairpods.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_audioairpodspro.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_airpodsmax.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_audiobt.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"provides-namespace" : true
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 503 B |
Binary file not shown.
After Width: | Height: | Size: 782 B |
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "CallCancelIcon@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "CallCancelIcon@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_flip (1).pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_microphone.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_speaker.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_call_camera.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -21,14 +21,6 @@ struct QuadVertexOut {
|
|||||||
float2 uv;
|
float2 uv;
|
||||||
};
|
};
|
||||||
|
|
||||||
vertex float4 clearVertex(const device float2* vertexArray [[ buffer(0) ]], unsigned int vid [[ vertex_id ]]) {
|
|
||||||
return float4(vertexArray[vid], 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment half4 clearFragment(const device float4 &color [[ buffer(0) ]]) {
|
|
||||||
return half4(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
vertex QuadVertexOut callBackgroundVertex(
|
vertex QuadVertexOut callBackgroundVertex(
|
||||||
const device Rectangle &rect [[ buffer(0) ]],
|
const device Rectangle &rect [[ buffer(0) ]],
|
||||||
unsigned int vid [[ vertex_id ]]
|
unsigned int vid [[ vertex_id ]]
|
@ -147,6 +147,18 @@ public final class AnimatedProperty<T: AnimationInterpolatable>: AnyAnimatedProp
|
|||||||
self.didStartAnimation?()
|
self.didStartAnimation?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func animate(from: T, to: T, duration: Double, curve: AnimationCurve) {
|
||||||
|
self.value = from
|
||||||
|
self.animation = Animation(from: from, to: to, duration: duration, curve: curve)
|
||||||
|
self.animation?.start()
|
||||||
|
self.didStartAnimation?()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func set(to: T) {
|
||||||
|
self.animation = nil
|
||||||
|
self.value = to
|
||||||
|
}
|
||||||
|
|
||||||
override public func update() {
|
override public func update() {
|
||||||
if let animation = self.animation {
|
if let animation = self.animation {
|
||||||
self.value = animation.update(at: CACurrentMediaTime())
|
self.value = animation.update(at: CACurrentMediaTime())
|
||||||
|
@ -1,4 +1,28 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import MetalKit
|
||||||
|
|
||||||
|
private final class BundleMarker: NSObject {
|
||||||
|
}
|
||||||
|
|
||||||
|
private var metalLibraryValue: MTLLibrary?
|
||||||
|
func metalLibrary(device: MTLDevice) -> MTLLibrary? {
|
||||||
|
if let metalLibraryValue {
|
||||||
|
return metalLibraryValue
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainBundle = Bundle(for: BundleMarker.self)
|
||||||
|
guard let path = mainBundle.path(forResource: "CallScreenMetalSourcesBundle", ofType: "bundle") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let bundle = Bundle(path: path) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let library = try? device.makeDefaultLibrary(bundle: bundle) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
metalLibraryValue = library
|
||||||
|
return library
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class AvatarLayer: SimpleLayer {
|
final class AvatarLayer: SimpleLayer {
|
||||||
var image: UIImage? {
|
var image: UIImage? {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class ButtonGroupView: UIView, ContentOverlayView {
|
final class ButtonGroupView: UIView, ContentOverlayView {
|
||||||
enum Key: Hashable {
|
enum Key: Hashable {
|
||||||
@ -12,6 +13,7 @@ final class ButtonGroupView: UIView, ContentOverlayView {
|
|||||||
let overlayMaskLayer: CALayer
|
let overlayMaskLayer: CALayer
|
||||||
private var buttons: [Key: ContentOverlayButton] = [:]
|
private var buttons: [Key: ContentOverlayButton] = [:]
|
||||||
|
|
||||||
|
var audioPressed: (() -> Void)?
|
||||||
var toggleVideo: (() -> Void)?
|
var toggleVideo: (() -> Void)?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@ -89,9 +91,15 @@ final class ButtonGroupView: UIView, ContentOverlayView {
|
|||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
switch key {
|
switch key {
|
||||||
case .audio:
|
case .audio:
|
||||||
image = UIImage(named: "Call/ButtonSpeaker")
|
image = UIImage(named: "Call/Speaker")
|
||||||
|
button.action = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.audioPressed?()
|
||||||
|
}
|
||||||
case .video:
|
case .video:
|
||||||
image = UIImage(named: "Call/ButtonVideo")
|
image = UIImage(named: "Call/Video")
|
||||||
button.action = { [weak self] in
|
button.action = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -99,9 +107,9 @@ final class ButtonGroupView: UIView, ContentOverlayView {
|
|||||||
self.toggleVideo?()
|
self.toggleVideo?()
|
||||||
}
|
}
|
||||||
case .mic:
|
case .mic:
|
||||||
image = UIImage(named: "Call/ButtonMicMuted")
|
image = UIImage(named: "Call/Mute")
|
||||||
case .close:
|
case .close:
|
||||||
image = UIImage(named: "Call/ButtonEnd")
|
image = UIImage(named: "Call/End")
|
||||||
}
|
}
|
||||||
|
|
||||||
button.setImage(image?.withRenderingMode(.alwaysTemplate), for: .normal)
|
button.setImage(image?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
private func shiftArray(array: [SIMD2<Float>], offset: Int) -> [SIMD2<Float>] {
|
private func shiftArray(array: [SIMD2<Float>], offset: Int) -> [SIMD2<Float>] {
|
||||||
var newArray = array
|
var newArray = array
|
||||||
@ -28,13 +29,28 @@ private func hexToFloat(_ hex: Int) -> SIMD4<Float> {
|
|||||||
return SIMD4<Float>(x: red, y: green, z: blue, w: 1.0)
|
return SIMD4<Float>(x: red, y: green, z: blue, w: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
private struct ColorSet: Equatable, AnimationInterpolatable {
|
||||||
var internalData: MetalSubjectInternalData?
|
static let animationInterpolator = AnimationInterpolator<ColorSet> { from, to, fraction in
|
||||||
|
var result: [SIMD4<Float>] = []
|
||||||
|
for i in 0 ..< min(from.colors.count, to.colors.count) {
|
||||||
|
result.append(from.colors[i] * Float(1.0 - fraction) + to.colors[i] * Float(fraction))
|
||||||
|
}
|
||||||
|
return ColorSet(colors: result)
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors: [SIMD4<Float>]
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CallBackgroundLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
||||||
|
var internalData: MetalEngineSubjectInternalData?
|
||||||
|
|
||||||
final class RenderState: RenderToLayerState {
|
final class RenderState: RenderToLayerState {
|
||||||
let pipelineState: MTLRenderPipelineState
|
let pipelineState: MTLRenderPipelineState
|
||||||
|
|
||||||
init?(device: MTLDevice, library: MTLLibrary) {
|
init?(device: MTLDevice) {
|
||||||
|
guard let library = metalLibrary(device: device) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
guard let vertexFunction = library.makeFunction(name: "callBackgroundVertex"), let fragmentFunction = library.makeFunction(name: "callBackgroundFragment") else {
|
guard let vertexFunction = library.makeFunction(name: "callBackgroundVertex"), let fragmentFunction = library.makeFunction(name: "callBackgroundFragment") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -66,11 +82,44 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
|
|
||||||
private var displayLinkSubscription: SharedDisplayLink.Subscription?
|
private var displayLinkSubscription: SharedDisplayLink.Subscription?
|
||||||
|
|
||||||
var renderSpec: RenderLayerSpec?
|
var renderSpec: RenderLayerSpec? {
|
||||||
|
didSet {
|
||||||
|
if self.renderSpec != oldValue {
|
||||||
|
self.setNeedsUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let colorSets: [ColorSet]
|
||||||
|
private let colorTransition: AnimatedProperty<ColorSet>
|
||||||
|
private var stateIndex: Int = 0
|
||||||
|
private let phaseAcceleration = AnimatedProperty<CGFloat>(0.0)
|
||||||
|
|
||||||
init(isBlur: Bool) {
|
init(isBlur: Bool) {
|
||||||
self.isBlur = isBlur
|
self.isBlur = isBlur
|
||||||
|
|
||||||
|
self.colorSets = [
|
||||||
|
ColorSet(colors: [
|
||||||
|
hexToFloat(0x568FD6),
|
||||||
|
hexToFloat(0x626ED5),
|
||||||
|
hexToFloat(0xA667D5),
|
||||||
|
hexToFloat(0x7664DA)
|
||||||
|
]),
|
||||||
|
ColorSet(colors: [
|
||||||
|
hexToFloat(0xACBD65),
|
||||||
|
hexToFloat(0x459F8D),
|
||||||
|
hexToFloat(0x53A4D1),
|
||||||
|
hexToFloat(0x3E917A)
|
||||||
|
]),
|
||||||
|
ColorSet(colors: [
|
||||||
|
hexToFloat(0xC0508D),
|
||||||
|
hexToFloat(0xF09536),
|
||||||
|
hexToFloat(0xCE5081),
|
||||||
|
hexToFloat(0xFC7C4C)
|
||||||
|
])
|
||||||
|
]
|
||||||
|
self.colorTransition = AnimatedProperty<ColorSet>(colorSets[0])
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.didEnterHierarchy = { [weak self] in
|
self.didEnterHierarchy = { [weak self] in
|
||||||
@ -81,8 +130,14 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.colorTransition.update()
|
||||||
|
self.phaseAcceleration.update()
|
||||||
|
|
||||||
let stepCount = 8
|
let stepCount = 8
|
||||||
self.phase = (self.phase + 1.0 / 60.0).truncatingRemainder(dividingBy: Float(stepCount))
|
var phaseStep: CGFloat = 0.5 / 30.0
|
||||||
|
phaseStep += phaseStep * self.phaseAcceleration.value * 0.5
|
||||||
|
self.phase = (self.phase + Float(phaseStep)).truncatingRemainder(dividingBy: Float(stepCount))
|
||||||
|
|
||||||
self.setNeedsUpdate()
|
self.setNeedsUpdate()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -95,6 +150,9 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override init(layer: Any) {
|
override init(layer: Any) {
|
||||||
|
self.colorSets = []
|
||||||
|
self.colorTransition = AnimatedProperty<ColorSet>(ColorSet(colors: []))
|
||||||
|
|
||||||
super.init(layer: layer)
|
super.init(layer: layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +160,19 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(context: MetalSubjectContext) {
|
func update(stateIndex: Int, animated: Bool) {
|
||||||
|
if self.stateIndex != stateIndex {
|
||||||
|
self.stateIndex = stateIndex
|
||||||
|
if animated {
|
||||||
|
self.phaseAcceleration.animate(from: 1.0, to: 0.0, duration: 2.0, curve: .easeInOut)
|
||||||
|
self.colorTransition.animate(to: self.colorSets[stateIndex % self.colorSets.count], duration: 0.3, curve: .easeInOut)
|
||||||
|
} else {
|
||||||
|
self.colorTransition.set(to: self.colorSets[stateIndex % self.colorSets.count])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(context: MetalEngineSubjectContext) {
|
||||||
guard let renderSpec = self.renderSpec else {
|
guard let renderSpec = self.renderSpec else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -127,22 +197,9 @@ final class CallBackgroundLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
}
|
}
|
||||||
encoder.setFragmentBytes(&positions, length: 4 * MemoryLayout<SIMD2<Float>>.size, index: 0)
|
encoder.setFragmentBytes(&positions, length: 4 * MemoryLayout<SIMD2<Float>>.size, index: 0)
|
||||||
|
|
||||||
var colors: [[SIMD4<Float>]] = [
|
var colors: [SIMD4<Float>] = self.colorTransition.value.colors
|
||||||
[
|
|
||||||
hexToFloat(0x568FD6),
|
|
||||||
hexToFloat(0x626ED5),
|
|
||||||
hexToFloat(0xA667D5),
|
|
||||||
hexToFloat(0x7664DA)
|
|
||||||
],
|
|
||||||
[
|
|
||||||
hexToFloat(0xACBD65),
|
|
||||||
hexToFloat(0x459F8D),
|
|
||||||
hexToFloat(0x53A4D1),
|
|
||||||
hexToFloat(0x3E917A)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
encoder.setFragmentBytes(&colors[0], length: 4 * MemoryLayout<SIMD4<Float>>.size, index: 1)
|
encoder.setFragmentBytes(&colors, length: 4 * MemoryLayout<SIMD4<Float>>.size, index: 1)
|
||||||
var brightness: Float = isBlur ? 1.1 : 1.0
|
var brightness: Float = isBlur ? 1.1 : 1.0
|
||||||
var saturation: Float = isBlur ? 1.2 : 1.0
|
var saturation: Float = isBlur ? 1.2 : 1.0
|
||||||
encoder.setFragmentBytes(&brightness, length: 4, index: 2)
|
encoder.setFragmentBytes(&brightness, length: 4, index: 2)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MetalKit
|
import MetalKit
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
final class CallBlobsLayer: MetalSubjectLayer, MetalSubject {
|
final class CallBlobsLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
||||||
var internalData: MetalSubjectInternalData?
|
var internalData: MetalEngineSubjectInternalData?
|
||||||
|
|
||||||
struct Blob {
|
struct Blob {
|
||||||
var points: [Float]
|
var points: [Float]
|
||||||
@ -48,10 +49,10 @@ final class CallBlobsLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
|
|
||||||
let pipelineState: MTLRenderPipelineState
|
let pipelineState: MTLRenderPipelineState
|
||||||
|
|
||||||
required init?(
|
required init?(device: MTLDevice) {
|
||||||
device: MTLDevice,
|
guard let library = metalLibrary(device: device) else {
|
||||||
library: MTLLibrary
|
return nil
|
||||||
) {
|
}
|
||||||
guard let vertexFunction = library.makeFunction(name: "callBlobVertex"), let fragmentFunction = library.makeFunction(name: "callBlobFragment") else {
|
guard let vertexFunction = library.makeFunction(name: "callBlobVertex"), let fragmentFunction = library.makeFunction(name: "callBlobFragment") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -123,7 +124,7 @@ final class CallBlobsLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(context: MetalSubjectContext) {
|
func update(context: MetalEngineSubjectContext) {
|
||||||
if self.bounds.isEmpty {
|
if self.bounds.isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class ContentOverlayButton: UIButton, ContentOverlayView {
|
final class ContentOverlayButton: UIButton, ContentOverlayView {
|
||||||
var overlayMaskLayer: CALayer {
|
var overlayMaskLayer: CALayer {
|
||||||
|
@ -1,7 +1,21 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
final class ContentView: UIView {
|
final class ContentView: UIView {
|
||||||
|
private struct Params: Equatable {
|
||||||
|
var size: CGSize
|
||||||
|
var insets: UIEdgeInsets
|
||||||
|
var state: PrivateCallScreen.State
|
||||||
|
|
||||||
|
init(size: CGSize, insets: UIEdgeInsets, state: PrivateCallScreen.State) {
|
||||||
|
self.size = size
|
||||||
|
self.insets = insets
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let blobLayer: CallBlobsLayer
|
private let blobLayer: CallBlobsLayer
|
||||||
private let avatarLayer: AvatarLayer
|
private let avatarLayer: AvatarLayer
|
||||||
private let titleView: TextView
|
private let titleView: TextView
|
||||||
@ -14,8 +28,7 @@ final class ContentView: UIView {
|
|||||||
private var videoLayerMask: SimpleShapeLayer?
|
private var videoLayerMask: SimpleShapeLayer?
|
||||||
private var blurredVideoLayerMask: SimpleShapeLayer?
|
private var blurredVideoLayerMask: SimpleShapeLayer?
|
||||||
|
|
||||||
private var currentLayout: (size: CGSize, insets: UIEdgeInsets)?
|
private var params: Params?
|
||||||
private var phase: CGFloat = 0.0
|
|
||||||
|
|
||||||
private var isDisplayingVideo: Bool = false
|
private var isDisplayingVideo: Bool = false
|
||||||
private var videoDisplayFraction = AnimatedProperty<CGFloat>(0.0)
|
private var videoDisplayFraction = AnimatedProperty<CGFloat>(0.0)
|
||||||
@ -54,8 +67,8 @@ final class ContentView: UIView {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let currentLayout = self.currentLayout {
|
if let params = self.params {
|
||||||
self.update(size: currentLayout.size, insets: currentLayout.insets)
|
self.updateInternal(params: params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,41 +77,59 @@ final class ContentView: UIView {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, insets: UIEdgeInsets) {
|
func update(size: CGSize, insets: UIEdgeInsets, state: PrivateCallScreen.State) {
|
||||||
self.currentLayout = (size, insets)
|
let params = Params(size: size, insets: insets, state: state)
|
||||||
|
if self.params == params {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.params = params
|
||||||
|
self.updateInternal(params: params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateInternal(params: Params) {
|
||||||
if self.emojiView == nil {
|
if self.emojiView == nil {
|
||||||
let emojiView = KeyEmojiView(emoji: ["🐱", "🚂", "❄️", "🎨"])
|
let emojiView = KeyEmojiView(emoji: ["🐱", "🚂", "❄️", "🎨"])
|
||||||
self.emojiView = emojiView
|
self.emojiView = emojiView
|
||||||
self.addSubview(emojiView)
|
self.addSubview(emojiView)
|
||||||
}
|
}
|
||||||
if let emojiView = self.emojiView {
|
if let emojiView = self.emojiView {
|
||||||
emojiView.frame = CGRect(origin: CGPoint(x: size.width - 12.0 - emojiView.size.width, y: insets.top + 27.0), size: emojiView.size)
|
emojiView.frame = CGRect(origin: CGPoint(x: params.size.width - 12.0 - emojiView.size.width, y: params.insets.top + 27.0), size: emojiView.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.videoInput == nil, let url = Bundle.main.url(forResource: "test2", withExtension: "mp4") {
|
if self.videoInput == nil, let url = Bundle.main.url(forResource: "test2", withExtension: "mp4") {
|
||||||
self.videoInput = VideoInput(device: MetalContext.shared.device, url: url)
|
self.videoInput = VideoInput(device: MetalEngine.shared.device, url: url)
|
||||||
self.videoLayer.video = self.videoInput
|
self.videoLayer.video = self.videoInput
|
||||||
}
|
}
|
||||||
|
|
||||||
self.phase += 3.0 / 60.0
|
//self.phase += 3.0 / 60.0
|
||||||
self.phase = self.phase.truncatingRemainder(dividingBy: 1.0)
|
//self.phase = self.phase.truncatingRemainder(dividingBy: 1.0)
|
||||||
var avatarScale: CGFloat = 0.05 * sin(CGFloat(self.phase) * CGFloat.pi)
|
var avatarScale: CGFloat = 0.05 * sin(CGFloat(0.0) * CGFloat.pi)
|
||||||
avatarScale *= 1.0 - self.videoDisplayFraction.value
|
avatarScale *= 1.0 - self.videoDisplayFraction.value
|
||||||
|
|
||||||
let avatarSize: CGFloat = 136.0
|
let avatarSize: CGFloat = 136.0
|
||||||
let blobSize: CGFloat = 176.0
|
let blobSize: CGFloat = 176.0
|
||||||
|
|
||||||
let expandedVideoRadius: CGFloat = sqrt(pow(size.width * 0.5, 2.0) + pow(size.height * 0.5, 2.0))
|
let expandedVideoRadius: CGFloat = sqrt(pow(params.size.width * 0.5, 2.0) + pow(params.size.height * 0.5, 2.0))
|
||||||
|
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: floor((size.width - avatarSize) * 0.5), y: CGFloat.animationInterpolator.interpolate(from: 222.0, to: floor((size.height - avatarSize) * 0.5), fraction: self.videoDisplayFraction.value)), size: CGSize(width: avatarSize, height: avatarSize))
|
let avatarFrame = CGRect(origin: CGPoint(x: floor((params.size.width - avatarSize) * 0.5), y: CGFloat.animationInterpolator.interpolate(from: 222.0, to: floor((params.size.height - avatarSize) * 0.5), fraction: self.videoDisplayFraction.value)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
|
||||||
let titleSize = self.titleView.update(string: "Emma Walters", fontSize: CGFloat.animationInterpolator.interpolate(from: 28.0, to: 17.0, fraction: self.videoDisplayFraction.value), fontWeight: CGFloat.animationInterpolator.interpolate(from: 0.0, to: 0.25, fraction: self.videoDisplayFraction.value), constrainedWidth: size.width - 16.0 * 2.0)
|
let titleSize = self.titleView.update(string: "Emma Walters", fontSize: CGFloat.animationInterpolator.interpolate(from: 28.0, to: 17.0, fraction: self.videoDisplayFraction.value), fontWeight: CGFloat.animationInterpolator.interpolate(from: 0.0, to: 0.25, fraction: self.videoDisplayFraction.value), constrainedWidth: params.size.width - 16.0 * 2.0)
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: (size.width - titleSize.width) * 0.5, y: CGFloat.animationInterpolator.interpolate(from: avatarFrame.maxY + 39.0, to: insets.top + 17.0, fraction: self.videoDisplayFraction.value)), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: (params.size.width - titleSize.width) * 0.5, y: CGFloat.animationInterpolator.interpolate(from: avatarFrame.maxY + 39.0, to: params.insets.top + 17.0, fraction: self.videoDisplayFraction.value)), size: titleSize)
|
||||||
self.titleView.frame = titleFrame
|
self.titleView.frame = titleFrame
|
||||||
|
|
||||||
let statusSize = self.statusView.update(state: .waiting(.requesting))
|
let statusState: StatusView.State
|
||||||
self.statusView.frame = CGRect(origin: CGPoint(x: (size.width - statusSize.width) * 0.5, y: titleFrame.maxY + CGFloat.animationInterpolator.interpolate(from: 4.0, to: 0.0, fraction: self.videoDisplayFraction.value)), size: statusSize)
|
switch params.state.lifecycleState {
|
||||||
|
case .connecting:
|
||||||
|
statusState = .waiting(.requesting)
|
||||||
|
case .ringing:
|
||||||
|
statusState = .waiting(.ringing)
|
||||||
|
case .exchangingKeys:
|
||||||
|
statusState = .waiting(.generatingKeys)
|
||||||
|
case let .active(_, signalInfo):
|
||||||
|
statusState = .active(StatusView.ActiveState(signalStrength: signalInfo.quality))
|
||||||
|
}
|
||||||
|
let statusSize = self.statusView.update(state: statusState)
|
||||||
|
self.statusView.frame = CGRect(origin: CGPoint(x: (params.size.width - statusSize.width) * 0.5, y: titleFrame.maxY + CGFloat.animationInterpolator.interpolate(from: 4.0, to: 0.0, fraction: self.videoDisplayFraction.value)), size: statusSize)
|
||||||
|
|
||||||
let blobFrame = CGRect(origin: CGPoint(x: floor(avatarFrame.midX - blobSize * 0.5), y: floor(avatarFrame.midY - blobSize * 0.5)), size: CGSize(width: blobSize, height: blobSize))
|
let blobFrame = CGRect(origin: CGPoint(x: floor(avatarFrame.midX - blobSize * 0.5), y: floor(avatarFrame.midY - blobSize * 0.5)), size: CGSize(width: blobSize, height: blobSize))
|
||||||
|
|
||||||
@ -114,7 +145,7 @@ final class ContentView: UIView {
|
|||||||
self.blobLayer.transform = CATransform3DMakeScale(1.0 + avatarScale * 2.0, 1.0 + avatarScale * 2.0, 1.0)
|
self.blobLayer.transform = CATransform3DMakeScale(1.0 + avatarScale * 2.0, 1.0 + avatarScale * 2.0, 1.0)
|
||||||
|
|
||||||
let videoResolution = CGSize(width: 400.0, height: 400.0)
|
let videoResolution = CGSize(width: 400.0, height: 400.0)
|
||||||
let videoFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)
|
let videoFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: params.size)
|
||||||
|
|
||||||
let videoRenderingSize = CGSize(width: videoResolution.width * 2.0, height: videoResolution.height * 2.0)
|
let videoRenderingSize = CGSize(width: videoResolution.width * 2.0, height: videoResolution.height * 2.0)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import UIKit
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import MetalPerformanceShaders
|
import MetalPerformanceShaders
|
||||||
import Accelerate
|
import Accelerate
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
func imageToCVPixelBuffer(image: UIImage) -> CVPixelBuffer? {
|
func imageToCVPixelBuffer(image: UIImage) -> CVPixelBuffer? {
|
||||||
guard let cgImage = image.cgImage, let data = cgImage.dataProvider?.data, let bytes = CFDataGetBytePtr(data), let colorSpace = cgImage.colorSpace, case .rgb = colorSpace.model, cgImage.bitsPerPixel / cgImage.bitsPerComponent == 4 else {
|
guard let cgImage = image.cgImage, let data = cgImage.dataProvider?.data, let bytes = CFDataGetBytePtr(data), let colorSpace = cgImage.colorSpace, case .rgb = colorSpace.model, cgImage.bitsPerPixel / cgImage.bitsPerComponent == 4 else {
|
||||||
@ -36,10 +37,10 @@ func imageToCVPixelBuffer(image: UIImage) -> CVPixelBuffer? {
|
|||||||
return pixelBuffer
|
return pixelBuffer
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
final class MainVideoLayer: MetalEngineSubjectLayer, MetalEngineSubject {
|
||||||
var internalData: MetalSubjectInternalData?
|
var internalData: MetalEngineSubjectInternalData?
|
||||||
|
|
||||||
let blurredLayer: MetalSubjectLayer
|
let blurredLayer: MetalEngineSubjectLayer
|
||||||
|
|
||||||
final class BlurState: ComputeState {
|
final class BlurState: ComputeState {
|
||||||
let computePipelineStateYUVToRGBA: MTLComputePipelineState
|
let computePipelineStateYUVToRGBA: MTLComputePipelineState
|
||||||
@ -47,10 +48,10 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
let computePipelineStateVertical: MTLComputePipelineState
|
let computePipelineStateVertical: MTLComputePipelineState
|
||||||
let downscaleKernel: MPSImageBilinearScale
|
let downscaleKernel: MPSImageBilinearScale
|
||||||
|
|
||||||
required init?(
|
required init?(device: MTLDevice) {
|
||||||
device: MTLDevice,
|
guard let library = metalLibrary(device: device) else {
|
||||||
library: MTLLibrary
|
return nil
|
||||||
) {
|
}
|
||||||
guard let functionVideoYUVToRGBA = library.makeFunction(name: "videoYUVToRGBA") else {
|
guard let functionVideoYUVToRGBA = library.makeFunction(name: "videoYUVToRGBA") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -79,10 +80,10 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
final class RenderState: RenderToLayerState {
|
final class RenderState: RenderToLayerState {
|
||||||
let pipelineState: MTLRenderPipelineState
|
let pipelineState: MTLRenderPipelineState
|
||||||
|
|
||||||
required init?(
|
required init?(device: MTLDevice) {
|
||||||
device: MTLDevice,
|
guard let library = metalLibrary(device: device) else {
|
||||||
library: MTLLibrary
|
return nil
|
||||||
) {
|
}
|
||||||
guard let vertexFunction = library.makeFunction(name: "mainVideoVertex"), let fragmentFunction = library.makeFunction(name: "mainVideoFragment") else {
|
guard let vertexFunction = library.makeFunction(name: "mainVideoVertex"), let fragmentFunction = library.makeFunction(name: "mainVideoFragment") else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -115,13 +116,13 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
private var blurredVerticalTexture: PooledTexture?
|
private var blurredVerticalTexture: PooledTexture?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.blurredLayer = MetalSubjectLayer()
|
self.blurredLayer = MetalEngineSubjectLayer()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(layer: Any) {
|
override init(layer: Any) {
|
||||||
self.blurredLayer = MetalSubjectLayer()
|
self.blurredLayer = MetalEngineSubjectLayer()
|
||||||
|
|
||||||
super.init(layer: layer)
|
super.init(layer: layer)
|
||||||
}
|
}
|
||||||
@ -130,7 +131,7 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(context: MetalSubjectContext) {
|
func update(context: MetalEngineSubjectContext) {
|
||||||
if self.isHidden {
|
if self.isHidden {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -143,16 +144,16 @@ final class MainVideoLayer: MetalSubjectLayer, MetalSubject {
|
|||||||
|
|
||||||
let rgbaTextureSpec = TextureSpec(width: videoTextures.y.width, height: videoTextures.y.height, pixelFormat: .rgba8UnsignedNormalized)
|
let rgbaTextureSpec = TextureSpec(width: videoTextures.y.width, height: videoTextures.y.height, pixelFormat: .rgba8UnsignedNormalized)
|
||||||
if self.rgbaTexture == nil || self.rgbaTexture?.spec != rgbaTextureSpec {
|
if self.rgbaTexture == nil || self.rgbaTexture?.spec != rgbaTextureSpec {
|
||||||
self.rgbaTexture = MetalContext.shared.pooledTexture(spec: rgbaTextureSpec)
|
self.rgbaTexture = MetalEngine.shared.pooledTexture(spec: rgbaTextureSpec)
|
||||||
}
|
}
|
||||||
if self.downscaledTexture == nil {
|
if self.downscaledTexture == nil {
|
||||||
self.downscaledTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
self.downscaledTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
||||||
}
|
}
|
||||||
if self.blurredHorizontalTexture == nil {
|
if self.blurredHorizontalTexture == nil {
|
||||||
self.blurredHorizontalTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
self.blurredHorizontalTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
||||||
}
|
}
|
||||||
if self.blurredVerticalTexture == nil {
|
if self.blurredVerticalTexture == nil {
|
||||||
self.blurredVerticalTexture = MetalContext.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
self.blurredVerticalTexture = MetalEngine.shared.pooledTexture(spec: TextureSpec(width: 128, height: 128, pixelFormat: .rgba8UnsignedNormalized))
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let rgbaTexture = self.rgbaTexture?.get(context: context) else {
|
guard let rgbaTexture = self.rgbaTexture?.get(context: context) else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
private final class AnimatedDotsLayer: SimpleLayer {
|
private final class AnimatedDotsLayer: SimpleLayer {
|
||||||
private let dotLayers: [SimpleLayer]
|
private let dotLayers: [SimpleLayer]
|
||||||
@ -76,8 +77,18 @@ final class StatusView: UIView {
|
|||||||
case ringing
|
case ringing
|
||||||
case generatingKeys
|
case generatingKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ActiveState {
|
||||||
|
var signalStrength: Double
|
||||||
|
|
||||||
|
init(signalStrength: Double) {
|
||||||
|
self.signalStrength = signalStrength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
case waiting(WaitingState)
|
case waiting(WaitingState)
|
||||||
|
case active(ActiveState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var textView: TextView
|
private var textView: TextView
|
||||||
@ -110,6 +121,9 @@ final class StatusView: UIView {
|
|||||||
case .generatingKeys:
|
case .generatingKeys:
|
||||||
textString = "Exchanging encryption keys"
|
textString = "Exchanging encryption keys"
|
||||||
}
|
}
|
||||||
|
case let .active(activeState):
|
||||||
|
textString = "0:00"
|
||||||
|
let _ = activeState
|
||||||
}
|
}
|
||||||
let textSize = self.textView.update(string: textString, fontSize: 16.0, fontWeight: 0.0, constrainedWidth: 200.0)
|
let textSize = self.textView.update(string: textString, fontSize: 16.0, fontWeight: 0.0, constrainedWidth: 200.0)
|
||||||
self.textView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: textSize)
|
self.textView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: textSize)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class ContentOverlayLayer: SimpleLayer {
|
final class ContentOverlayLayer: SimpleLayer {
|
||||||
private struct Params: Equatable {
|
private struct Params: Equatable {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
final class MirroringLayer: SimpleLayer {
|
final class MirroringLayer: SimpleLayer {
|
||||||
var targetLayer: SimpleLayer?
|
var targetLayer: SimpleLayer?
|
||||||
|
@ -1,7 +1,42 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
public final class PrivateCallScreen: UIView {
|
public final class PrivateCallScreen: UIView {
|
||||||
|
public struct State: Equatable {
|
||||||
|
public struct SignalInfo: Equatable {
|
||||||
|
public var quality: Double
|
||||||
|
|
||||||
|
public init(quality: Double) {
|
||||||
|
self.quality = quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum LifecycleState: Equatable {
|
||||||
|
case connecting
|
||||||
|
case ringing
|
||||||
|
case exchangingKeys
|
||||||
|
case active(startTime: Double, signalInfo: SignalInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var lifecycleState: LifecycleState
|
||||||
|
|
||||||
|
public init(lifecycleState: LifecycleState) {
|
||||||
|
self.lifecycleState = lifecycleState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Params: Equatable {
|
||||||
|
var size: CGSize
|
||||||
|
var insets: UIEdgeInsets
|
||||||
|
|
||||||
|
init(size: CGSize, insets: UIEdgeInsets) {
|
||||||
|
self.size = size
|
||||||
|
self.insets = insets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let backgroundLayer: CallBackgroundLayer
|
private let backgroundLayer: CallBackgroundLayer
|
||||||
private let contentOverlayLayer: ContentOverlayLayer
|
private let contentOverlayLayer: ContentOverlayLayer
|
||||||
private let contentOverlayContainer: ContentOverlayContainer
|
private let contentOverlayContainer: ContentOverlayContainer
|
||||||
@ -13,6 +48,18 @@ public final class PrivateCallScreen: UIView {
|
|||||||
|
|
||||||
private let buttonGroupView: ButtonGroupView
|
private let buttonGroupView: ButtonGroupView
|
||||||
|
|
||||||
|
public var state: State = State(lifecycleState: .connecting) {
|
||||||
|
didSet {
|
||||||
|
if self.state != oldValue {
|
||||||
|
if let params = self.params {
|
||||||
|
self.updateInternal(params: params, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var params: Params?
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
self.blurContentsLayer = SimpleLayer()
|
self.blurContentsLayer = SimpleLayer()
|
||||||
|
|
||||||
@ -44,6 +91,26 @@ public final class PrivateCallScreen: UIView {
|
|||||||
|
|
||||||
self.contentOverlayContainer.addSubview(self.buttonGroupView)
|
self.contentOverlayContainer.addSubview(self.buttonGroupView)
|
||||||
|
|
||||||
|
self.buttonGroupView.audioPressed = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = self.state
|
||||||
|
switch state.lifecycleState {
|
||||||
|
case .connecting, .ringing, .exchangingKeys:
|
||||||
|
state.lifecycleState = .active(startTime: CFAbsoluteTimeGetCurrent(), signalInfo: State.SignalInfo(quality: 1.0))
|
||||||
|
case let .active(startTime, signalInfo):
|
||||||
|
if signalInfo.quality == 1.0 {
|
||||||
|
state.lifecycleState = .active(startTime: startTime, signalInfo: State.SignalInfo(quality: 0.2))
|
||||||
|
} else if signalInfo.quality == 0.2 {
|
||||||
|
state.lifecycleState = .connecting
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
self.state = state
|
||||||
|
}
|
||||||
|
|
||||||
self.buttonGroupView.toggleVideo = { [weak self] in
|
self.buttonGroupView.toggleVideo = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -65,9 +132,18 @@ public final class PrivateCallScreen: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(size: CGSize, insets: UIEdgeInsets) {
|
public func update(size: CGSize, insets: UIEdgeInsets) {
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
let params = Params(size: size, insets: insets)
|
||||||
|
if self.params == params {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.params = params
|
||||||
|
self.updateInternal(params: params, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
let aspect: CGFloat = size.width / size.height
|
private func updateInternal(params: Params, animated: Bool) {
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
|
|
||||||
|
let aspect: CGFloat = params.size.width / params.size.height
|
||||||
let sizeNorm: CGFloat = 64.0
|
let sizeNorm: CGFloat = 64.0
|
||||||
let renderingSize = CGSize(width: floor(sizeNorm * aspect), height: sizeNorm)
|
let renderingSize = CGSize(width: floor(sizeNorm * aspect), height: sizeNorm)
|
||||||
let edgeSize: Int = 2
|
let edgeSize: Int = 2
|
||||||
@ -77,18 +153,36 @@ public final class PrivateCallScreen: UIView {
|
|||||||
self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
|
self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
|
||||||
self.backgroundLayer.frame = visualBackgroundFrame
|
self.backgroundLayer.frame = visualBackgroundFrame
|
||||||
|
|
||||||
self.contentOverlayLayer.frame = CGRect(origin: CGPoint(), size: size)
|
let backgroundStateIndex: Int
|
||||||
self.contentOverlayLayer.update(size: size, contentInsets: UIEdgeInsets())
|
switch self.state.lifecycleState {
|
||||||
|
case .connecting:
|
||||||
|
backgroundStateIndex = 0
|
||||||
|
case .ringing:
|
||||||
|
backgroundStateIndex = 0
|
||||||
|
case .exchangingKeys:
|
||||||
|
backgroundStateIndex = 0
|
||||||
|
case let .active(_, signalInfo):
|
||||||
|
if signalInfo.quality <= 0.2 {
|
||||||
|
backgroundStateIndex = 2
|
||||||
|
} else {
|
||||||
|
backgroundStateIndex = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.backgroundLayer.update(stateIndex: backgroundStateIndex, animated: animated)
|
||||||
|
|
||||||
self.contentOverlayContainer.frame = CGRect(origin: CGPoint(), size: size)
|
self.contentOverlayLayer.frame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
|
self.contentOverlayLayer.update(size: params.size, contentInsets: UIEdgeInsets())
|
||||||
|
|
||||||
|
self.contentOverlayContainer.frame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
|
|
||||||
self.blurBackgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
|
self.blurBackgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
|
||||||
self.blurBackgroundLayer.frame = visualBackgroundFrame
|
self.blurBackgroundLayer.frame = visualBackgroundFrame
|
||||||
|
self.blurBackgroundLayer.update(stateIndex: backgroundStateIndex, animated: animated)
|
||||||
|
|
||||||
self.buttonGroupView.frame = CGRect(origin: CGPoint(), size: size)
|
self.buttonGroupView.frame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
self.buttonGroupView.update(size: size)
|
self.buttonGroupView.update(size: params.size)
|
||||||
|
|
||||||
self.contentView.frame = CGRect(origin: CGPoint(), size: size)
|
self.contentView.frame = CGRect(origin: CGPoint(), size: params.size)
|
||||||
self.contentView.update(size: size, insets: insets)
|
self.contentView.update(size: params.size, insets: params.insets, state: self.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public final class NullActionClass: NSObject, CAAction {
|
|
||||||
@objc public func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public let nullAction = NullActionClass()
|
|
||||||
|
|
||||||
open class SimpleLayer: CALayer {
|
|
||||||
public var didEnterHierarchy: (() -> Void)?
|
|
||||||
public var didExitHierarchy: (() -> Void)?
|
|
||||||
public private(set) var isInHierarchy: Bool = false
|
|
||||||
|
|
||||||
override open func action(forKey event: String) -> CAAction? {
|
|
||||||
if event == kCAOnOrderIn {
|
|
||||||
self.isInHierarchy = true
|
|
||||||
self.didEnterHierarchy?()
|
|
||||||
} else if event == kCAOnOrderOut {
|
|
||||||
self.isInHierarchy = false
|
|
||||||
self.didExitHierarchy?()
|
|
||||||
}
|
|
||||||
return nullAction
|
|
||||||
}
|
|
||||||
|
|
||||||
override public init() {
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public init(layer: Any) {
|
|
||||||
super.init(layer: layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class SimpleShapeLayer: CAShapeLayer {
|
|
||||||
public var didEnterHierarchy: (() -> Void)?
|
|
||||||
public var didExitHierarchy: (() -> Void)?
|
|
||||||
public private(set) var isInHierarchy: Bool = false
|
|
||||||
|
|
||||||
override open func action(forKey event: String) -> CAAction? {
|
|
||||||
if event == kCAOnOrderIn {
|
|
||||||
self.isInHierarchy = true
|
|
||||||
self.didEnterHierarchy?()
|
|
||||||
} else if event == kCAOnOrderOut {
|
|
||||||
self.isInHierarchy = false
|
|
||||||
self.didExitHierarchy?()
|
|
||||||
}
|
|
||||||
return nullAction
|
|
||||||
}
|
|
||||||
|
|
||||||
override public init() {
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
override public init(layer: Any) {
|
|
||||||
super.init(layer: layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,6 +41,7 @@ import DeviceProximity
|
|||||||
import MediaEditor
|
import MediaEditor
|
||||||
import TelegramUIDeclareEncodables
|
import TelegramUIDeclareEncodables
|
||||||
import ContextMenuScreen
|
import ContextMenuScreen
|
||||||
|
import MetalEngine
|
||||||
|
|
||||||
#if canImport(AppCenter)
|
#if canImport(AppCenter)
|
||||||
import AppCenter
|
import AppCenter
|
||||||
@ -341,6 +342,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
self.window = window
|
self.window = window
|
||||||
self.nativeWindow = window
|
self.nativeWindow = window
|
||||||
|
|
||||||
|
hostView.containerView.layer.addSublayer(MetalEngine.shared.rootLayer)
|
||||||
|
|
||||||
if !UIDevice.current.isBatteryMonitoringEnabled {
|
if !UIDevice.current.isBatteryMonitoringEnabled {
|
||||||
UIDevice.current.isBatteryMonitoringEnabled = true
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user