Swiftgram/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift
2021-05-21 20:42:10 +04:00

240 lines
10 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import GradientBackground
import TelegramPresentationData
import SyncCore
import TelegramCore
import AccountContext
import SwiftSignalKit
import WallpaperResources
import Postbox
private let motionAmount: CGFloat = 32.0
public final class WallpaperBackgroundNode: ASDisplayNode {
private let context: AccountContext
private let contentNode: ASDisplayNode
private var gradientBackgroundNode: GradientBackgroundNode?
private let patternImageNode: TransformImageNode
private var validLayout: CGSize?
private var wallpaper: TelegramWallpaper?
private let patternImageDisposable = MetaDisposable()
private var motionEnabled: Bool = false {
didSet {
if oldValue != self.motionEnabled {
if self.motionEnabled {
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = motionAmount
horizontal.maximumRelativeValue = -motionAmount
let vertical = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
vertical.minimumRelativeValue = motionAmount
vertical.maximumRelativeValue = -motionAmount
let group = UIMotionEffectGroup()
group.motionEffects = [horizontal, vertical]
self.contentNode.view.addMotionEffect(group)
} else {
for effect in self.contentNode.view.motionEffects {
self.contentNode.view.removeMotionEffect(effect)
}
}
if !self.frame.isEmpty {
self.updateScale()
}
}
}
}
public var rotation: CGFloat = 0.0 {
didSet {
var fromValue: CGFloat = 0.0
if let value = (self.layer.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue {
fromValue = CGFloat(value)
}
self.contentNode.layer.transform = CATransform3DMakeRotation(self.rotation, 0.0, 0.0, 1.0)
self.contentNode.layer.animateRotation(from: fromValue, to: self.rotation, duration: 0.3)
}
}
private var imageContentMode: UIView.ContentMode {
didSet {
self.contentNode.contentMode = self.imageContentMode
}
}
private func updateScale() {
if self.motionEnabled {
let scale = (self.frame.width + motionAmount * 2.0) / self.frame.width
self.contentNode.transform = CATransform3DMakeScale(scale, scale, 1.0)
} else {
self.contentNode.transform = CATransform3DIdentity
}
}
public init(context: AccountContext) {
self.context = context
self.imageContentMode = .scaleAspectFill
self.contentNode = ASDisplayNode()
self.contentNode.contentMode = self.imageContentMode
self.patternImageNode = TransformImageNode()
self.patternImageNode.layer.compositingFilter = "softLightBlendMode"
super.init()
self.clipsToBounds = true
self.contentNode.frame = self.bounds
self.addSubnode(self.contentNode)
self.addSubnode(self.patternImageNode)
}
deinit {
self.patternImageDisposable.dispose()
}
public func update(wallpaper: TelegramWallpaper) {
let previousWallpaper = self.wallpaper
if self.wallpaper == wallpaper {
return
}
self.wallpaper = wallpaper
var gradientColors: [UInt32] = []
var gradientAngle: Int32 = 0
if case let .color(color) = wallpaper {
gradientColors = [color]
} else if case let .gradient(colors, settings) = wallpaper {
gradientColors = colors
gradientAngle = settings.rotation ?? 0
} else if case let .file(_, _, _, _, isPattern, _, _, _, settings) = wallpaper, isPattern {
gradientColors = settings.colors
gradientAngle = settings.rotation ?? 0
}
if gradientColors.count >= 3 {
if self.gradientBackgroundNode == nil {
let gradientBackgroundNode = createGradientBackgroundNode()
self.gradientBackgroundNode = gradientBackgroundNode
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
gradientBackgroundNode.addSubnode(self.patternImageNode)
}
self.gradientBackgroundNode?.updateColors(colors: gradientColors.map { color -> UIColor in
return UIColor(rgb: color)
})
self.contentNode.backgroundColor = nil
self.contentNode.contents = nil
self.motionEnabled = false
} else {
if let gradientBackgroundNode = self.gradientBackgroundNode {
self.gradientBackgroundNode = nil
gradientBackgroundNode.removeFromSupernode()
self.insertSubnode(self.patternImageNode, aboveSubnode: self.contentNode)
}
self.motionEnabled = wallpaper.settings?.motion ?? false
if gradientColors.count >= 2 {
self.contentNode.backgroundColor = nil
self.contentNode.contents = generateImage(CGSize(width: 100.0, height: 200.0), rotatedContext: { size, context in
let gradientColors = [UIColor(rgb: gradientColors[0]).cgColor, UIColor(rgb: gradientColors[1]).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.rotate(by: CGFloat(gradientAngle) * CGFloat.pi / 180.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
})?.cgImage
} else if gradientColors.count >= 1 {
self.contentNode.backgroundColor = UIColor(rgb: gradientColors[0])
self.contentNode.contents = nil
} else {
self.contentNode.backgroundColor = .white
self.contentNode.contents = chatControllerBackgroundImage(theme: nil, wallpaper: wallpaper, mediaBox: self.context.sharedContext.accountManager.mediaBox, knockoutMode: false)?.cgImage
self.contentNode.isHidden = false
}
}
switch wallpaper {
case let .file(id, _, _, _, isPattern, _, _, file, settings) where isPattern:
var updated = true
if let previousWallpaper = previousWallpaper {
switch previousWallpaper {
case let .file(previousId, _, _, _, previousIsPattern, _, _, _, _):
if id == previousId && isPattern == previousIsPattern {
updated = false
}
default:
break
}
}
if updated {
func reference(for resource: MediaResource, media: Media, message: Message?) -> MediaResourceReference {
if let message = message {
return .media(media: .message(message: MessageReference(message), media: media), resource: resource)
}
return .wallpaper(wallpaper: nil, resource: resource)
}
var convertedRepresentations: [ImageRepresentationWithReference] = []
for representation in file.previewRepresentations {
convertedRepresentations.append(ImageRepresentationWithReference(representation: representation, reference: reference(for: representation.resource, media: file, message: nil)))
}
let dimensions = file.dimensions ?? PixelDimensions(width: 2000, height: 4000)
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil), reference: reference(for: file.resource, media: file, message: nil)))
let signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
self.patternImageNode.setSignal(signal)
}
self.patternImageNode.alpha = CGFloat(settings.intensity ?? 50) / 100.0
self.patternImageNode.isHidden = false
default:
self.patternImageNode.isHidden = true
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
}
}
public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil
self.validLayout = size
transition.updatePosition(node: self.contentNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
transition.updateBounds(node: self.contentNode, bounds: CGRect(origin: CGPoint(), size: size))
if let gradientBackgroundNode = self.gradientBackgroundNode {
transition.updateFrame(node: gradientBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
gradientBackgroundNode.updateLayout(size: size, transition: transition)
}
let makeImageLayout = self.patternImageNode.asyncLayout()
let applyImage = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: .black, preview: false)))
applyImage()
transition.updateFrame(node: self.patternImageNode, frame: CGRect(origin: CGPoint(), size: size))
if isFirstLayout && !self.frame.isEmpty {
self.updateScale()
}
}
public func animateEvent(transition: ContainedViewLayoutTransition) {
self.gradientBackgroundNode?.animateEvent(transition: transition)
}
}