mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
208 lines
10 KiB
Swift
208 lines
10 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import ComponentFlow
|
|
|
|
final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerViewProtocol {
|
|
private struct ContentParams: Equatable {
|
|
var size: CGSize
|
|
var image: UIImage?
|
|
var isSelected: Bool
|
|
var isDestructive: Bool
|
|
|
|
init(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool) {
|
|
self.size = size
|
|
self.image = image
|
|
self.isSelected = isSelected
|
|
self.isDestructive = isDestructive
|
|
}
|
|
}
|
|
|
|
let maskContents: UIView
|
|
|
|
override static var layerClass: AnyClass {
|
|
return MirroringLayer.self
|
|
}
|
|
|
|
var action: (() -> Void)?
|
|
|
|
private let contentView: UIImageView
|
|
private var currentContentViewIsSelected: Bool?
|
|
|
|
private let textView: TextView
|
|
|
|
private var contentParams: ContentParams?
|
|
|
|
override init(frame: CGRect) {
|
|
self.maskContents = UIView()
|
|
|
|
self.contentView = UIImageView()
|
|
self.textView = TextView()
|
|
|
|
super.init(frame: frame)
|
|
|
|
self.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
|
|
|
let size: CGFloat = 56.0
|
|
let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)))
|
|
self.maskContents.layer.contents = renderer.image { context in
|
|
UIGraphicsPushContext(context.cgContext)
|
|
context.cgContext.setFillColor(UIColor.white.cgColor)
|
|
context.cgContext.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size, height: size)))
|
|
UIGraphicsPopContext()
|
|
}.cgImage
|
|
|
|
(self.layer as? MirroringLayer)?.targetLayer = self.maskContents.layer
|
|
|
|
self.addSubview(self.contentView)
|
|
self.addSubview(self.textView)
|
|
|
|
self.internalHighligthedChanged = { [weak self] highlighted in
|
|
if let self, self.bounds.width > 0.0 {
|
|
let topScale: CGFloat = (self.bounds.width - 8.0) / self.bounds.width
|
|
let maxScale: CGFloat = (self.bounds.width + 2.0) / self.bounds.width
|
|
|
|
if highlighted {
|
|
self.layer.removeAnimation(forKey: "opacity")
|
|
self.layer.removeAnimation(forKey: "transform")
|
|
let transition = Transition(animation: .curve(duration: 0.15, curve: .easeInOut))
|
|
transition.setScale(layer: self.layer, scale: topScale)
|
|
} else {
|
|
let t = self.layer.presentation()?.transform ?? layer.transform
|
|
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
|
|
|
|
let transition = Transition(animation: .none)
|
|
transition.setScale(layer: self.layer, scale: 1.0)
|
|
|
|
self.layer.animateScale(from: currentScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] completed in
|
|
guard let self, completed else {
|
|
return
|
|
}
|
|
|
|
self.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
@objc private func buttonPressed() {
|
|
self.action?()
|
|
}
|
|
|
|
func update(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool, title: String, transition: Transition) {
|
|
let contentParams = ContentParams(size: size, image: image, isSelected: isSelected, isDestructive: isDestructive)
|
|
if self.contentParams != contentParams {
|
|
self.contentParams = contentParams
|
|
self.updateContent(contentParams: contentParams, transition: transition)
|
|
}
|
|
|
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
|
|
|
|
let textSize = self.textView.update(string: title, fontSize: 13.0, fontWeight: 0.0, color: .white, constrainedWidth: 100.0, transition: .immediate)
|
|
self.textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) * 0.5), y: size.height + 4.0), size: textSize)
|
|
}
|
|
|
|
private func updateContent(contentParams: ContentParams, transition: Transition) {
|
|
let image = generateImage(contentParams.size, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
|
|
if contentParams.isDestructive {
|
|
context.setFillColor(UIColor(rgb: 0xFF3B30).cgColor)
|
|
} else {
|
|
context.setFillColor(UIColor(white: 1.0, alpha: contentParams.isSelected ? 1.0 : 0.0).cgColor)
|
|
}
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
|
|
if let image = contentParams.image, let cgImage = image.cgImage {
|
|
let imageSize = CGSize(width: image.size.width * 0.8, height: image.size.height * 0.8)
|
|
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize)
|
|
|
|
context.saveGState()
|
|
context.translateBy(x: imageFrame.midX, y: imageFrame.midY)
|
|
context.scaleBy(x: 1.0, y: -1.0)
|
|
context.translateBy(x: -imageFrame.midX, y: -imageFrame.midY)
|
|
|
|
context.clip(to: imageFrame, mask: cgImage)
|
|
context.setBlendMode(contentParams.isSelected ? .copy : .normal)
|
|
context.setFillColor(contentParams.isSelected ? UIColor.clear.cgColor : UIColor(white: 1.0, alpha: 1.0).cgColor)
|
|
context.fill(imageFrame)
|
|
|
|
context.resetClip()
|
|
context.restoreGState()
|
|
}
|
|
})
|
|
|
|
if !transition.animation.isImmediate, let currentContentViewIsSelected = self.currentContentViewIsSelected, currentContentViewIsSelected != contentParams.isSelected, let previousImage = self.contentView.image, let image {
|
|
self.contentView.layer.mask = nil
|
|
let _ = previousImage
|
|
let _ = image
|
|
let _ = currentContentViewIsSelected
|
|
|
|
let previousContentView = UIImageView(image: previousImage)
|
|
previousContentView.frame = self.contentView.frame
|
|
self.addSubview(previousContentView)
|
|
|
|
let animationDuration = 0.16
|
|
let animationTimingFunction: String = CAMediaTimingFunctionName.linear.rawValue
|
|
|
|
if contentParams.isSelected {
|
|
let maskLayer = CAShapeLayer()
|
|
maskLayer.frame = self.contentView.bounds
|
|
maskLayer.path = UIBezierPath(ovalIn: self.contentView.bounds).cgPath
|
|
maskLayer.strokeColor = UIColor.black.cgColor
|
|
maskLayer.fillColor = nil
|
|
maskLayer.lineWidth = 1.0
|
|
self.contentView.layer.mask = maskLayer
|
|
maskLayer.animate(from: 0.0 as NSNumber, to: contentParams.size.width as NSNumber, keyPath: "lineWidth", timingFunction: animationTimingFunction, duration: animationDuration, removeOnCompletion: false, completion: { [weak self, weak maskLayer] _ in
|
|
guard let self, let maskLayer, self.contentView.layer.mask === maskLayer else {
|
|
return
|
|
}
|
|
self.contentView.layer.mask = nil
|
|
})
|
|
|
|
let previousMaskLayer = CAShapeLayer()
|
|
previousMaskLayer.frame = previousContentView.bounds
|
|
previousMaskLayer.path = UIBezierPath(ovalIn: previousContentView.bounds).cgPath
|
|
previousMaskLayer.strokeColor = nil
|
|
previousMaskLayer.fillColor = UIColor.black.cgColor
|
|
previousContentView.layer.mask = previousMaskLayer
|
|
previousMaskLayer.animate(from: 1.0 as NSNumber, to: 0.0001 as NSNumber, keyPath: "transform.scale", timingFunction: animationTimingFunction, duration: animationDuration, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
|
previousContentView?.removeFromSuperview()
|
|
})
|
|
} else {
|
|
let maskLayer = CAShapeLayer()
|
|
maskLayer.frame = self.contentView.bounds
|
|
maskLayer.path = UIBezierPath(ovalIn: self.contentView.bounds).cgPath
|
|
maskLayer.strokeColor = nil
|
|
maskLayer.fillColor = UIColor.black.cgColor
|
|
self.contentView.layer.mask = maskLayer
|
|
maskLayer.animate(from: 0.0001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", timingFunction: animationTimingFunction, duration: animationDuration, removeOnCompletion: false, completion: { [weak self, weak maskLayer] _ in
|
|
guard let self, let maskLayer, self.contentView.layer.mask === maskLayer else {
|
|
return
|
|
}
|
|
self.contentView.layer.mask = nil
|
|
})
|
|
|
|
let previousMaskLayer = CAShapeLayer()
|
|
previousMaskLayer.frame = previousContentView.bounds
|
|
previousMaskLayer.path = UIBezierPath(ovalIn: previousContentView.bounds).cgPath
|
|
previousMaskLayer.strokeColor = UIColor.black.cgColor
|
|
previousMaskLayer.fillColor = nil
|
|
previousMaskLayer.lineWidth = 1.0
|
|
previousContentView.layer.mask = previousMaskLayer
|
|
previousMaskLayer.animate(from: contentParams.size.width as NSNumber, to: 0.0 as NSNumber, keyPath: "lineWidth", timingFunction: animationTimingFunction, duration: animationDuration, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
|
previousContentView?.removeFromSuperview()
|
|
})
|
|
}
|
|
}
|
|
|
|
self.contentView.image = image
|
|
self.currentContentViewIsSelected = contentParams.isSelected
|
|
}
|
|
}
|