mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
275 lines
8.0 KiB
Swift
275 lines
8.0 KiB
Swift
import Foundation
|
|
import UIKit
|
|
|
|
public final class SharedDisplayLinkDriver {
|
|
public static let shared = SharedDisplayLinkDriver()
|
|
|
|
public final class Link {
|
|
private let driver: SharedDisplayLinkDriver
|
|
public let needsHighestFramerate: Bool
|
|
let update: () -> Void
|
|
var isValid: Bool = true
|
|
public var isPaused: Bool = false {
|
|
didSet {
|
|
if self.isPaused != oldValue {
|
|
driver.requestUpdate()
|
|
}
|
|
}
|
|
}
|
|
|
|
init(driver: SharedDisplayLinkDriver, needsHighestFramerate: Bool, update: @escaping () -> Void) {
|
|
self.driver = driver
|
|
self.needsHighestFramerate = needsHighestFramerate
|
|
self.update = update
|
|
}
|
|
|
|
public func invalidate() {
|
|
self.isValid = false
|
|
}
|
|
}
|
|
|
|
private final class RequestContext {
|
|
weak var link: Link?
|
|
|
|
init(link: Link) {
|
|
self.link = link
|
|
}
|
|
}
|
|
|
|
private var displayLink: CADisplayLink?
|
|
private var hasRequestedHighestFramerate: Bool = false
|
|
private var requests: [RequestContext] = []
|
|
|
|
private var isInForeground: Bool = false
|
|
|
|
private init() {
|
|
let _ = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.isInForeground = true
|
|
self.update()
|
|
})
|
|
let _ = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.isInForeground = false
|
|
self.update()
|
|
})
|
|
|
|
switch UIApplication.shared.applicationState {
|
|
case .active:
|
|
self.isInForeground = true
|
|
default:
|
|
self.isInForeground = false
|
|
}
|
|
|
|
self.update()
|
|
}
|
|
|
|
private func requestUpdate() {
|
|
self.update()
|
|
}
|
|
|
|
private func update() {
|
|
var hasActiveItems = false
|
|
var needHighestFramerate = false
|
|
for request in self.requests {
|
|
if let link = request.link {
|
|
needHighestFramerate = link.needsHighestFramerate
|
|
if link.isValid && !link.isPaused {
|
|
hasActiveItems = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.isInForeground && hasActiveItems {
|
|
let displayLink: CADisplayLink
|
|
if let current = self.displayLink {
|
|
displayLink = current
|
|
} else {
|
|
displayLink = CADisplayLink(target: self, selector: #selector(self.displayLinkEvent))
|
|
self.displayLink = displayLink
|
|
displayLink.add(to: .main, forMode: .common)
|
|
}
|
|
if #available(iOS 15.0, *) {
|
|
let frameRateRange: CAFrameRateRange
|
|
if needHighestFramerate {
|
|
frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
|
|
} else {
|
|
frameRateRange = .default
|
|
}
|
|
if displayLink.preferredFrameRateRange != frameRateRange {
|
|
displayLink.preferredFrameRateRange = frameRateRange
|
|
}
|
|
}
|
|
displayLink.isPaused = false
|
|
} else {
|
|
if let displayLink = self.displayLink {
|
|
self.displayLink = nil
|
|
displayLink.invalidate()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func displayLinkEvent() {
|
|
var removeIndices: [Int]?
|
|
for i in 0 ..< self.requests.count {
|
|
if let link = self.requests[i].link, link.isValid {
|
|
link.update()
|
|
} else {
|
|
if removeIndices == nil {
|
|
removeIndices = [i]
|
|
} else {
|
|
removeIndices?.append(i)
|
|
}
|
|
}
|
|
}
|
|
if let removeIndices = removeIndices {
|
|
for index in removeIndices.reversed() {
|
|
self.requests.remove(at: index)
|
|
}
|
|
|
|
if self.requests.isEmpty {
|
|
self.update()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func add(needsHighestFramerate: Bool = true, _ update: @escaping () -> Void) -> Link {
|
|
let link = Link(driver: self, needsHighestFramerate: needsHighestFramerate, update: update)
|
|
self.requests.append(RequestContext(link: link))
|
|
|
|
self.update()
|
|
|
|
return link
|
|
}
|
|
}
|
|
|
|
public final class DisplayLinkTarget: NSObject {
|
|
private let f: () -> Void
|
|
|
|
public init(_ f: @escaping () -> Void) {
|
|
self.f = f
|
|
}
|
|
|
|
@objc public func event() {
|
|
self.f()
|
|
}
|
|
}
|
|
|
|
public final class DisplayLinkAnimator {
|
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
|
private let duration: Double
|
|
private let fromValue: CGFloat
|
|
private let toValue: CGFloat
|
|
private let startTime: Double
|
|
private let update: (CGFloat) -> Void
|
|
private let completion: () -> Void
|
|
private var completed = false
|
|
|
|
public init(duration: Double, from fromValue: CGFloat, to toValue: CGFloat, update: @escaping (CGFloat) -> Void, completion: @escaping () -> Void) {
|
|
self.duration = duration
|
|
self.fromValue = fromValue
|
|
self.toValue = toValue
|
|
self.update = update
|
|
self.completion = completion
|
|
|
|
self.startTime = CACurrentMediaTime()
|
|
|
|
self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
|
self?.tick()
|
|
}
|
|
self.displayLink?.isPaused = false
|
|
}
|
|
|
|
deinit {
|
|
self.displayLink?.isPaused = true
|
|
self.displayLink?.invalidate()
|
|
}
|
|
|
|
public func invalidate() {
|
|
self.displayLink?.isPaused = true
|
|
self.displayLink?.invalidate()
|
|
}
|
|
|
|
@objc private func tick() {
|
|
if self.completed {
|
|
return
|
|
}
|
|
let timestamp = CACurrentMediaTime()
|
|
var t = (timestamp - self.startTime) / self.duration
|
|
t = max(0.0, t)
|
|
t = min(1.0, t)
|
|
self.update(self.fromValue * CGFloat(1 - t) + self.toValue * CGFloat(t))
|
|
if abs(t - 1.0) < Double.ulpOfOne {
|
|
self.completed = true
|
|
self.displayLink?.isPaused = true
|
|
self.completion()
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class ConstantDisplayLinkAnimator {
|
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
|
private let update: () -> Void
|
|
private var completed = false
|
|
|
|
public var frameInterval: Int = 1 {
|
|
didSet {
|
|
self.updateDisplayLink()
|
|
}
|
|
}
|
|
|
|
private func updateDisplayLink() {
|
|
guard let displayLink = self.displayLink else {
|
|
return
|
|
}
|
|
let _ = displayLink
|
|
}
|
|
|
|
public var isPaused: Bool = true {
|
|
didSet {
|
|
if self.isPaused != oldValue {
|
|
if !self.isPaused && self.displayLink == nil {
|
|
let displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
|
self?.tick()
|
|
}
|
|
self.displayLink = displayLink
|
|
self.updateDisplayLink()
|
|
}
|
|
|
|
self.displayLink?.isPaused = self.isPaused
|
|
}
|
|
}
|
|
}
|
|
|
|
public init(update: @escaping () -> Void) {
|
|
self.update = update
|
|
}
|
|
|
|
deinit {
|
|
if let displayLink = self.displayLink {
|
|
displayLink.isPaused = true
|
|
displayLink.invalidate()
|
|
}
|
|
}
|
|
|
|
public func invalidate() {
|
|
if let displayLink = self.displayLink {
|
|
displayLink.isPaused = true
|
|
displayLink.invalidate()
|
|
}
|
|
}
|
|
|
|
@objc private func tick() {
|
|
if self.completed {
|
|
return
|
|
}
|
|
self.update()
|
|
}
|
|
}
|
|
|