diff --git a/submodules/GradientBackground/BUILD b/submodules/GradientBackground/BUILD new file mode 100644 index 0000000000..e84cbca60e --- /dev/null +++ b/submodules/GradientBackground/BUILD @@ -0,0 +1,16 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GradientBackground", + module_name = "GradientBackground", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/GradientBackground/Sources/GradientBackground.swift b/submodules/GradientBackground/Sources/GradientBackground.swift new file mode 100644 index 0000000000..45deab1eb0 --- /dev/null +++ b/submodules/GradientBackground/Sources/GradientBackground.swift @@ -0,0 +1,265 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit + +private struct GradientPoint { + var color: UIColor + var position: CGPoint +} + +private func applyTransformerToPoints(step: Int, substep: Int) -> [GradientPoint] { + var points: [GradientPoint] = [] + + var firstSet: [CGPoint] + var secondSet: [CGPoint] + + let colors: [UIColor] = [ + UIColor(rgb: 0x7FA381), + UIColor(rgb: 0xFFF5C5), + UIColor(rgb: 0x336F55), + UIColor(rgb: 0xFBE37D) + ] + + let firstStepPoints: [CGPoint] = [ + CGPoint(x: 0.823, y: 0.086), + CGPoint(x: 0.362, y: 0.254), + CGPoint(x: 0.184, y: 0.923), + CGPoint(x: 0.648, y: 0.759) + ] + + let nextStepPoints: [CGPoint] = [ + CGPoint(x: 0.59, y: 0.16), + CGPoint(x: 0.28, y: 0.58), + CGPoint(x: 0.42, y: 0.83), + CGPoint(x: 0.74, y: 0.42) + ] + + if step % 2 == 0 { + firstSet = shiftArray(array: firstStepPoints, offset: step / 2) + secondSet = shiftArray(array: nextStepPoints, offset: step / 2) + } else { + firstSet = shiftArray(array: nextStepPoints, offset: step / 2) + secondSet = shiftArray(array: firstStepPoints, offset: step / 2 + 1) + } + + for index in 0 ..< colors.count { + let point = transformPoint( + points: (firstSet[index], secondSet[index]), + substep: substep + ) + + points.append(GradientPoint( + color: colors[index], + position: point + )) + } + + return points +} + +private func shiftArray(array: [CGPoint], offset: Int) -> [CGPoint] { + var newArray = array + var offset = offset + while offset > 0 { + let element = newArray.removeFirst() + newArray.append(element) + offset -= 1 + } + return newArray +} + +private func transformPoint(points: (first: CGPoint, second: CGPoint), substep: Int) -> CGPoint { + let delta = CGFloat(substep) / CGFloat(30) + let x = points.first.x + (points.second.x - points.first.x) * delta + let y = points.first.y + (points.second.y - points.first.y) * delta + + return CGPoint(x: x, y: y) +} + +private func generateGradientComponent(size: CGSize, color: UIColor) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 1.0) + + let c = UIGraphicsGetCurrentContext() + + c?.clear(CGRect(origin: CGPoint.zero, size: size)) + + c?.setBlendMode(.normal) + + var gradLocs: [CGFloat] = [0, 0.1, 0.35, 1] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let radius = min(size.width / 2.0, size.height / 2.0) + + let colors = [ + color.cgColor, + color.withAlphaComponent(0.8).cgColor, + color.withAlphaComponent(0.3).cgColor, + color.withAlphaComponent(0).cgColor + ] + + let grad = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &gradLocs) + if let grad = grad { + let newPoint = CGPoint(x: size.width / 2.0, y: size.height / 2.0) + + c?.drawRadialGradient(grad, startCenter: newPoint, startRadius: 0, endCenter: newPoint, endRadius: radius, options: []) + } + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image +} + +private func generateGradient(with size: CGSize, gradPointArray gradPoints: [GradientPoint]) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, true, 1.0) + + let c = UIGraphicsGetCurrentContext() + + c?.setFillColor(UIColor.white.cgColor) + c?.fill(CGRect(origin: CGPoint.zero, size: size)) + + c?.setBlendMode(.multiply) + + var gradLocs: [CGFloat] = [0, 0.0, 0.35, 1] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let radius = max(size.width, size.height) + + for point in gradPoints { + let colors = [ + point.color.cgColor, + point.color.withAlphaComponent(0.8).cgColor, + point.color.withAlphaComponent(0.3).cgColor, + point.color.withAlphaComponent(0).cgColor + ] + + let grad = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &gradLocs) + if let grad = grad { + let newPoint = point.position.applying( + .init(scaleX: size.width, y: size.height) + ) + + c?.drawRadialGradient(grad, startCenter: newPoint, startRadius: 0, endCenter: newPoint, endRadius: radius, options: []) + } + } + + let i = UIGraphicsGetImageFromCurrentImageContext() + + UIGraphicsEndImageContext() + + return i +} + +public final class GradientBackgroundNode: ASDisplayNode { + //private let imageView: UIImageView + + private var pointImages: [UIImageView] = [] + + private let firstStepPoints: [CGPoint] = [ + CGPoint(x: 0.823, y: 0.086), + CGPoint(x: 0.362, y: 0.254), + CGPoint(x: 0.184, y: 0.923), + CGPoint(x: 0.648, y: 0.759) + ] + + private let nextStepPoints: [CGPoint] = [ + CGPoint(x: 0.59, y: 0.16), + CGPoint(x: 0.28, y: 0.58), + CGPoint(x: 0.42, y: 0.83), + CGPoint(x: 0.74, y: 0.42) + ] + + private var phase: Int = 0 + + private var timer: Timer? + + private var validLayout: CGSize? + + override public init() { + //self.imageView = UIImageView() + + super.init() + + self.phase = 3 + + self.backgroundColor = .white + self.clipsToBounds = true + + //self.view.addSubview(self.imageView) + + /*let compositingModes: [String] = CIFilter + .filterNames(inCategory: nil) // fetch all the available filters + .filter { $0.contains("Compositing")} // retrieve only the compositing ones + .map { + let capitalizedFilter = $0.dropFirst(2) // drop the CIn prefix + let first = capitalizedFilter.first! // fetch the first letter + // lowercase the first letter and drop the `Compositing` suffix + return "\(first.lowercased())\(capitalizedFilter.dropFirst().dropLast("Compositing".count))" + }*/ + + //print("compositingModes: \(compositingModes)") + + //self.imageView.alpha = 0.5 + //self.imageView.layer.compositingFilter = "multiplyBlendMode" + + let colors: [UIColor] = [ + UIColor(rgb: 0xfff6bf), + UIColor(rgb: 0x76a076), + UIColor(rgb: 0xf6e477), + UIColor(rgb: 0x316b4d) + ] + + for color in colors { + let pointImage = UIImageView(image: generateGradientComponent(size: CGSize(width: 800.0, height: 800.0), color: color.withMultiplied(hue: 1.0, saturation: 1.2, brightness: 1.0))) + pointImage.layer.compositingFilter = "multiplyBlendMode" + //pointImage.layer.compositingFilter = "additionBlendMode" + pointImage.alpha = 0.7 + self.view.addSubview(pointImage) + self.pointImages.append(pointImage) + } + + if #available(iOS 10.0, *) { + let timer = Timer(timeInterval: 2.0, repeats: true, block: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.phase = (strongSelf.phase + 1) % 4 + if let size = strongSelf.validLayout { + strongSelf.updateLayout(size: size, transition: .animated(duration: 0.5, curve: .spring)) + } + }) + self.timer = timer + RunLoop.main.add(timer, forMode: .common) + } + } + + deinit { + self.timer?.invalidate() + } + + public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayout = size + + let positions: [CGPoint] + + if self.phase % 2 == 0 { + positions = shiftArray(array: firstStepPoints, offset: self.phase / 2) + } else { + positions = shiftArray(array: nextStepPoints, offset: self.phase / 2 + 1) + } + + for i in 0 ..< firstStepPoints.count { + if self.pointImages.count <= i { + break + } + let pointCenter = CGPoint(x: size.width * positions[i].x, y: size.height * positions[i].y) + let pointSide = max(size.width, size.height) * 2.0 + let pointSize = CGSize(width: pointSide, height: pointSide) + transition.updateFrame(view: self.pointImages[i], frame: CGRect(origin: CGPoint(x: pointCenter.x - pointSize.width / 2.0, y: pointCenter.y - pointSize.height / 2.0), size: pointSize)) + } + + /*self.imageView.frame = CGRect(origin: CGPoint(), size: size) + self.imageView.layer.magnificationFilter = .linear + + self.imageView.image = generateGradient(with: size.fitted(CGSize(width: 64.0, height: 64.0)), gradPointArray: applyTransformerToPoints(step: 0, substep: 0))*/ + } +} diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 9642bf8b48..391f01676d 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -221,6 +221,7 @@ swift_library( "//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode", "//submodules/DebugSettingsUI:DebugSettingsUI", "//submodules/ImportStickerPackUI:ImportStickerPackUI", + "//submodules/GradientBackground:GradientBackground", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 53800e9a45..e0e7c85b75 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -16,6 +16,7 @@ import TelegramUniversalVideoContent import ChatInterfaceState import FastBlur import ConfettiEffect +import GradientBackground final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -300,6 +301,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } let backgroundNode: WallpaperBackgroundNode + let gradientBackgroundNode: GradientBackgroundNode? let backgroundImageDisposable = MetaDisposable() let historyNode: ChatHistoryListNode var blurredHistoryNode: ASImageNode? @@ -466,6 +468,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.backgroundNode = WallpaperBackgroundNode() self.backgroundNode.displaysAsynchronously = false + + self.gradientBackgroundNode = GradientBackgroundNode() self.titleAccessoryPanelContainer = ChatControllerTitlePanelNodeContainer() self.titleAccessoryPanelContainer.clipsToBounds = true @@ -598,6 +602,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.enableExtractedBackgrounds = true self.addSubnode(self.backgroundNode) + if let gradientBackgroundNode = self.gradientBackgroundNode { + self.addSubnode(gradientBackgroundNode) + } self.addSubnode(self.historyNodeContainer) self.addSubnode(self.navigateButtons) self.addSubnode(self.titleAccessoryPanelContainer) @@ -1282,6 +1289,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: self.backgroundNode, frame: contentBounds) self.backgroundNode.updateLayout(size: contentBounds.size, transition: transition) + + if let gradientBackgroundNode = self.gradientBackgroundNode { + transition.updateFrame(node: gradientBackgroundNode, frame: contentBounds) + gradientBackgroundNode.updateLayout(size: contentBounds.size, transition: transition) + } + transition.updateFrame(node: self.historyNodeContainer, frame: contentBounds) transition.updateBounds(node: self.historyNode, bounds: CGRect(origin: CGPoint(), size: contentBounds.size)) transition.updatePosition(node: self.historyNode, position: CGPoint(x: contentBounds.size.width / 2.0, y: contentBounds.size.height / 2.0))