Files
TelegramSwift/packages/TGUIKit/Sources/ContainableController.swift
2024-05-31 10:24:55 +04:00

330 lines
12 KiB
Swift

//
// ContainableController.swift
// TGUIKit
//
// Created by keepcoder on 23/10/2016.
// Copyright © 2016 Telegram. All rights reserved.
//
import Cocoa
public enum ContainedViewLayoutTransitionCurve {
case linear
case easeInOut
case easeOut
case spring
case legacy
}
public let listViewAnimationCurveSystem: (CGFloat) -> CGFloat = { t in
return bezierPoint(0.23, 1.0, 0.32, 1.0, t)
}
public let listViewAnimationCurveEaseInOut: (CGFloat) -> CGFloat = { t in
return bezierPoint(0.42, 0.0, 0.58, 1.0, t)
}
public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in
return t
}
public extension ContainedViewLayoutTransitionCurve {
var timingFunction: CAMediaTimingFunctionName {
switch self {
case .easeInOut:
return CAMediaTimingFunctionName.easeInEaseOut
case .spring:
return CAMediaTimingFunctionName.spring
case .legacy:
return CAMediaTimingFunctionName.easeInEaseOut
case .linear:
return CAMediaTimingFunctionName.linear
case .easeOut:
return CAMediaTimingFunctionName.easeOut
}
}
func solve(at offset: CGFloat) -> CGFloat {
switch self {
case .easeInOut:
return listViewAnimationCurveEaseInOut(offset)
case .spring:
return listViewAnimationCurveSystem(offset)
case .legacy:
return listViewAnimationCurveEaseInOut(offset)
case .easeOut:
return listViewAnimationCurveEaseInOut(offset)
case .linear:
return listViewAnimationCurveLinear(offset)
}
}
}
public enum ContainedViewLayoutTransition {
case immediate
case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve)
public var isAnimated: Bool {
switch self {
case .immediate:
return false
case .animated:
return true
}
}
public var duration: Double {
switch self {
case .immediate:
return 0
case let .animated(duration, _):
return duration
}
}
public var timingFunction:CAMediaTimingFunctionName {
switch self {
case .immediate:
return .linear
case let .animated(_, curve):
return curve.timingFunction
}
}
}
public extension ContainedViewLayoutTransition {
func updateFrame(view: NSView, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
view.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
var curve = curve
if view is NSVisualEffectView {
curve = .legacy
}
switch curve {
case .legacy:
NSAnimationContext.runAnimationGroup({ ctx in
ctx.duration = duration
ctx.timingFunction = .init(name: curve.timingFunction)
view.animator().frame = frame
}, completionHandler: {
completion?(true)
})
default:
var ignoreSize: Bool = false
if let view = view as? TableView {
view.change(size: frame.size, animated: true, duration: duration, timingFunction: curve.timingFunction, completion: { completed in
completion?(completed)
})
}
var notifyOnOrigin: Bool = false
var notifyOnSize: Bool = false
if frame.size != view.frame.size {
notifyOnSize = true
} else if frame.origin != view.frame.origin {
notifyOnOrigin = true
} else {
completion?(true)
}
view._change(size: frame.size, animated: true, duration: duration, timingFunction: curve.timingFunction, completion: { completed in
if notifyOnSize {
completion?(completed)
}
})
view._change(pos: frame.origin, animated: true, duration: duration, timingFunction: curve.timingFunction, completion: { completed in
if notifyOnOrigin {
completion?(completed)
}
})
}
}
}
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil, save: Bool = true, updatePosition: Bool = false) {
switch self {
case .immediate:
layer.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, _):
func animateSize(_ layer: CALayer) -> Void {
var presentBounds:NSRect = layer.bounds
let presentation = layer.presentation()
if let presentation = presentation, layer.animation(forKey:"bounds") != nil {
presentBounds.size.width = NSWidth(presentation.bounds)
presentBounds.size.height = NSHeight(presentation.bounds)
}
layer.animateBounds(from: presentBounds, to: frame.size.bounds, duration: duration, timingFunction: timingFunction, completion: completion)
}
func animatePos(_ layer: CALayer) -> Void {
var presentRect:NSPoint = layer.position
let presentation = layer.presentation()
if let presentation = presentation, layer.animation(forKey:"position") != nil {
presentRect.x = presentation.position.x
presentRect.y = presentation.position.y
}
layer.animatePosition(from: presentRect, to: frame.origin, duration: duration, timingFunction: timingFunction, completion: completion)
}
if layer.frame.origin != frame.origin {
animatePos(layer)
}
if layer.frame.size != frame.size {
animateSize(layer)
}
if save {
if updatePosition {
layer.frame = frame.size.bounds
layer.position = frame.origin
} else {
layer.frame = frame
}
}
}
}
func updateTransformScale(layer: CALayer, scale: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: scale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousScale: CGFloat
if beginWithCurrentState, let presentation = layer.presentation() {
let t = presentation.transform
previousScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
} else {
previousScale = currentScale
}
layer.animateScaleSpring(from: previousScale, to: scale, duration: duration, bounce: false, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateAlpha(view: NSView, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
view.layer?.opacity = Float(alpha)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAlpha = view.layer?.presentation()?.opacity ?? view.layer?.opacity ?? 1
view.layer?.opacity = Float(alpha)
view.layer?.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
layer.opacity = Float(alpha)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAlpha = layer.presentation()?.opacity ?? layer.opacity
layer.opacity = Float(alpha)
layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
// func updatePosition(layer: CALayer, position: NSPoint, completion: ((Bool) -> Void)? = nil) {
// switch self {
// case .immediate:
// layer.position = position
// if let completion = completion {
// completion(true)
// }
// case let .animated(duration, curve):
// let previousAlpha = layer.presentation()?.opacity ?? layer.opacity
// layer.position = position
// layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in
// if let completion = completion {
// completion(result)
// }
// })
// }
// }
func animatePositionWithKeyframes(layer: CALayer, keyframes: [CGPoint], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
completion?(true)
case let .animated(duration, curve):
layer.animateKeyframes(values: keyframes.map(NSValue.init(point:)), duration: duration, keyPath: "position", timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
}
}
func setShapeLayerStrokeEnd(layer: CAShapeLayer, strokeEnd: CGFloat, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
layer.strokeEnd = strokeEnd
completion?(true)
case let .animated(duration, curve):
let previousStrokeEnd = layer.strokeEnd
layer.strokeEnd = strokeEnd
layer.animate(
from: previousStrokeEnd as NSNumber,
to: strokeEnd as NSNumber,
keyPath: "strokeEnd",
timingFunction: curve.timingFunction,
duration: duration,
delay: 0.0,
removeOnCompletion: true,
additive: false,
completion: completion
)
}
}
}
public protocol ContainableController: class {
var view: View! { get }
func containerLayoutUpdated(transition: ContainedViewLayoutTransition)
}