mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Merge commit '5eb0d74f40751e6c44488aedbc13091a7fc23d25' into beta
This commit is contained in:
@@ -1038,7 +1038,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
if let current = self.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
dustNode.alpha = self.spoilersRevealed ? 0.0 : 1.0
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
textInputNode.textView.addSubview(dustNode.view)
|
||||
@@ -1298,7 +1298,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
private func updateOneLineSpoiler() {
|
||||
if let textLayout = self.oneLineNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty {
|
||||
if self.oneLineDustNode == nil {
|
||||
let oneLineDustNode = InvisibleInkDustNode(textNode: nil)
|
||||
let oneLineDustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
self.oneLineDustNode = oneLineDustNode
|
||||
self.oneLineNode.textNode.supernode?.insertSubnode(oneLineDustNode, aboveSubnode: self.oneLineNode.textNode)
|
||||
|
||||
|
||||
+1
-1
@@ -549,7 +549,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
if let textLayout = self.currentOptionNode.cachedLayout, !textLayout.spoilers.isEmpty {
|
||||
if self.dustNode == nil {
|
||||
let dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
let dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: true)
|
||||
self.dustNode = dustNode
|
||||
self.currentOptionNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.currentOptionNode)
|
||||
|
||||
|
||||
@@ -3124,7 +3124,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
strongSelf.dustNode = dustNode
|
||||
|
||||
|
||||
@@ -813,7 +813,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.spoilerTextNode = spoilerTextNode
|
||||
self.textNode.supernode?.insertSubnode(spoilerTextNode, aboveSubnode: self.textNode)
|
||||
|
||||
let dustNode = InvisibleInkDustNode(textNode: spoilerTextNode)
|
||||
let dustNode = InvisibleInkDustNode(textNode: spoilerTextNode, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
self.dustNode = dustNode
|
||||
spoilerTextNode.supernode?.insertSubnode(dustNode, aboveSubnode: spoilerTextNode)
|
||||
|
||||
|
||||
@@ -2639,13 +2639,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
let theme = strongSelf.presentationData.theme
|
||||
for (text, _, rate) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
|
||||
let isSelected = abs(status.baseRate - rate) < 0.01
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: sliderValuePromise.get()
|
||||
|> map { value in
|
||||
if isSelected && value == nil {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ import Display
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
struct ArbitraryRandomNumberGenerator : RandomNumberGenerator {
|
||||
init(seed: Int) { srand48(seed) }
|
||||
func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) }
|
||||
}
|
||||
|
||||
func createEmitterBehavior(type: String) -> NSObject {
|
||||
let selector = ["behaviorWith", "Type:"].joined(separator: "")
|
||||
let behaviorClass = NSClassFromString(["CA", "Emitter", "Behavior"].joined(separator: "")) as! NSObject.Type
|
||||
@@ -45,6 +50,7 @@ func generateMaskImage(size originalSize: CGSize, position: CGPoint, inverse: Bo
|
||||
public class InvisibleInkDustNode: ASDisplayNode {
|
||||
private var currentParams: (size: CGSize, color: UIColor, textColor: UIColor, rects: [CGRect], wordRects: [CGRect])?
|
||||
private var animColor: CGColor?
|
||||
private let enableAnimations: Bool
|
||||
|
||||
private weak var textNode: TextNode?
|
||||
private let textMaskNode: ASDisplayNode
|
||||
@@ -56,12 +62,16 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
private let emitterMaskNode: ASDisplayNode
|
||||
private let emitterSpotNode: ASImageNode
|
||||
private let emitterMaskFillNode: ASDisplayNode
|
||||
|
||||
private var staticNode: ASImageNode?
|
||||
private var staticParams: (size: CGSize, color: UIColor, rects: [CGRect])?
|
||||
|
||||
public var isRevealed = false
|
||||
private var isExploding = false
|
||||
|
||||
public init(textNode: TextNode?) {
|
||||
public init(textNode: TextNode?, enableAnimations: Bool) {
|
||||
self.textNode = textNode
|
||||
self.enableAnimations = enableAnimations
|
||||
|
||||
self.emitterNode = ASDisplayNode()
|
||||
self.emitterNode.isUserInteractionEnabled = false
|
||||
@@ -94,47 +104,53 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
|
||||
emitter.contentsScale = 1.8
|
||||
emitter.emissionRange = .pi * 2.0
|
||||
emitter.lifetime = 1.0
|
||||
emitter.scale = 0.5
|
||||
emitter.velocityRange = 20.0
|
||||
emitter.name = "dustCell"
|
||||
emitter.alphaRange = 1.0
|
||||
emitter.setValue("point", forKey: "particleType")
|
||||
emitter.setValue(3.0, forKey: "mass")
|
||||
emitter.setValue(2.0, forKey: "massRange")
|
||||
self.emitter = emitter
|
||||
|
||||
let fingerAttractor = createEmitterBehavior(type: "simpleAttractor")
|
||||
fingerAttractor.setValue("fingerAttractor", forKey: "name")
|
||||
|
||||
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
|
||||
alphaBehavior.setValue([0.0, 0.0, 1.0, 0.0, -1.0], forKey: "values")
|
||||
alphaBehavior.setValue(true, forKey: "additive")
|
||||
|
||||
let behaviors = [fingerAttractor, alphaBehavior]
|
||||
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
emitterLayer.masksToBounds = true
|
||||
emitterLayer.allowsGroupOpacity = true
|
||||
emitterLayer.lifetime = 1
|
||||
emitterLayer.emitterCells = [emitter]
|
||||
emitterLayer.emitterPosition = CGPoint(x: 0, y: 0)
|
||||
emitterLayer.seed = arc4random()
|
||||
emitterLayer.emitterSize = CGSize(width: 1, height: 1)
|
||||
emitterLayer.emitterShape = CAEmitterLayerEmitterShape(rawValue: "rectangles")
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
|
||||
emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
|
||||
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
|
||||
self.emitterLayer = emitterLayer
|
||||
|
||||
self.emitterNode.layer.addSublayer(emitterLayer)
|
||||
if self.enableAnimations {
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
|
||||
emitter.contentsScale = 1.8
|
||||
emitter.emissionRange = .pi * 2.0
|
||||
emitter.lifetime = 1.0
|
||||
emitter.scale = 0.5
|
||||
emitter.velocityRange = 20.0
|
||||
emitter.name = "dustCell"
|
||||
emitter.alphaRange = 1.0
|
||||
emitter.setValue("point", forKey: "particleType")
|
||||
emitter.setValue(3.0, forKey: "mass")
|
||||
emitter.setValue(2.0, forKey: "massRange")
|
||||
self.emitter = emitter
|
||||
|
||||
let fingerAttractor = createEmitterBehavior(type: "simpleAttractor")
|
||||
fingerAttractor.setValue("fingerAttractor", forKey: "name")
|
||||
|
||||
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
|
||||
alphaBehavior.setValue([0.0, 0.0, 1.0, 0.0, -1.0], forKey: "values")
|
||||
alphaBehavior.setValue(true, forKey: "additive")
|
||||
|
||||
let behaviors = [fingerAttractor, alphaBehavior]
|
||||
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
emitterLayer.masksToBounds = true
|
||||
emitterLayer.allowsGroupOpacity = true
|
||||
emitterLayer.lifetime = 1
|
||||
emitterLayer.emitterCells = [emitter]
|
||||
emitterLayer.emitterPosition = CGPoint(x: 0, y: 0)
|
||||
emitterLayer.seed = arc4random()
|
||||
emitterLayer.emitterSize = CGSize(width: 1, height: 1)
|
||||
emitterLayer.emitterShape = CAEmitterLayerEmitterShape(rawValue: "rectangles")
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
|
||||
emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
|
||||
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
|
||||
self.emitterLayer = emitterLayer
|
||||
|
||||
self.emitterNode.layer.addSublayer(emitterLayer)
|
||||
} else {
|
||||
let staticNode = ASImageNode()
|
||||
self.staticNode = staticNode
|
||||
self.addSubnode(staticNode)
|
||||
}
|
||||
|
||||
self.updateEmitter()
|
||||
|
||||
@@ -170,102 +186,167 @@ public class InvisibleInkDustNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
self.isRevealed = true
|
||||
self.isExploding = true
|
||||
|
||||
let position = gestureRecognizer.location(in: self.view)
|
||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||
|
||||
let maskSize = self.emitterNode.frame.size
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
let textMaskImage = generateMaskImage(size: maskSize, position: position, inverse: false)
|
||||
let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true)
|
||||
if self.enableAnimations {
|
||||
self.isExploding = true
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.textSpotNode.image = textMaskImage
|
||||
self.emitterSpotNode.image = emitterMaskImage
|
||||
let position = gestureRecognizer.location(in: self.view)
|
||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||
|
||||
let maskSize = self.emitterNode.frame.size
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
let textMaskImage = generateMaskImage(size: maskSize, position: position, inverse: false)
|
||||
let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true)
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.textSpotNode.image = textMaskImage
|
||||
self.emitterSpotNode.image = emitterMaskImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
||||
|
||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
||||
textNode.alpha = 1.0
|
||||
|
||||
textNode.view.mask = self.textMaskNode.view
|
||||
self.textSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0
|
||||
let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0
|
||||
let maxFactor = max(abs(xFactor), abs(yFactor))
|
||||
|
||||
var scaleAddition = maxFactor * 4.0
|
||||
var durationAddition = -maxFactor * 0.2
|
||||
if self.emitterNode.frame.height > 0.0, self.emitterNode.frame.width / self.emitterNode.frame.height < 0.7 {
|
||||
scaleAddition *= 5.0
|
||||
durationAddition *= 2.0
|
||||
}
|
||||
|
||||
self.textSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.textSpotNode.position = position
|
||||
self.textSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { _ in
|
||||
textNode.view.mask = nil
|
||||
})
|
||||
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
|
||||
self.emitterNode.view.mask = self.emitterMaskNode.view
|
||||
self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.emitterSpotNode.position = position
|
||||
self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.alpha = 0.0
|
||||
self?.emitterNode.view.mask = nil
|
||||
|
||||
self?.emitter?.color = textColor.cgColor
|
||||
})
|
||||
self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
|
||||
self.isExploding = false
|
||||
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.textSpotNode.layer.removeAllAnimations()
|
||||
|
||||
self.emitterSpotNode.layer.removeAllAnimations()
|
||||
self.emitterMaskFillNode.layer.removeAllAnimations()
|
||||
}
|
||||
} else {
|
||||
textNode.alpha = 1.0
|
||||
textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
|
||||
textNode.view.mask = self.textMaskNode.view
|
||||
self.textSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0
|
||||
let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0
|
||||
let maxFactor = max(abs(xFactor), abs(yFactor))
|
||||
|
||||
var scaleAddition = maxFactor * 4.0
|
||||
var durationAddition = -maxFactor * 0.2
|
||||
if self.emitterNode.frame.height > 0.0, self.emitterNode.frame.width / self.emitterNode.frame.height < 0.7 {
|
||||
scaleAddition *= 5.0
|
||||
durationAddition *= 2.0
|
||||
}
|
||||
|
||||
self.textSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.textSpotNode.position = position
|
||||
self.textSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { _ in
|
||||
textNode.view.mask = nil
|
||||
})
|
||||
self.textSpotNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
|
||||
self.emitterNode.view.mask = self.emitterMaskNode.view
|
||||
self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.emitterSpotNode.position = position
|
||||
self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.55 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.alpha = 0.0
|
||||
self?.emitterNode.view.mask = nil
|
||||
|
||||
self?.emitter?.color = textColor.cgColor
|
||||
})
|
||||
self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
|
||||
self.isExploding = false
|
||||
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.textSpotNode.layer.removeAllAnimations()
|
||||
|
||||
self.emitterSpotNode.layer.removeAllAnimations()
|
||||
self.emitterMaskFillNode.layer.removeAllAnimations()
|
||||
self.staticNode?.alpha = 0.0
|
||||
self.staticNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateEmitter() {
|
||||
guard let (size, color, _, _, wordRects) = self.currentParams else {
|
||||
guard let (size, color, _, lineRects, wordRects) = self.currentParams else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emitter?.color = self.animColor ?? color.cgColor
|
||||
self.emitterLayer?.setValue(wordRects, forKey: "emitterRects")
|
||||
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let radius = max(size.width, size.height)
|
||||
self.emitterLayer?.setValue(max(size.width, size.height), forKeyPath: "emitterBehaviors.fingerAttractor.radius")
|
||||
self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff")
|
||||
|
||||
var square: Float = 0.0
|
||||
for rect in wordRects {
|
||||
square += Float(rect.width * rect.height)
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.emitter?.birthRate = min(100000, square * 0.35)
|
||||
if self.enableAnimations {
|
||||
self.emitter?.color = self.animColor ?? color.cgColor
|
||||
self.emitterLayer?.setValue(wordRects, forKey: "emitterRects")
|
||||
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let radius = max(size.width, size.height)
|
||||
self.emitterLayer?.setValue(max(size.width, size.height), forKeyPath: "emitterBehaviors.fingerAttractor.radius")
|
||||
self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff")
|
||||
|
||||
var square: Float = 0.0
|
||||
for rect in wordRects {
|
||||
square += Float(rect.width * rect.height)
|
||||
}
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.emitter?.birthRate = min(100000, square * 0.35)
|
||||
}
|
||||
} else {
|
||||
if let staticParams = self.staticParams, staticParams.size == size && staticParams.color == color && staticParams.rects == lineRects && self.staticNode?.image != nil {
|
||||
return
|
||||
}
|
||||
self.staticParams = (size, color, lineRects)
|
||||
|
||||
let start = CACurrentMediaTime()
|
||||
|
||||
var combinedRect: CGRect?
|
||||
var combinedRects: [CGRect] = []
|
||||
for rect in lineRects {
|
||||
if let currentRect = combinedRect {
|
||||
if abs(currentRect.minY - rect.minY) < 1.0 && abs(currentRect.maxY - rect.maxY) < 1.0 {
|
||||
combinedRect = currentRect.union(rect)
|
||||
} else {
|
||||
combinedRects.append(currentRect.insetBy(dx: 0.0, dy: -1.0 + UIScreenPixel))
|
||||
combinedRect = rect
|
||||
}
|
||||
} else {
|
||||
combinedRect = rect
|
||||
}
|
||||
}
|
||||
if let combinedRect {
|
||||
combinedRects.append(combinedRect.insetBy(dx: 0.0, dy: -1.0))
|
||||
}
|
||||
|
||||
print("combining \(CACurrentMediaTime() - start)")
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
var generator = ArbitraryRandomNumberGenerator(seed: 1)
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
for rect in combinedRects {
|
||||
if rect.width > 10.0 {
|
||||
let rate = Int(rect.width * rect.height * 0.25)
|
||||
for _ in 0 ..< rate {
|
||||
let location = CGPoint(x: .random(in: rect.minX ..< rect.maxX, using: &generator), y: .random(in: rect.minY ..< rect.maxY, using: &generator))
|
||||
context.fillEllipse(in: CGRect(origin: location, size: CGSize(width: 1.0, height: 1.0)))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
self.staticNode?.image = image
|
||||
}
|
||||
}
|
||||
self.staticNode?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
print("total draw \(CACurrentMediaTime() - start)")
|
||||
}
|
||||
}
|
||||
|
||||
public func update(size: CGSize, color: UIColor, textColor: UIColor, rects: [CGRect], wordRects: [CGRect]) {
|
||||
self.currentParams = (size, color, textColor, rects, wordRects)
|
||||
|
||||
self.emitterNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.emitterMaskNode.frame = self.emitterNode.bounds
|
||||
self.emitterMaskFillNode.frame = self.emitterNode.bounds
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
self.emitterNode.frame = bounds
|
||||
self.emitterMaskNode.frame = bounds
|
||||
self.emitterMaskFillNode.frame = bounds
|
||||
self.textMaskNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: size)
|
||||
|
||||
self.staticNode?.frame = bounds
|
||||
|
||||
if self.isNodeLoaded {
|
||||
self.updateEmitter()
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ public class MediaDustLayer: CALayer {
|
||||
public class MediaDustNode: ASDisplayNode {
|
||||
private var currentParams: (size: CGSize, color: UIColor)?
|
||||
private var animColor: CGColor?
|
||||
private let enableAnimations: Bool
|
||||
|
||||
private var emitterNode: ASDisplayNode
|
||||
private var emitter: CAEmitterCell?
|
||||
@@ -101,13 +102,18 @@ public class MediaDustNode: ASDisplayNode {
|
||||
private let emitterSpotNode: ASImageNode
|
||||
private let emitterMaskFillNode: ASDisplayNode
|
||||
|
||||
private var staticNode: ASImageNode?
|
||||
private var staticParams: CGSize?
|
||||
|
||||
public var isRevealed = false
|
||||
private var isExploding = false
|
||||
|
||||
public var revealed: () -> Void = {}
|
||||
public var tapped: () -> Void = {}
|
||||
|
||||
public override init() {
|
||||
public init(enableAnimations: Bool) {
|
||||
self.enableAnimations = enableAnimations
|
||||
|
||||
self.emitterNode = ASDisplayNode()
|
||||
self.emitterNode.isUserInteractionEnabled = false
|
||||
self.emitterNode.clipsToBounds = true
|
||||
@@ -132,65 +138,71 @@ public class MediaDustNode: ASDisplayNode {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
|
||||
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
|
||||
emitter.contentsScale = 1.8
|
||||
emitter.emissionRange = .pi * 2.0
|
||||
emitter.lifetime = 8.0
|
||||
emitter.scale = 0.5
|
||||
emitter.velocityRange = 0.0
|
||||
emitter.name = "dustCell"
|
||||
emitter.alphaRange = 1.0
|
||||
emitter.setValue("point", forKey: "particleType")
|
||||
emitter.setValue(1.0, forKey: "mass")
|
||||
emitter.setValue(0.01, forKey: "massRange")
|
||||
self.emitter = emitter
|
||||
|
||||
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
|
||||
alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
|
||||
alphaBehavior.setValue(true, forKey: "additive")
|
||||
|
||||
let scaleBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
scaleBehavior.setValue("scale", forKey: "keyPath")
|
||||
scaleBehavior.setValue([0.0, 0.5], forKey: "values")
|
||||
scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
|
||||
|
||||
let randomAttractor0 = createEmitterBehavior(type: "simpleAttractor")
|
||||
randomAttractor0.setValue("randomAttractor0", forKey: "name")
|
||||
randomAttractor0.setValue(20, forKey: "falloff")
|
||||
randomAttractor0.setValue(35, forKey: "radius")
|
||||
randomAttractor0.setValue(5, forKey: "stiffness")
|
||||
randomAttractor0.setValue(NSValue(cgPoint: .zero), forKey: "position")
|
||||
|
||||
let randomAttractor1 = createEmitterBehavior(type: "simpleAttractor")
|
||||
randomAttractor1.setValue("randomAttractor1", forKey: "name")
|
||||
randomAttractor1.setValue(20, forKey: "falloff")
|
||||
randomAttractor1.setValue(35, forKey: "radius")
|
||||
randomAttractor1.setValue(5, forKey: "stiffness")
|
||||
randomAttractor1.setValue(NSValue(cgPoint: .zero), forKey: "position")
|
||||
|
||||
let fingerAttractor = createEmitterBehavior(type: "simpleAttractor")
|
||||
fingerAttractor.setValue("fingerAttractor", forKey: "name")
|
||||
|
||||
let behaviors = [randomAttractor0, randomAttractor1, fingerAttractor, alphaBehavior, scaleBehavior]
|
||||
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
emitterLayer.masksToBounds = true
|
||||
emitterLayer.allowsGroupOpacity = true
|
||||
emitterLayer.lifetime = 1
|
||||
emitterLayer.emitterCells = [emitter]
|
||||
emitterLayer.seed = arc4random()
|
||||
emitterLayer.emitterShape = .rectangle
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
|
||||
emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
|
||||
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
|
||||
self.emitterLayer = emitterLayer
|
||||
|
||||
self.emitterNode.layer.addSublayer(emitterLayer)
|
||||
if self.enableAnimations {
|
||||
let emitter = CAEmitterCell()
|
||||
emitter.color = UIColor(rgb: 0xffffff, alpha: 0.0).cgColor
|
||||
emitter.contents = UIImage(bundleImageName: "Components/TextSpeckle")?.cgImage
|
||||
emitter.contentsScale = 1.8
|
||||
emitter.emissionRange = .pi * 2.0
|
||||
emitter.lifetime = 8.0
|
||||
emitter.scale = 0.5
|
||||
emitter.velocityRange = 0.0
|
||||
emitter.name = "dustCell"
|
||||
emitter.alphaRange = 1.0
|
||||
emitter.setValue("point", forKey: "particleType")
|
||||
emitter.setValue(1.0, forKey: "mass")
|
||||
emitter.setValue(0.01, forKey: "massRange")
|
||||
self.emitter = emitter
|
||||
|
||||
let alphaBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
alphaBehavior.setValue("color.alpha", forKey: "keyPath")
|
||||
alphaBehavior.setValue([0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1, 0, 0, 1, 0, -1], forKey: "values")
|
||||
alphaBehavior.setValue(true, forKey: "additive")
|
||||
|
||||
let scaleBehavior = createEmitterBehavior(type: "valueOverLife")
|
||||
scaleBehavior.setValue("scale", forKey: "keyPath")
|
||||
scaleBehavior.setValue([0.0, 0.5], forKey: "values")
|
||||
scaleBehavior.setValue([0.0, 0.05], forKey: "locations")
|
||||
|
||||
let randomAttractor0 = createEmitterBehavior(type: "simpleAttractor")
|
||||
randomAttractor0.setValue("randomAttractor0", forKey: "name")
|
||||
randomAttractor0.setValue(20, forKey: "falloff")
|
||||
randomAttractor0.setValue(35, forKey: "radius")
|
||||
randomAttractor0.setValue(5, forKey: "stiffness")
|
||||
randomAttractor0.setValue(NSValue(cgPoint: .zero), forKey: "position")
|
||||
|
||||
let randomAttractor1 = createEmitterBehavior(type: "simpleAttractor")
|
||||
randomAttractor1.setValue("randomAttractor1", forKey: "name")
|
||||
randomAttractor1.setValue(20, forKey: "falloff")
|
||||
randomAttractor1.setValue(35, forKey: "radius")
|
||||
randomAttractor1.setValue(5, forKey: "stiffness")
|
||||
randomAttractor1.setValue(NSValue(cgPoint: .zero), forKey: "position")
|
||||
|
||||
let fingerAttractor = createEmitterBehavior(type: "simpleAttractor")
|
||||
fingerAttractor.setValue("fingerAttractor", forKey: "name")
|
||||
|
||||
let behaviors = [randomAttractor0, randomAttractor1, fingerAttractor, alphaBehavior, scaleBehavior]
|
||||
|
||||
let emitterLayer = CAEmitterLayer()
|
||||
emitterLayer.masksToBounds = true
|
||||
emitterLayer.allowsGroupOpacity = true
|
||||
emitterLayer.lifetime = 1
|
||||
emitterLayer.emitterCells = [emitter]
|
||||
emitterLayer.seed = arc4random()
|
||||
emitterLayer.emitterShape = .rectangle
|
||||
emitterLayer.setValue(behaviors, forKey: "emitterBehaviors")
|
||||
|
||||
emitterLayer.setValue(4.0, forKeyPath: "emitterBehaviors.fingerAttractor.stiffness")
|
||||
emitterLayer.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
|
||||
self.emitterLayer = emitterLayer
|
||||
|
||||
self.emitterNode.layer.addSublayer(emitterLayer)
|
||||
} else {
|
||||
let staticNode = ASImageNode()
|
||||
self.staticNode = staticNode
|
||||
self.addSubnode(staticNode)
|
||||
}
|
||||
|
||||
self.updateEmitter()
|
||||
|
||||
@@ -207,49 +219,57 @@ public class MediaDustNode: ASDisplayNode {
|
||||
self.tapped()
|
||||
|
||||
self.isRevealed = true
|
||||
self.isExploding = true
|
||||
|
||||
let position = gestureRecognizer.location(in: self.view)
|
||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||
|
||||
let maskSize = self.emitterNode.frame.size
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true)
|
||||
if self.enableAnimations {
|
||||
self.isExploding = true
|
||||
|
||||
Queue.mainQueue().async {
|
||||
self.emitterSpotNode.image = emitterMaskImage
|
||||
}
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
||||
let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0
|
||||
let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0
|
||||
let maxFactor = max(abs(xFactor), abs(yFactor))
|
||||
|
||||
let scaleAddition = maxFactor * 4.0
|
||||
let durationAddition = -maxFactor * 0.2
|
||||
let position = gestureRecognizer.location(in: self.view)
|
||||
self.emitterLayer?.setValue(true, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
self.emitterLayer?.setValue(position, forKeyPath: "emitterBehaviors.fingerAttractor.position")
|
||||
|
||||
self.supernode?.view.mask = self.emitterMaskNode.view
|
||||
self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.emitterSpotNode.position = position
|
||||
self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.45 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.revealed()
|
||||
self?.alpha = 0.0
|
||||
self?.supernode?.view.mask = nil
|
||||
let maskSize = self.emitterNode.frame.size
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
let emitterMaskImage = generateMaskImage(size: maskSize, position: position, inverse: true)
|
||||
|
||||
})
|
||||
self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
|
||||
self.isExploding = false
|
||||
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
Queue.mainQueue().async {
|
||||
self.emitterSpotNode.image = emitterMaskImage
|
||||
}
|
||||
}
|
||||
|
||||
self.emitterSpotNode.layer.removeAllAnimations()
|
||||
self.emitterMaskFillNode.layer.removeAllAnimations()
|
||||
Queue.mainQueue().after(0.1 * UIView.animationDurationFactor()) {
|
||||
let xFactor = (position.x / self.emitterNode.frame.width - 0.5) * 2.0
|
||||
let yFactor = (position.y / self.emitterNode.frame.height - 0.5) * 2.0
|
||||
let maxFactor = max(abs(xFactor), abs(yFactor))
|
||||
|
||||
let scaleAddition = maxFactor * 4.0
|
||||
let durationAddition = -maxFactor * 0.2
|
||||
|
||||
self.supernode?.view.mask = self.emitterMaskNode.view
|
||||
self.emitterSpotNode.frame = CGRect(x: 0.0, y: 0.0, width: self.emitterMaskNode.frame.width * 3.0, height: self.emitterMaskNode.frame.height * 3.0)
|
||||
|
||||
self.emitterSpotNode.layer.anchorPoint = CGPoint(x: position.x / self.emitterMaskNode.frame.width, y: position.y / self.emitterMaskNode.frame.height)
|
||||
self.emitterSpotNode.position = position
|
||||
self.emitterSpotNode.layer.animateScale(from: 0.3333, to: 10.5 + scaleAddition, duration: 0.45 + durationAddition, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.revealed()
|
||||
self?.alpha = 0.0
|
||||
self?.supernode?.view.mask = nil
|
||||
|
||||
})
|
||||
self.emitterMaskFillNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.8 * UIView.animationDurationFactor()) {
|
||||
self.isExploding = false
|
||||
self.emitterLayer?.setValue(false, forKeyPath: "emitterBehaviors.fingerAttractor.enabled")
|
||||
|
||||
self.emitterSpotNode.layer.removeAllAnimations()
|
||||
self.emitterMaskFillNode.layer.removeAllAnimations()
|
||||
}
|
||||
} else {
|
||||
self.supernode?.alpha = 0.0
|
||||
self.supernode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { [weak self] _ in
|
||||
self?.revealed()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,18 +347,49 @@ public class MediaDustNode: ASDisplayNode {
|
||||
guard let (size, _) = self.currentParams else {
|
||||
return
|
||||
}
|
||||
|
||||
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.emitterLayer?.emitterSize = size
|
||||
self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
let radius = max(size.width, size.height)
|
||||
self.emitterLayer?.setValue(max(size.width, size.height), forKeyPath: "emitterBehaviors.fingerAttractor.radius")
|
||||
self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff")
|
||||
|
||||
let square = Float(size.width * size.height)
|
||||
Queue.mainQueue().async {
|
||||
self.emitter?.birthRate = min(100000.0, square * 0.02)
|
||||
if self.enableAnimations {
|
||||
self.emitterLayer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.emitterLayer?.emitterSize = size
|
||||
self.emitterLayer?.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
let radius = max(size.width, size.height)
|
||||
self.emitterLayer?.setValue(max(size.width, size.height), forKeyPath: "emitterBehaviors.fingerAttractor.radius")
|
||||
self.emitterLayer?.setValue(radius * -0.5, forKeyPath: "emitterBehaviors.fingerAttractor.falloff")
|
||||
|
||||
let square = Float(size.width * size.height)
|
||||
Queue.mainQueue().async {
|
||||
self.emitter?.birthRate = min(100000.0, square * 0.02)
|
||||
}
|
||||
} else {
|
||||
if let staticParams = self.staticParams, staticParams == size && self.staticNode?.image != nil {
|
||||
return
|
||||
}
|
||||
self.staticParams = size
|
||||
|
||||
let start = CACurrentMediaTime()
|
||||
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
var generator = ArbitraryRandomNumberGenerator(seed: 1)
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
let rect = CGRect(origin: .zero, size: size)
|
||||
|
||||
let rate = Int(rect.width * rect.height * 0.04)
|
||||
for _ in 0 ..< rate {
|
||||
let location = CGPoint(x: .random(in: rect.minX ..< rect.maxX, using: &generator), y: .random(in: rect.minY ..< rect.maxY, using: &generator))
|
||||
context.fillEllipse(in: CGRect(origin: location, size: CGSize(width: 1.0, height: 1.0)))
|
||||
}
|
||||
})
|
||||
Queue.mainQueue().async {
|
||||
self.staticNode?.image = image
|
||||
}
|
||||
}
|
||||
self.staticNode?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
print("total draw \(CACurrentMediaTime() - start)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +402,8 @@ public class MediaDustNode: ASDisplayNode {
|
||||
self.emitterMaskNode.frame = bounds
|
||||
self.emitterMaskFillNode.frame = bounds
|
||||
|
||||
self.staticNode?.frame = bounds
|
||||
|
||||
if self.isNodeLoaded {
|
||||
self.updateEmitter()
|
||||
self.setupRandomAnimations()
|
||||
|
||||
@@ -25,24 +25,26 @@ final class MediaPickerGridItem: GridItem {
|
||||
let content: MediaPickerGridItemContent
|
||||
let interaction: MediaPickerInteraction
|
||||
let theme: PresentationTheme
|
||||
let enableAnimations: Bool
|
||||
|
||||
let section: GridSection? = nil
|
||||
|
||||
init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme) {
|
||||
init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, enableAnimations: Bool) {
|
||||
self.content = content
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
self.enableAnimations = enableAnimations
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
switch self.content {
|
||||
case let .asset(fetchResult, index):
|
||||
let node = MediaPickerGridItemNode()
|
||||
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme)
|
||||
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
|
||||
return node
|
||||
case let .media(media, index):
|
||||
let node = MediaPickerGridItemNode()
|
||||
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme)
|
||||
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
|
||||
return node
|
||||
}
|
||||
}
|
||||
@@ -54,13 +56,13 @@ final class MediaPickerGridItem: GridItem {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme)
|
||||
node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
|
||||
case let .media(media, index):
|
||||
guard let node = node as? MediaPickerGridItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme)
|
||||
node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,6 +83,8 @@ private let maskImage = generateImage(CGSize(width: 1.0, height: 24.0), opaque:
|
||||
final class MediaPickerGridItemNode: GridItemNode {
|
||||
var currentMediaState: (TGMediaSelectableItem, Int)?
|
||||
var currentState: (PHFetchResult<PHAsset>, Int)?
|
||||
var enableAnimations: Bool = true
|
||||
|
||||
private let imageNode: ImageNode
|
||||
private var checkNode: InteractiveCheckNode?
|
||||
private let gradientNode: ASImageNode
|
||||
@@ -214,9 +218,10 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme) {
|
||||
func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme, enableAnimations: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
self.enableAnimations = enableAnimations
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
@@ -281,9 +286,10 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.updateHiddenMedia()
|
||||
}
|
||||
|
||||
func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult<PHAsset>, index: Int, theme: PresentationTheme) {
|
||||
func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult<PHAsset>, index: Int, theme: PresentationTheme, enableAnimations: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
self.enableAnimations = enableAnimations
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
@@ -395,7 +401,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
if hasSpoiler {
|
||||
if self.spoilerNode == nil {
|
||||
let spoilerNode = SpoilerOverlayNode()
|
||||
let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
|
||||
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
|
||||
self.spoilerNode = spoilerNode
|
||||
|
||||
@@ -458,12 +464,12 @@ class SpoilerOverlayNode: ASDisplayNode {
|
||||
private var maskView: UIView?
|
||||
private var maskLayer: CAShapeLayer?
|
||||
|
||||
override init() {
|
||||
init(enableAnimations: Bool) {
|
||||
self.blurNode = ASImageNode()
|
||||
self.blurNode.displaysAsynchronously = false
|
||||
self.blurNode.contentMode = .scaleAspectFill
|
||||
|
||||
self.dustNode = MediaDustNode()
|
||||
self.dustNode = MediaDustNode(enableAnimations: enableAnimations)
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ private struct MediaPickerGridEntry: Comparable, Identifiable {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem {
|
||||
return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme)
|
||||
func item(context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem {
|
||||
return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +64,12 @@ private struct MediaPickerGridTransaction {
|
||||
let updates: [GridNodeUpdateItem]
|
||||
let scrollToItem: GridNodeScrollToItem?
|
||||
|
||||
init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], account: Account, interaction: MediaPickerInteraction, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) {
|
||||
init(previousList: [MediaPickerGridEntry], list: [MediaPickerGridEntry], context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme, scrollToItem: GridNodeScrollToItem?) {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
|
||||
|
||||
self.deletions = deleteIndices
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, theme: theme), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, theme: theme)) }
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) }
|
||||
|
||||
self.scrollToItem = scrollToItem
|
||||
}
|
||||
@@ -545,7 +545,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false)
|
||||
}
|
||||
|
||||
let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, account: controller.context.account, interaction: interaction, theme: self.presentationData.theme, scrollToItem: scrollToItem)
|
||||
let transaction = MediaPickerGridTransaction(previousList: previousEntries, list: entries, context: controller.context, interaction: interaction, theme: self.presentationData.theme, scrollToItem: scrollToItem)
|
||||
self.enqueueTransaction(transaction)
|
||||
|
||||
if updateLayout, let (layout, navigationBarHeight) = self.validLayout {
|
||||
|
||||
@@ -17,6 +17,7 @@ import ChatMessageBackground
|
||||
private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
let asset: TGMediaEditableItem
|
||||
private let interaction: MediaPickerInteraction?
|
||||
private let enableAnimations: Bool
|
||||
|
||||
private let imageNode: ImageNode
|
||||
private var checkNode: InteractiveCheckNode?
|
||||
@@ -56,15 +57,16 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
|
||||
private var videoDuration: Double?
|
||||
|
||||
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?) {
|
||||
init(asset: TGMediaEditableItem, interaction: MediaPickerInteraction?, enableAnimations: Bool) {
|
||||
self.asset = asset
|
||||
self.interaction = interaction
|
||||
self.enableAnimations = enableAnimations
|
||||
|
||||
self.imageNode = ImageNode()
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
self.imageNode.clipsToBounds = true
|
||||
self.imageNode.animateFirstTransition = false
|
||||
|
||||
self.asset = asset
|
||||
self.interaction = interaction
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
@@ -165,7 +167,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
|
||||
if hasSpoiler {
|
||||
if self.spoilerNode == nil {
|
||||
let spoilerNode = SpoilerOverlayNode()
|
||||
let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
|
||||
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
|
||||
self.spoilerNode = spoilerNode
|
||||
|
||||
@@ -781,7 +783,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
if let current = self.itemNodes[identifier] {
|
||||
itemNode = current
|
||||
} else {
|
||||
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction)
|
||||
itemNode = MediaPickerSelectedItemNode(asset: asset, interaction: self.interaction, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
self.itemNodes[identifier] = itemNode
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
|
||||
|
||||
@@ -249,12 +249,6 @@ public func cacheAnimatedStickerFrames(data: Data, size: CGSize, fitzModifier: E
|
||||
|
||||
subscriber.putNext(.tempFile(tempFile))
|
||||
subscriber.putCompletion()
|
||||
/*print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
print("of which delta time \(deltaTime)")
|
||||
|
||||
print("of which compression time \(compressionTime)")*/
|
||||
}
|
||||
}
|
||||
}))
|
||||
@@ -399,12 +393,6 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String
|
||||
|
||||
subscriber.putNext(.tempFile(tempFile))
|
||||
subscriber.putCompletion()
|
||||
/*print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
print("of which delta time \(deltaTime)")
|
||||
|
||||
print("of which compression time \(compressionTime)")*/
|
||||
}))
|
||||
|
||||
return ActionDisposable {
|
||||
|
||||
@@ -316,7 +316,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.insertSubnode(dustNode, aboveSubnode: strongSelf.labelNode.textNode)
|
||||
|
||||
@@ -189,11 +189,11 @@ private class ExtendedMediaOverlayNode: ASDisplayNode {
|
||||
var isRevealed = false
|
||||
var tapped: () -> Void = {}
|
||||
|
||||
override init() {
|
||||
init(enableAnimations: Bool) {
|
||||
self.blurredImageNode = TransformImageNode()
|
||||
self.blurredImageNode.contentAnimations = []
|
||||
|
||||
self.dustNode = MediaDustNode()
|
||||
self.dustNode = MediaDustNode(enableAnimations: enableAnimations)
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
self.buttonNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3)
|
||||
@@ -1898,7 +1898,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
||||
|
||||
if displaySpoiler {
|
||||
if self.extendedMediaOverlayNode == nil {
|
||||
let extendedMediaOverlayNode = ExtendedMediaOverlayNode()
|
||||
let extendedMediaOverlayNode = ExtendedMediaOverlayNode(enableAnimations: self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true)
|
||||
extendedMediaOverlayNode.tapped = { [weak self] in
|
||||
self?.internallyVisible = true
|
||||
self?.updateVisibility()
|
||||
|
||||
@@ -436,12 +436,13 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
|
||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: width - 10.0 - imageSize.width, y: (panelHeight - imageSize.height) / 2.0), size: imageSize))
|
||||
|
||||
if !textLayout.spoilers.isEmpty, let presentationData = self.item?.context.sharedContext.currentPresentationData.with({ $0 }) {
|
||||
if !textLayout.spoilers.isEmpty, let item = self.item {
|
||||
let presentationData = item.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let dustNode: InvisibleInkDustNode
|
||||
if let current = self.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
self.dustNode = dustNode
|
||||
self.insertSubnode(dustNode, aboveSubnode: self.textNode.textNode)
|
||||
|
||||
@@ -344,7 +344,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
if let current = node.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: arguments.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
node.dustNode = dustNode
|
||||
node.contentNode.insertSubnode(dustNode, aboveSubnode: textNode.textNode)
|
||||
|
||||
@@ -485,7 +485,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode)
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: item.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
||||
}
|
||||
|
||||
@@ -764,7 +764,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if let current = strongSelf.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode)
|
||||
dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
strongSelf.dustNode = dustNode
|
||||
strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
|
||||
}
|
||||
|
||||
@@ -2289,7 +2289,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
if let start = textInputNode.textView.position(from: beginning, offset: startIndex), let end = textInputNode.textView.position(from: start, offset: endIndex - startIndex), let textRange = textInputNode.textView.textRange(from: start, to: end) {
|
||||
let textRects = textInputNode.textView.selectionRects(for: textRange)
|
||||
for textRect in textRects {
|
||||
rects.append(textRect.rect.insetBy(dx: 1.0, dy: 1.0).offsetBy(dx: 0.0, dy: 1.0))
|
||||
if textRect.rect.width > 1.0 && textRect.rect.size.height > 1.0 {
|
||||
rects.append(textRect.rect.insetBy(dx: 1.0, dy: 1.0).offsetBy(dx: 0.0, dy: 1.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2335,7 +2337,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
if let current = self.dustNode {
|
||||
dustNode = current
|
||||
} else {
|
||||
dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.context?.sharedContext.energyUsageSettings.fullTranslucency ?? true)
|
||||
dustNode.alpha = self.spoilersRevealed ? 0.0 : 1.0
|
||||
dustNode.isUserInteractionEnabled = false
|
||||
textInputNode.textView.addSubview(dustNode.view)
|
||||
|
||||
@@ -34,6 +34,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
private let actionArea: AccessibilityAreaNode
|
||||
|
||||
private let context: AccountContext
|
||||
var theme: PresentationTheme
|
||||
var strings: PresentationStrings
|
||||
|
||||
@@ -42,6 +43,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
init(context: AccountContext, messageId: MessageId, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) {
|
||||
self.messageId = messageId
|
||||
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
@@ -344,7 +346,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
if let textLayout = self.textNode.cachedLayout, !textLayout.spoilers.isEmpty {
|
||||
if self.dustNode == nil {
|
||||
let dustNode = InvisibleInkDustNode(textNode: nil)
|
||||
let dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency)
|
||||
self.dustNode = dustNode
|
||||
self.textNode.supernode?.insertSubnode(dustNode, aboveSubnode: self.textNode)
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
|
||||
var image: UIImage?
|
||||
if let fullSizeData = fullSizeData {
|
||||
if mode == .screen {
|
||||
image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0, displayMode != .aspectFill)
|
||||
image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0, displayMode == .aspectFit)
|
||||
} else {
|
||||
image = UIImage(data: fullSizeData)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user