mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Camera improvements
This commit is contained in:
@@ -1,29 +1,177 @@
|
||||
import Foundation
|
||||
import Metal
|
||||
import MetalKit
|
||||
import ComponentFlow
|
||||
import Display
|
||||
|
||||
private final class PropertyAnimation<T: Interpolatable> {
|
||||
let from: T
|
||||
let to: T
|
||||
let animation: Transition.Animation
|
||||
let startTimestamp: Double
|
||||
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
|
||||
|
||||
init(fromValue: T, toValue: T, animation: Transition.Animation, startTimestamp: Double) {
|
||||
self.from = fromValue
|
||||
self.to = toValue
|
||||
self.animation = animation
|
||||
self.startTimestamp = startTimestamp
|
||||
self.interpolator = T.interpolator()
|
||||
}
|
||||
|
||||
func valueAt(_ t: CGFloat) -> Interpolatable {
|
||||
if t <= 0.0 {
|
||||
return self.from
|
||||
} else if t >= 1.0 {
|
||||
return self.to
|
||||
} else {
|
||||
return self.interpolator(self.from, self.to, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class AnimatableProperty<T: Interpolatable> {
|
||||
var presentationValue: T
|
||||
var value: T
|
||||
private var animation: PropertyAnimation<T>?
|
||||
|
||||
init(value: T) {
|
||||
self.value = value
|
||||
self.presentationValue = value
|
||||
}
|
||||
|
||||
func update(value: T, transition: Transition = .immediate) {
|
||||
if case .none = transition.animation {
|
||||
if let animation = self.animation, case let .curve(duration, curve) = animation.animation {
|
||||
self.value = value
|
||||
let elapsed = duration - (CACurrentMediaTime() - animation.startTimestamp)
|
||||
if elapsed < 0.1 {
|
||||
self.presentationValue = value
|
||||
self.animation = nil
|
||||
} else {
|
||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: .curve(duration: elapsed, curve: curve), startTimestamp: CACurrentMediaTime())
|
||||
}
|
||||
} else {
|
||||
self.value = value
|
||||
self.presentationValue = value
|
||||
self.animation = nil
|
||||
}
|
||||
} else {
|
||||
self.value = value
|
||||
self.animation = PropertyAnimation(fromValue: self.presentationValue, toValue: value, animation: transition.animation, startTimestamp: CACurrentMediaTime())
|
||||
}
|
||||
}
|
||||
|
||||
func tick(timestamp: Double) -> Bool {
|
||||
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
||||
return false
|
||||
}
|
||||
|
||||
let timeFromStart = timestamp - animation.startTimestamp
|
||||
var t = max(0.0, timeFromStart / duration)
|
||||
switch curve {
|
||||
case .easeInOut:
|
||||
t = listViewAnimationCurveEaseInOut(t)
|
||||
case .spring:
|
||||
t = listViewAnimationCurveSystem(t)
|
||||
case let .custom(x1, y1, x2, y2):
|
||||
t = bezierPoint(CGFloat(x1), CGFloat(y1), CGFloat(x2), CGFloat(y2), t)
|
||||
}
|
||||
self.presentationValue = animation.valueAt(t) as! T
|
||||
|
||||
return timeFromStart <= duration
|
||||
}
|
||||
}
|
||||
|
||||
final class ShutterBlobView: MTKView, MTKViewDelegate {
|
||||
public func draw(in view: MTKView) {
|
||||
enum BlobState {
|
||||
case generic
|
||||
case video
|
||||
case transientToLock
|
||||
case lock
|
||||
case transientToFlip
|
||||
case stopVideo
|
||||
|
||||
var primarySize: CGFloat {
|
||||
switch self {
|
||||
case .generic, .video, .transientToFlip:
|
||||
return 0.63
|
||||
case .transientToLock, .lock, .stopVideo:
|
||||
return 0.275
|
||||
}
|
||||
}
|
||||
|
||||
var primaryRedness: CGFloat {
|
||||
switch self {
|
||||
case .generic:
|
||||
return 0.0
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
|
||||
var primaryCornerRadius: CGFloat {
|
||||
switch self {
|
||||
case .generic, .video, .transientToFlip:
|
||||
return 0.63
|
||||
case .transientToLock, .lock, .stopVideo:
|
||||
return 0.185
|
||||
}
|
||||
}
|
||||
|
||||
var secondarySize: CGFloat {
|
||||
switch self {
|
||||
case .generic, .video, .transientToFlip, .transientToLock:
|
||||
return 0.335
|
||||
case .lock:
|
||||
return 0.5
|
||||
case .stopVideo:
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
var secondaryRedness: CGFloat {
|
||||
switch self {
|
||||
case .generic, .lock, .transientToLock, .transientToFlip:
|
||||
return 0.0
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let commandQueue: MTLCommandQueue
|
||||
private let drawPassthroughPipelineState: MTLRenderPipelineState
|
||||
|
||||
private var displayLink: CADisplayLink?
|
||||
|
||||
private var viewportDimensions = CGSize(width: 1, height: 1)
|
||||
|
||||
private var startTimestamp = CACurrentMediaTime()
|
||||
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||
|
||||
private var primarySize = AnimatableProperty<CGFloat>(value: 0.63)
|
||||
private var primaryOffset = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryRedness = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var primaryCornerRadius = AnimatableProperty<CGFloat>(value: 0.63)
|
||||
|
||||
private var secondarySize = AnimatableProperty<CGFloat>(value: 0.34)
|
||||
private var secondaryOffset = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
private var secondaryRedness = AnimatableProperty<CGFloat>(value: 0.0)
|
||||
|
||||
private(set) var state: BlobState = .generic
|
||||
|
||||
public init?(test: Bool) {
|
||||
let mainBundle = Bundle(for: ShutterBlobView.self)
|
||||
|
||||
guard let path = mainBundle.path(forResource: "CameraScreenBundle", 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: mainBundle) else {
|
||||
guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -62,25 +210,11 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
||||
self.backgroundColor = .clear
|
||||
|
||||
self.framebufferOnly = true
|
||||
|
||||
class DisplayLinkProxy: NSObject {
|
||||
weak var target: ShutterBlobView?
|
||||
init(target: ShutterBlobView) {
|
||||
self.target = target
|
||||
}
|
||||
|
||||
@objc func displayLinkEvent() {
|
||||
self.target?.displayLinkEvent()
|
||||
}
|
||||
|
||||
self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
||||
self?.tick()
|
||||
}
|
||||
|
||||
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
|
||||
if #available(iOS 15.0, *) {
|
||||
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps)
|
||||
}
|
||||
self.displayLink?.add(to: .main, forMode: .common)
|
||||
self.displayLink?.isPaused = false
|
||||
self.displayLink?.isPaused = true
|
||||
|
||||
self.isPaused = true
|
||||
}
|
||||
@@ -96,8 +230,63 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
||||
deinit {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
|
||||
func updateState(_ state: BlobState, transition: Transition = .immediate) {
|
||||
guard self.state != state else {
|
||||
return
|
||||
}
|
||||
self.state = state
|
||||
|
||||
@objc private func displayLinkEvent() {
|
||||
self.primarySize.update(value: state.primarySize, transition: transition)
|
||||
self.primaryRedness.update(value: state.primaryRedness, transition: transition)
|
||||
self.primaryCornerRadius.update(value: state.primaryCornerRadius, transition: transition)
|
||||
self.secondarySize.update(value: state.secondarySize, transition: transition)
|
||||
self.secondaryRedness.update(value: state.secondaryRedness, transition: transition)
|
||||
|
||||
self.tick()
|
||||
}
|
||||
|
||||
func updatePrimaryOffset(_ offset: CGFloat, transition: Transition = .immediate) {
|
||||
guard self.frame.height > 0.0 else {
|
||||
return
|
||||
}
|
||||
let mappedOffset = offset / self.frame.height * 2.0
|
||||
self.primaryOffset.update(value: mappedOffset, transition: transition)
|
||||
self.tick()
|
||||
}
|
||||
|
||||
func updateSecondaryOffset(_ offset: CGFloat, transition: Transition = .immediate) {
|
||||
guard self.frame.height > 0.0 else {
|
||||
return
|
||||
}
|
||||
let mappedOffset = offset / self.frame.height * 2.0
|
||||
self.secondaryOffset.update(value: mappedOffset, transition: transition)
|
||||
self.tick()
|
||||
}
|
||||
|
||||
private func updateAnimations() {
|
||||
let properties = [
|
||||
self.primarySize,
|
||||
self.primaryOffset,
|
||||
self.primaryRedness,
|
||||
self.primaryCornerRadius,
|
||||
self.secondarySize,
|
||||
self.secondaryOffset,
|
||||
self.secondaryRedness
|
||||
]
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
var hasAnimations = false
|
||||
for property in properties {
|
||||
if property.tick(timestamp: timestamp) {
|
||||
hasAnimations = true
|
||||
}
|
||||
}
|
||||
self.displayLink?.isPaused = !hasAnimations
|
||||
}
|
||||
|
||||
private func tick() {
|
||||
self.updateAnimations()
|
||||
self.draw()
|
||||
}
|
||||
|
||||
@@ -138,9 +327,21 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
||||
|
||||
var resolution = simd_uint2(UInt32(viewportDimensions.width), UInt32(viewportDimensions.height))
|
||||
renderEncoder.setFragmentBytes(&resolution, length: MemoryLayout<simd_uint2>.size * 2, index: 0)
|
||||
|
||||
var primaryParameters = simd_float4(
|
||||
Float(self.primarySize.presentationValue),
|
||||
Float(self.primaryOffset.presentationValue),
|
||||
Float(self.primaryRedness.presentationValue),
|
||||
Float(self.primaryCornerRadius.presentationValue)
|
||||
)
|
||||
renderEncoder.setFragmentBytes(&primaryParameters, length: MemoryLayout<simd_float4>.size, index: 1)
|
||||
|
||||
var time = Float(CACurrentMediaTime() - self.startTimestamp) * 0.5
|
||||
renderEncoder.setFragmentBytes(&time, length: 4, index: 1)
|
||||
var secondaryParameters = simd_float3(
|
||||
Float(self.secondarySize.presentationValue),
|
||||
Float(self.secondaryOffset.presentationValue),
|
||||
Float(self.secondaryRedness.presentationValue)
|
||||
)
|
||||
renderEncoder.setFragmentBytes(&secondaryParameters, length: MemoryLayout<simd_float3>.size, index: 2)
|
||||
|
||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
||||
|
||||
@@ -149,4 +350,14 @@ final class ShutterBlobView: MTKView, MTKViewDelegate {
|
||||
commandBuffer.present(drawable)
|
||||
commandBuffer.commit()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.tick()
|
||||
}
|
||||
|
||||
func draw(in view: MTKView) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user