From 131be5aaab8c4614e8f5eb442c57d01f8bb77fb5 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 28 Jun 2022 22:54:05 +0200 Subject: [PATCH] Input node improvements --- .../Source/Base/Transition.swift | 3 + .../Sources/ComponentDisplayAdapters.swift | 16 +- .../Sources/PagerComponent.swift | 5 +- .../ContextUI/Sources/PeekController.swift | 10 +- .../PeekControllerGestureRecognizer.swift | 24 +- .../Source/ContextControllerSourceNode.swift | 53 +- .../Display/Source/ContextGesture.swift | 11 +- .../Navigation/NavigationController.swift | 2 +- .../Navigation/NavigationModalFrame.swift | 8 +- .../ImportStickerPackControllerNode.swift | 10 +- .../StickerPackPreviewControllerNode.swift | 12 +- .../Sources/StickerPackPreviewGridItem.swift | 2 +- .../Sources/StickerPackScreen.swift | 12 +- .../Sources/StickerPreviewPeekContent.swift | 6 +- submodules/TelegramUI/BUILD | 1 + .../Components/AnimationCache/BUILD | 2 +- .../DCT/PublicHeaders/DCT/DCT.h | 14 - .../AnimationCache/DCT/Sources/DCT.m | 991 ------------------ .../AnimationCache/{DCT => ImageDCT}/BUILD | 7 +- .../PublicHeaders/ImageDCT/ImageDCT.h | 17 + .../PublicHeaders/ImageDCT}/YuvConversion.h | 0 .../AnimationCache/ImageDCT/Sources/DCT.cpp | 376 +++++++ .../AnimationCache/ImageDCT/Sources/DCT.h | 26 + .../ImageDCT/Sources/DCTCommon.h | 27 + .../AnimationCache/ImageDCT/Sources/DCT_C.c | 399 +++++++ .../ImageDCT/Sources/DCT_Neon.c | 677 ++++++++++++ .../ImageDCT/Sources/ImageDCT.mm | 31 + .../{DCT => ImageDCT}/Sources/YuvConversion.m | 2 +- .../AnimationCache/Sources/ImageData.swift | 12 +- .../Components/ChatInputPanelContainer/BUILD | 20 + .../Sources/ChatInputPanelContainer.swift | 113 ++ .../Components/EntityKeyboard/BUILD | 5 + .../Sources/EmojiPagerContentComponent.swift | 267 ++++- .../Sources/EntityKeyboard.swift | 8 +- .../EntityKeyboardTopPanelComponent.swift | 5 +- .../TelegramUI/Sources/ChatController.swift | 24 +- .../Sources/ChatControllerNode.swift | 497 +++++---- .../Sources/ChatEntityKeyboardInputNode.swift | 101 +- .../TelegramUI/Sources/ChatInputNode.swift | 3 + .../Sources/ChatInterfaceInputNodes.swift | 2 +- .../Sources/ChatMediaInputNode.swift | 16 +- .../ChatMediaInputStickerGridItem.swift | 2 +- .../Sources/ChatMessageTransitionNode.swift | 10 +- .../Sources/ChatTextInputPanelNode.swift | 10 +- .../Sources/FeaturedStickersScreen.swift | 18 +- ...textResultsChatInputContextPanelNode.swift | 12 +- ...rizontalStickersChatContextPanelNode.swift | 24 +- .../Sources/InlineReactionSearchPanel.swift | 24 +- .../StickerPaneSearchContentNode.swift | 8 +- .../StickersChatInputContextPanelItem.swift | 2 +- .../StickersChatInputContextPanelNode.swift | 22 +- 51 files changed, 2586 insertions(+), 1363 deletions(-) delete mode 100644 submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/DCT.h delete mode 100644 submodules/TelegramUI/Components/AnimationCache/DCT/Sources/DCT.m rename submodules/TelegramUI/Components/AnimationCache/{DCT => ImageDCT}/BUILD (72%) create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/ImageDCT.h rename submodules/TelegramUI/Components/AnimationCache/{DCT/PublicHeaders/DCT => ImageDCT/PublicHeaders/ImageDCT}/YuvConversion.h (100%) create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.h create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCTCommon.h create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_C.c create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_Neon.c create mode 100644 submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/ImageDCT.mm rename submodules/TelegramUI/Components/AnimationCache/{DCT => ImageDCT}/Sources/YuvConversion.m (98%) create mode 100644 submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD create mode 100644 submodules/TelegramUI/Components/ChatInputPanelContainer/Sources/ChatInputPanelContainer.swift diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 1477eb4fbd..a57d781e9d 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -120,6 +120,8 @@ private extension Transition.Animation.Curve { switch self { case .easeInOut: return CAMediaTimingFunction(name: .easeInEaseOut) + case let .custom(a, b, c, d): + return CAMediaTimingFunction(controlPoints: a, b, c, d) case .spring: preconditionFailure() } @@ -131,6 +133,7 @@ public struct Transition { public enum Curve { case easeInOut case spring + case custom(Float, Float, Float, Float) } case none diff --git a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift index 34fa9ce017..2c50f91083 100644 --- a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift +++ b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift @@ -10,8 +10,8 @@ public extension Transition.Animation.Curve { self = .easeInOut case .easeInOut: self = .easeInOut - case .custom: - self = .spring + case let .custom(a, b, c, d): + self = .custom(a, b, c, d) case .customSpring: self = .spring case .spring: @@ -21,10 +21,12 @@ public extension Transition.Animation.Curve { var containedViewLayoutTransitionCurve: ContainedViewLayoutTransitionCurve { switch self { - case .easeInOut: - return .easeInOut - case .spring: - return .spring + case .easeInOut: + return .easeInOut + case .spring: + return .spring + case let .custom(a, b, c, d): + return .custom(a, b, c, d) } } } @@ -47,4 +49,4 @@ public extension Transition { return .animated(duration: duration, curve: curve.containedViewLayoutTransitionCurve) } } -} \ No newline at end of file +} diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index 12b311e547..c24f6a728b 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -532,14 +532,13 @@ public final class PagerComponent: Component { } var removedIds: [AnyHashable] = [] - for (id, contentView) in self.contentViews { + for (id, _) in self.contentViews { if !validIds.contains(id) { removedIds.append(id) - contentView.view.removeFromSuperview() } } for id in removedIds { - self.contentViews.removeValue(forKey: id) + self.contentViews.removeValue(forKey: id)?.view.removeFromSuperview() } if let panelStateUpdated = component.panelStateUpdated { diff --git a/submodules/ContextUI/Sources/PeekController.swift b/submodules/ContextUI/Sources/PeekController.swift index 78950898b7..2bacd9ad6f 100644 --- a/submodules/ContextUI/Sources/PeekController.swift +++ b/submodules/ContextUI/Sources/PeekController.swift @@ -60,7 +60,7 @@ public final class PeekController: ViewController, ContextControllerProtocol { private let presentationData: PresentationData private let content: PeekControllerContent - var sourceNode: () -> ASDisplayNode? + var sourceView: () -> (UIView, CGRect)? public var visibilityUpdated: ((Bool) -> Void)? @@ -68,10 +68,10 @@ public final class PeekController: ViewController, ContextControllerProtocol { private var animatedIn = false - public init(presentationData: PresentationData, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) { + public init(presentationData: PresentationData, content: PeekControllerContent, sourceView: @escaping () -> (UIView, CGRect)?) { self.presentationData = presentationData self.content = content - self.sourceNode = sourceNode + self.sourceView = sourceView super.init(navigationBarPresentationData: nil) @@ -90,8 +90,8 @@ public final class PeekController: ViewController, ContextControllerProtocol { } private func getSourceRect() -> CGRect { - if let sourceNode = self.sourceNode() { - return sourceNode.view.convert(sourceNode.bounds, to: self.view) + if let (sourceView, sourceRect) = self.sourceView() { + return sourceView.convert(sourceRect, to: self.view) } else { let size = self.displayNode.bounds.size return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0)) diff --git a/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift b/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift index 623cad78ff..a766664a2c 100644 --- a/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift +++ b/submodules/ContextUI/Sources/PeekControllerGestureRecognizer.swift @@ -18,8 +18,8 @@ private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> B } public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { - private let contentAtPoint: (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? - private let present: (PeekControllerContent, ASDisplayNode) -> ViewController? + private let contentAtPoint: (CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? + private let present: (PeekControllerContent, UIView, CGRect) -> ViewController? private let updateContent: (PeekControllerContent?) -> Void private let activateBySingleTap: Bool public var checkSingleTapActivationAtPoint: ((CGPoint) -> Bool)? @@ -29,16 +29,16 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { private var pressTimer: SwiftSignalKit.Timer? private let candidateContentDisposable = MetaDisposable() - private var candidateContent: (ASDisplayNode, PeekControllerContent)? { + private var candidateContent: (UIView, CGRect, PeekControllerContent)? { didSet { - self.updateContent(self.candidateContent?.1) + self.updateContent(self.candidateContent?.2) } } private var menuActivation: PeerControllerMenuActivation? private weak var presentedController: PeekController? - public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> ViewController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) { + public init(contentAtPoint: @escaping (CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, UIView, CGRect) -> ViewController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) { self.contentAtPoint = contentAtPoint self.present = present self.updateContent = updateContent @@ -254,21 +254,21 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } //print("check received, will process: \(processResult), force: \(forceActivate), state: \(strongSelf.state)") if processResult { - if let (sourceNode, content) = result { + if let (sourceView, sourceRect, content) = result { if let currentContent = strongSelf.candidateContent { - if !currentContent.1.isEqual(to: content) { + if !currentContent.2.isEqual(to: content) { strongSelf.tapLocation = touchLocation - strongSelf.candidateContent = (sourceNode, content) + strongSelf.candidateContent = (sourceView, sourceRect, content) strongSelf.menuActivation = content.menuActivation() if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { - presentedController.sourceNode = { - return sourceNode + presentedController.sourceView = { + return (sourceView, sourceRect) } (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) } } } else { - if let presentedController = strongSelf.present(content, sourceNode) { + if let presentedController = strongSelf.present(content, sourceView, sourceRect) { if let presentedController = presentedController as? PeekController { if forceActivate { strongSelf.candidateContent = nil @@ -276,7 +276,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { (presentedController.displayNode as? PeekControllerNode)?.activateMenu() } } else { - strongSelf.candidateContent = (sourceNode, content) + strongSelf.candidateContent = (sourceView, sourceRect, content) strongSelf.menuActivation = content.menuActivation() strongSelf.presentedController = presentedController diff --git a/submodules/Display/Source/ContextControllerSourceNode.swift b/submodules/Display/Source/ContextControllerSourceNode.swift index 5429722f3b..3e2d8916c5 100644 --- a/submodules/Display/Source/ContextControllerSourceNode.swift +++ b/submodules/Display/Source/ContextControllerSourceNode.swift @@ -271,7 +271,9 @@ open class ContextControllerSourceView: UIView { public weak var additionalActivationProgressLayer: CALayer? public var targetNodeForActivationProgress: ASDisplayNode? public var targetViewForActivationProgress: UIView? + public var targetLayerForActivationProgress: CALayer? public var targetNodeForActivationProgressContentRect: CGRect? + public var useSublayerTransformForActivation: Bool = true override public init(frame: CGRect) { super.init(frame: frame) @@ -297,35 +299,42 @@ open class ContextControllerSourceView: UIView { if let customActivationProgress = strongSelf.customActivationProgress { customActivationProgress(progress, update) } else if strongSelf.animateScale { - let targetView: UIView + let targetLayer: CALayer let targetContentRect: CGRect if let targetNodeForActivationProgress = strongSelf.targetNodeForActivationProgress { - targetView = targetNodeForActivationProgress.view + targetLayer = targetNodeForActivationProgress.layer if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { targetContentRect = targetNodeForActivationProgressContentRect } else { - targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + targetContentRect = CGRect(origin: CGPoint(), size: targetLayer.bounds.size) } } else if let targetViewForActivationProgress = strongSelf.targetViewForActivationProgress { - targetView = targetViewForActivationProgress + targetLayer = targetViewForActivationProgress.layer if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { targetContentRect = targetNodeForActivationProgressContentRect } else { - targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + targetContentRect = CGRect(origin: CGPoint(), size: targetLayer.bounds.size) + } + } else if let targetLayerForActivationProgress = strongSelf.targetLayerForActivationProgress { + targetLayer = targetLayerForActivationProgress + if let targetNodeForActivationProgressContentRect = strongSelf.targetNodeForActivationProgressContentRect { + targetContentRect = targetNodeForActivationProgressContentRect + } else { + targetContentRect = CGRect(origin: CGPoint(), size: targetLayer.bounds.size) } } else { - targetView = strongSelf - targetContentRect = CGRect(origin: CGPoint(), size: targetView.bounds.size) + targetLayer = strongSelf.layer + targetContentRect = CGRect(origin: CGPoint(), size: targetLayer.bounds.size) } let scaleSide = targetContentRect.width let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) let currentScale = 1.0 * (1.0 - progress) + minScale * progress - let originalCenterOffsetX: CGFloat = targetView.bounds.width / 2.0 - targetContentRect.midX + let originalCenterOffsetX: CGFloat = targetLayer.bounds.width / 2.0 - targetContentRect.midX let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale - let originalCenterOffsetY: CGFloat = targetView.bounds.height / 2.0 - targetContentRect.midY + let originalCenterOffsetY: CGFloat = targetLayer.bounds.height / 2.0 - targetContentRect.midY let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX @@ -334,22 +343,38 @@ open class ContextControllerSourceView: UIView { switch update { case .update: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) - targetView.layer.sublayerTransform = sublayerTransform + if strongSelf.useSublayerTransformForActivation { + targetLayer.sublayerTransform = sublayerTransform + } else { + targetLayer.transform = sublayerTransform + } if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .begin: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) - targetView.layer.sublayerTransform = sublayerTransform + if strongSelf.useSublayerTransformForActivation { + targetLayer.sublayerTransform = sublayerTransform + } else { + targetLayer.transform = sublayerTransform + } if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { additionalActivationProgressLayer.transform = sublayerTransform } case .ended: let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) - let previousTransform = targetView.layer.sublayerTransform - targetView.layer.sublayerTransform = sublayerTransform - targetView.layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + if strongSelf.useSublayerTransformForActivation { + let previousTransform = targetLayer.sublayerTransform + targetLayer.sublayerTransform = sublayerTransform + + targetLayer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + } else { + let previousTransform = targetLayer.transform + targetLayer.transform = sublayerTransform + + targetLayer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "transform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + } if let additionalActivationProgressLayer = strongSelf.additionalActivationProgressLayer { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: { diff --git a/submodules/Display/Source/ContextGesture.swift b/submodules/Display/Source/ContextGesture.swift index 7e2eedc52c..7a2a1b0de0 100644 --- a/submodules/Display/Source/ContextGesture.swift +++ b/submodules/Display/Source/ContextGesture.swift @@ -20,9 +20,12 @@ private class TimerTargetWrapper: NSObject { } } -public func cancelParentGestures(view: UIView) { +public func cancelParentGestures(view: UIView, ignore: [UIGestureRecognizer] = []) { if let gestureRecognizers = view.gestureRecognizers { for recognizer in gestureRecognizers { + if ignore.contains(where: { $0 === recognizer }) { + continue + } recognizer.state = .failed } } @@ -66,6 +69,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg public var externalUpdated: ((UIView?, CGPoint) -> Void)? public var externalEnded: (((UIView?, CGPoint)?) -> Void)? public var activatedAfterCompletion: (() -> Void)? + public var cancelGesturesOnActivation: (() -> Void)? override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) @@ -142,11 +146,12 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg strongSelf.animator?.invalidate() strongSelf.activated?(strongSelf, location) strongSelf.wasActivated = true - if let view = strongSelf.view?.superview { + if let view = strongSelf.view { if let window = view.window { cancelOtherGestures(gesture: strongSelf, view: window) } - cancelParentGestures(view: view) + strongSelf.cancelGesturesOnActivation?() + cancelParentGestures(view: view, ignore: [strongSelf]) } strongSelf.state = .began default: diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index eaacde87bd..c5679aabd9 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -938,7 +938,7 @@ open class NavigationController: UINavigationController, ContainableController, rootModalFrame.updateDismissal(transition: transition, progress: effectiveRootModalDismissProgress, additionalProgress: additionalModalFrameProgress, completion: {}) forceStatusBarAnimation = true } else { - rootModalFrame = NavigationModalFrame(theme: self.theme) + rootModalFrame = NavigationModalFrame() self.rootModalFrame = rootModalFrame if let rootContainer = self.rootContainer { var rootContainerNode: ASDisplayNode diff --git a/submodules/Display/Source/Navigation/NavigationModalFrame.swift b/submodules/Display/Source/Navigation/NavigationModalFrame.swift index a4e36ecda3..c1f919be46 100644 --- a/submodules/Display/Source/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Source/Navigation/NavigationModalFrame.swift @@ -34,7 +34,7 @@ private func generateCornerImage(radius: CGFloat, type: CornerType) -> UIImage? }) } -final class NavigationModalFrame: ASDisplayNode { +public final class NavigationModalFrame: ASDisplayNode { private let topShade: ASDisplayNode private let leftShade: ASDisplayNode private let rightShade: ASDisplayNode @@ -50,7 +50,7 @@ final class NavigationModalFrame: ASDisplayNode { private var additionalProgress: CGFloat = 0.0 private var validLayout: ContainerViewLayout? - init(theme: NavigationControllerTheme) { + override public init() { self.topShade = ASDisplayNode() self.topShade.backgroundColor = .black self.leftShade = ASDisplayNode() @@ -81,13 +81,13 @@ final class NavigationModalFrame: ASDisplayNode { self.addSubnode(self.bottomRightCorner) } - func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + public func update(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout self.updateShades(layout: layout, progress: 1.0 - self.progress, additionalProgress: self.additionalProgress, transition: transition, completion: {}) } - func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, additionalProgress: CGFloat, completion: @escaping () -> Void) { + public func updateDismissal(transition: ContainedViewLayoutTransition, progress: CGFloat, additionalProgress: CGFloat, completion: @escaping () -> Void) { self.progress = progress self.additionalProgress = additionalProgress diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 1bb571e473..5c991109d9 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -240,7 +240,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never } - self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in + self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in if let strongSelf = self { if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { var menuItems: [ContextMenuItem] = [] @@ -259,14 +259,14 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } }))) } - return .single((itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))) + return .single((itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))) } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { - let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) controller.visibilityUpdated = { [weak self] visible in if let strongSelf = self { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index a317390966..68f071283e 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -197,7 +197,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never } - self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in + self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in if let strongSelf = self { if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { let accountPeerId = strongSelf.context.account.peerId @@ -212,7 +212,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol } ) |> deliverOnMainQueue - |> map { isStarred, hasPremium -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { @@ -238,7 +238,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol } }))) } - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { })) } else { @@ -248,10 +248,10 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { - let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) controller.visibilityUpdated = { [weak self] visible in if let strongSelf = self { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index 6009f7e493..a5dc7ae6d6 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -343,7 +343,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { func updatePreviewing(animated: Bool) { var isPreviewing = false if let (_, maybeItem) = self.currentState, let interaction = self.interaction, let item = maybeItem { - isPreviewing = interaction.previewedItem == .pack(item) + isPreviewing = interaction.previewedItem == .pack(item.file) } if self.currentIsPreviewing != isPreviewing { self.currentIsPreviewing = isPreviewing diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 7cb8c4b2b2..03355801aa 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -344,7 +344,7 @@ private final class StickerPackContainer: ASDisplayNode { override func didLoad() { super.didLoad() - self.gridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in + self.gridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in if let strongSelf = self { if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { let accountPeerId = strongSelf.context.account.peerId @@ -359,7 +359,7 @@ private final class StickerPackContainer: ASDisplayNode { } ) |> deliverOnMainQueue - |> map { isStarred, hasPremium -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { @@ -385,7 +385,7 @@ private final class StickerPackContainer: ASDisplayNode { } }))) } - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -401,10 +401,10 @@ private final class StickerPackContainer: ASDisplayNode { } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { - let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) controller.visibilityUpdated = { [weak self] visible in if let strongSelf = self { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift index 96c61efd36..07dbc281d7 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift @@ -15,13 +15,13 @@ import AccountContext import AppBundle public enum StickerPreviewPeekItem: Equatable { - case pack(StickerPackItem) + case pack(TelegramMediaFile) case found(FoundStickerItem) public var file: TelegramMediaFile { switch self { - case let .pack(item): - return item.file + case let .pack(file): + return file case let .found(item): return item.file } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index ccf6ae5604..c0eb480146 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -287,6 +287,7 @@ swift_library( "//submodules/TelegramUI/Components/LottieAnimationCache:LottieAnimationCache", "//submodules/TelegramUI/Components/VideoAnimationCache:VideoAnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/TelegramUI/Components/ChatInputPanelContainer:ChatInputPanelContainer", "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", "//submodules/Media/ConvertOpusToAAC:ConvertOpusToAAC", "//submodules/Media/LocalAudioTranscription:LocalAudioTranscription", diff --git a/submodules/TelegramUI/Components/AnimationCache/BUILD b/submodules/TelegramUI/Components/AnimationCache/BUILD index 3e79cc1f13..e655aa9fe6 100644 --- a/submodules/TelegramUI/Components/AnimationCache/BUILD +++ b/submodules/TelegramUI/Components/AnimationCache/BUILD @@ -13,7 +13,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/CryptoUtils:CryptoUtils", "//submodules/ManagedFile:ManagedFile", - "//submodules/TelegramUI/Components/AnimationCache/DCT:DCT", + "//submodules/TelegramUI/Components/AnimationCache/ImageDCT:ImageDCT", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/DCT.h b/submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/DCT.h deleted file mode 100644 index 01fa6dc26c..0000000000 --- a/submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/DCT.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef DctImageTransform_h -#define DctImageTransform_h - -#import - -#import - -NSData *generateForwardDctData(int quality); -void performForwardDct(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow, NSData *dctData); - -NSData *generateInverseDctData(int quality); -void performInverseDct(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow, NSData *idctData); - -#endif /* DctImageTransform_h */ diff --git a/submodules/TelegramUI/Components/AnimationCache/DCT/Sources/DCT.m b/submodules/TelegramUI/Components/AnimationCache/DCT/Sources/DCT.m deleted file mode 100644 index b01c769007..0000000000 --- a/submodules/TelegramUI/Components/AnimationCache/DCT/Sources/DCT.m +++ /dev/null @@ -1,991 +0,0 @@ -#import - -typedef long JLONG; - -typedef unsigned char JSAMPLE; -#define GETJSAMPLE(value) ((int)(value)) - -#define MAXJSAMPLE 255 -#define CENTERJSAMPLE 128 - -typedef short JCOEF; - -typedef unsigned int JDIMENSION; - -#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ - -#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ - -typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ -#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ - -/* Various constants determining the sizes of things. - * All of these are specified by the JPEG standard, so don't change them - * if you want to be compatible. - */ - -#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ -#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ -#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ -#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ -#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ -#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ -#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ -/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; - * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. - * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU - * to handle it. We even let you do this from the jconfig.h file. However, - * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe - * sometimes emits noncompliant files doesn't mean you should too. - */ -#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ -#ifndef D_MAX_BLOCKS_IN_MCU -#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ -#endif - - -/* Data structures for images (arrays of samples and of DCT coefficients). - */ - -typedef JSAMPLE *JSAMPROW; /* ptr to one image row of pixel samples. */ -typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ -typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ - -typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ -typedef JBLOCK *JBLOCKROW; /* pointer to one row of coefficient blocks */ -typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ -typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ - -typedef JCOEF *JCOEFPTR; /* useful in a couple of places */ - -#include - -/* jsimd_idct_ifast_neon() performs dequantization and a fast, not so accurate - * inverse DCT (Discrete Cosine Transform) on one block of coefficients. It - * uses the same calculations and produces exactly the same output as IJG's - * original jpeg_idct_ifast() function, which can be found in jidctfst.c. - * - * Scaled integer constants are used to avoid floating-point arithmetic: - * 0.082392200 = 2688 * 2^-15 - * 0.414213562 = 13568 * 2^-15 - * 0.847759065 = 27776 * 2^-15 - * 0.613125930 = 20096 * 2^-15 - * - * See jidctfst.c for further details of the IDCT algorithm. Where possible, - * the variable names and comments here in jsimd_idct_ifast_neon() match up - * with those in jpeg_idct_ifast(). - */ - -#define PASS1_BITS 2 - -#define F_0_082 2688 -#define F_0_414 13568 -#define F_0_847 27776 -#define F_0_613 20096 - - -__attribute__((aligned(16))) static const int16_t jsimd_idct_ifast_neon_consts[] = { - F_0_082, F_0_414, F_0_847, F_0_613 -}; - -#define F_0_382 12544 -#define F_0_541 17792 -#define F_0_707 23168 -#define F_0_306 9984 - - -__attribute__((aligned(16))) static const int16_t jsimd_fdct_ifast_neon_consts[] = { - F_0_382, F_0_541, F_0_707, F_0_306 -}; - -typedef short DCTELEM; /* prefer 16 bit with SIMD for parellelism */ -typedef unsigned short UDCTELEM; -typedef unsigned int UDCTELEM2; - -static void jsimd_fdct_ifast_neon(DCTELEM *data) { - /* Load an 8x8 block of samples into Neon registers. De-interleaving loads - * are used, followed by vuzp to transpose the block such that we have a - * column of samples per vector - allowing all rows to be processed at once. - */ - int16x8x4_t data1 = vld4q_s16(data); - int16x8x4_t data2 = vld4q_s16(data + 4 * DCTSIZE); - - int16x8x2_t cols_04 = vuzpq_s16(data1.val[0], data2.val[0]); - int16x8x2_t cols_15 = vuzpq_s16(data1.val[1], data2.val[1]); - int16x8x2_t cols_26 = vuzpq_s16(data1.val[2], data2.val[2]); - int16x8x2_t cols_37 = vuzpq_s16(data1.val[3], data2.val[3]); - - int16x8_t col0 = cols_04.val[0]; - int16x8_t col1 = cols_15.val[0]; - int16x8_t col2 = cols_26.val[0]; - int16x8_t col3 = cols_37.val[0]; - int16x8_t col4 = cols_04.val[1]; - int16x8_t col5 = cols_15.val[1]; - int16x8_t col6 = cols_26.val[1]; - int16x8_t col7 = cols_37.val[1]; - - /* Pass 1: process rows. */ - - /* Load DCT conversion constants. */ - const int16x4_t consts = vld1_s16(jsimd_fdct_ifast_neon_consts); - - int16x8_t tmp0 = vaddq_s16(col0, col7); - int16x8_t tmp7 = vsubq_s16(col0, col7); - int16x8_t tmp1 = vaddq_s16(col1, col6); - int16x8_t tmp6 = vsubq_s16(col1, col6); - int16x8_t tmp2 = vaddq_s16(col2, col5); - int16x8_t tmp5 = vsubq_s16(col2, col5); - int16x8_t tmp3 = vaddq_s16(col3, col4); - int16x8_t tmp4 = vsubq_s16(col3, col4); - - /* Even part */ - int16x8_t tmp10 = vaddq_s16(tmp0, tmp3); /* phase 2 */ - int16x8_t tmp13 = vsubq_s16(tmp0, tmp3); - int16x8_t tmp11 = vaddq_s16(tmp1, tmp2); - int16x8_t tmp12 = vsubq_s16(tmp1, tmp2); - - col0 = vaddq_s16(tmp10, tmp11); /* phase 3 */ - col4 = vsubq_s16(tmp10, tmp11); - - int16x8_t z1 = vqdmulhq_lane_s16(vaddq_s16(tmp12, tmp13), consts, 2); - col2 = vaddq_s16(tmp13, z1); /* phase 5 */ - col6 = vsubq_s16(tmp13, z1); - - /* Odd part */ - tmp10 = vaddq_s16(tmp4, tmp5); /* phase 2 */ - tmp11 = vaddq_s16(tmp5, tmp6); - tmp12 = vaddq_s16(tmp6, tmp7); - - int16x8_t z5 = vqdmulhq_lane_s16(vsubq_s16(tmp10, tmp12), consts, 0); - int16x8_t z2 = vqdmulhq_lane_s16(tmp10, consts, 1); - z2 = vaddq_s16(z2, z5); - int16x8_t z4 = vqdmulhq_lane_s16(tmp12, consts, 3); - z5 = vaddq_s16(tmp12, z5); - z4 = vaddq_s16(z4, z5); - int16x8_t z3 = vqdmulhq_lane_s16(tmp11, consts, 2); - - int16x8_t z11 = vaddq_s16(tmp7, z3); /* phase 5 */ - int16x8_t z13 = vsubq_s16(tmp7, z3); - - col5 = vaddq_s16(z13, z2); /* phase 6 */ - col3 = vsubq_s16(z13, z2); - col1 = vaddq_s16(z11, z4); - col7 = vsubq_s16(z11, z4); - - /* Transpose to work on columns in pass 2. */ - int16x8x2_t cols_01 = vtrnq_s16(col0, col1); - int16x8x2_t cols_23 = vtrnq_s16(col2, col3); - int16x8x2_t cols_45 = vtrnq_s16(col4, col5); - int16x8x2_t cols_67 = vtrnq_s16(col6, col7); - - int32x4x2_t cols_0145_l = vtrnq_s32(vreinterpretq_s32_s16(cols_01.val[0]), - vreinterpretq_s32_s16(cols_45.val[0])); - int32x4x2_t cols_0145_h = vtrnq_s32(vreinterpretq_s32_s16(cols_01.val[1]), - vreinterpretq_s32_s16(cols_45.val[1])); - int32x4x2_t cols_2367_l = vtrnq_s32(vreinterpretq_s32_s16(cols_23.val[0]), - vreinterpretq_s32_s16(cols_67.val[0])); - int32x4x2_t cols_2367_h = vtrnq_s32(vreinterpretq_s32_s16(cols_23.val[1]), - vreinterpretq_s32_s16(cols_67.val[1])); - - int32x4x2_t rows_04 = vzipq_s32(cols_0145_l.val[0], cols_2367_l.val[0]); - int32x4x2_t rows_15 = vzipq_s32(cols_0145_h.val[0], cols_2367_h.val[0]); - int32x4x2_t rows_26 = vzipq_s32(cols_0145_l.val[1], cols_2367_l.val[1]); - int32x4x2_t rows_37 = vzipq_s32(cols_0145_h.val[1], cols_2367_h.val[1]); - - int16x8_t row0 = vreinterpretq_s16_s32(rows_04.val[0]); - int16x8_t row1 = vreinterpretq_s16_s32(rows_15.val[0]); - int16x8_t row2 = vreinterpretq_s16_s32(rows_26.val[0]); - int16x8_t row3 = vreinterpretq_s16_s32(rows_37.val[0]); - int16x8_t row4 = vreinterpretq_s16_s32(rows_04.val[1]); - int16x8_t row5 = vreinterpretq_s16_s32(rows_15.val[1]); - int16x8_t row6 = vreinterpretq_s16_s32(rows_26.val[1]); - int16x8_t row7 = vreinterpretq_s16_s32(rows_37.val[1]); - - /* Pass 2: process columns. */ - - tmp0 = vaddq_s16(row0, row7); - tmp7 = vsubq_s16(row0, row7); - tmp1 = vaddq_s16(row1, row6); - tmp6 = vsubq_s16(row1, row6); - tmp2 = vaddq_s16(row2, row5); - tmp5 = vsubq_s16(row2, row5); - tmp3 = vaddq_s16(row3, row4); - tmp4 = vsubq_s16(row3, row4); - - /* Even part */ - tmp10 = vaddq_s16(tmp0, tmp3); /* phase 2 */ - tmp13 = vsubq_s16(tmp0, tmp3); - tmp11 = vaddq_s16(tmp1, tmp2); - tmp12 = vsubq_s16(tmp1, tmp2); - - row0 = vaddq_s16(tmp10, tmp11); /* phase 3 */ - row4 = vsubq_s16(tmp10, tmp11); - - z1 = vqdmulhq_lane_s16(vaddq_s16(tmp12, tmp13), consts, 2); - row2 = vaddq_s16(tmp13, z1); /* phase 5 */ - row6 = vsubq_s16(tmp13, z1); - - /* Odd part */ - tmp10 = vaddq_s16(tmp4, tmp5); /* phase 2 */ - tmp11 = vaddq_s16(tmp5, tmp6); - tmp12 = vaddq_s16(tmp6, tmp7); - - z5 = vqdmulhq_lane_s16(vsubq_s16(tmp10, tmp12), consts, 0); - z2 = vqdmulhq_lane_s16(tmp10, consts, 1); - z2 = vaddq_s16(z2, z5); - z4 = vqdmulhq_lane_s16(tmp12, consts, 3); - z5 = vaddq_s16(tmp12, z5); - z4 = vaddq_s16(z4, z5); - z3 = vqdmulhq_lane_s16(tmp11, consts, 2); - - z11 = vaddq_s16(tmp7, z3); /* phase 5 */ - z13 = vsubq_s16(tmp7, z3); - - row5 = vaddq_s16(z13, z2); /* phase 6 */ - row3 = vsubq_s16(z13, z2); - row1 = vaddq_s16(z11, z4); - row7 = vsubq_s16(z11, z4); - - vst1q_s16(data + 0 * DCTSIZE, row0); - vst1q_s16(data + 1 * DCTSIZE, row1); - vst1q_s16(data + 2 * DCTSIZE, row2); - vst1q_s16(data + 3 * DCTSIZE, row3); - vst1q_s16(data + 4 * DCTSIZE, row4); - vst1q_s16(data + 5 * DCTSIZE, row5); - vst1q_s16(data + 6 * DCTSIZE, row6); - vst1q_s16(data + 7 * DCTSIZE, row7); -} - -static void jsimd_idct_ifast_neon(void *dct_table, JCOEFPTR coef_block, - JSAMPROW output_buf) -{ - IFAST_MULT_TYPE *quantptr = dct_table; - - /* Load DCT coefficients. */ - int16x8_t row0 = vld1q_s16(coef_block + 0 * DCTSIZE); - int16x8_t row1 = vld1q_s16(coef_block + 1 * DCTSIZE); - int16x8_t row2 = vld1q_s16(coef_block + 2 * DCTSIZE); - int16x8_t row3 = vld1q_s16(coef_block + 3 * DCTSIZE); - int16x8_t row4 = vld1q_s16(coef_block + 4 * DCTSIZE); - int16x8_t row5 = vld1q_s16(coef_block + 5 * DCTSIZE); - int16x8_t row6 = vld1q_s16(coef_block + 6 * DCTSIZE); - int16x8_t row7 = vld1q_s16(coef_block + 7 * DCTSIZE); - - /* Load quantization table values for DC coefficients. */ - int16x8_t quant_row0 = vld1q_s16(quantptr + 0 * DCTSIZE); - /* Dequantize DC coefficients. */ - row0 = vmulq_s16(row0, quant_row0); - - /* Construct bitmap to test if all AC coefficients are 0. */ - int16x8_t bitmap = vorrq_s16(row1, row2); - bitmap = vorrq_s16(bitmap, row3); - bitmap = vorrq_s16(bitmap, row4); - bitmap = vorrq_s16(bitmap, row5); - bitmap = vorrq_s16(bitmap, row6); - bitmap = vorrq_s16(bitmap, row7); - - int64_t left_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 0); - int64_t right_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 1); - - /* Load IDCT conversion constants. */ - const int16x4_t consts = vld1_s16(jsimd_idct_ifast_neon_consts); - - if (left_ac_bitmap == 0 && right_ac_bitmap == 0) { - /* All AC coefficients are zero. - * Compute DC values and duplicate into vectors. - */ - int16x8_t dcval = row0; - row1 = dcval; - row2 = dcval; - row3 = dcval; - row4 = dcval; - row5 = dcval; - row6 = dcval; - row7 = dcval; - } else if (left_ac_bitmap == 0) { - /* AC coefficients are zero for columns 0, 1, 2, and 3. - * Use DC values for these columns. - */ - int16x4_t dcval = vget_low_s16(row0); - - /* Commence regular fast IDCT computation for columns 4, 5, 6, and 7. */ - - /* Load quantization table. */ - int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE + 4); - int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE + 4); - int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE + 4); - int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE + 4); - int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE + 4); - int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE + 4); - int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE + 4); - - /* Even part: dequantize DCT coefficients. */ - int16x4_t tmp0 = vget_high_s16(row0); - int16x4_t tmp1 = vmul_s16(vget_high_s16(row2), quant_row2); - int16x4_t tmp2 = vmul_s16(vget_high_s16(row4), quant_row4); - int16x4_t tmp3 = vmul_s16(vget_high_s16(row6), quant_row6); - - int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */ - int16x4_t tmp11 = vsub_s16(tmp0, tmp2); - - int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */ - int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3); - int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1); - tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3); - tmp12 = vsub_s16(tmp12, tmp13); - - tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */ - tmp3 = vsub_s16(tmp10, tmp13); - tmp1 = vadd_s16(tmp11, tmp12); - tmp2 = vsub_s16(tmp11, tmp12); - - /* Odd part: dequantize DCT coefficients. */ - int16x4_t tmp4 = vmul_s16(vget_high_s16(row1), quant_row1); - int16x4_t tmp5 = vmul_s16(vget_high_s16(row3), quant_row3); - int16x4_t tmp6 = vmul_s16(vget_high_s16(row5), quant_row5); - int16x4_t tmp7 = vmul_s16(vget_high_s16(row7), quant_row7); - - int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */ - int16x4_t neg_z10 = vsub_s16(tmp5, tmp6); - int16x4_t z11 = vadd_s16(tmp4, tmp7); - int16x4_t z12 = vsub_s16(tmp4, tmp7); - - tmp7 = vadd_s16(z11, z13); /* phase 5 */ - int16x4_t z11_sub_z13 = vsub_s16(z11, z13); - tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1); - tmp11 = vadd_s16(tmp11, z11_sub_z13); - - int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10); - int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2); - z5 = vadd_s16(z5, z10_add_z12); - tmp10 = vqdmulh_lane_s16(z12, consts, 0); - tmp10 = vadd_s16(tmp10, z12); - tmp10 = vsub_s16(tmp10, z5); - tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3); - tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10)); - tmp12 = vadd_s16(tmp12, z5); - - tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */ - tmp5 = vsub_s16(tmp11, tmp6); - tmp4 = vadd_s16(tmp10, tmp5); - - row0 = vcombine_s16(dcval, vadd_s16(tmp0, tmp7)); - row7 = vcombine_s16(dcval, vsub_s16(tmp0, tmp7)); - row1 = vcombine_s16(dcval, vadd_s16(tmp1, tmp6)); - row6 = vcombine_s16(dcval, vsub_s16(tmp1, tmp6)); - row2 = vcombine_s16(dcval, vadd_s16(tmp2, tmp5)); - row5 = vcombine_s16(dcval, vsub_s16(tmp2, tmp5)); - row4 = vcombine_s16(dcval, vadd_s16(tmp3, tmp4)); - row3 = vcombine_s16(dcval, vsub_s16(tmp3, tmp4)); - } else if (right_ac_bitmap == 0) { - /* AC coefficients are zero for columns 4, 5, 6, and 7. - * Use DC values for these columns. - */ - int16x4_t dcval = vget_high_s16(row0); - - /* Commence regular fast IDCT computation for columns 0, 1, 2, and 3. */ - - /* Load quantization table. */ - int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE); - int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE); - int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE); - int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE); - int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE); - int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE); - int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE); - - /* Even part: dequantize DCT coefficients. */ - int16x4_t tmp0 = vget_low_s16(row0); - int16x4_t tmp1 = vmul_s16(vget_low_s16(row2), quant_row2); - int16x4_t tmp2 = vmul_s16(vget_low_s16(row4), quant_row4); - int16x4_t tmp3 = vmul_s16(vget_low_s16(row6), quant_row6); - - int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */ - int16x4_t tmp11 = vsub_s16(tmp0, tmp2); - - int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */ - int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3); - int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1); - tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3); - tmp12 = vsub_s16(tmp12, tmp13); - - tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */ - tmp3 = vsub_s16(tmp10, tmp13); - tmp1 = vadd_s16(tmp11, tmp12); - tmp2 = vsub_s16(tmp11, tmp12); - - /* Odd part: dequantize DCT coefficients. */ - int16x4_t tmp4 = vmul_s16(vget_low_s16(row1), quant_row1); - int16x4_t tmp5 = vmul_s16(vget_low_s16(row3), quant_row3); - int16x4_t tmp6 = vmul_s16(vget_low_s16(row5), quant_row5); - int16x4_t tmp7 = vmul_s16(vget_low_s16(row7), quant_row7); - - int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */ - int16x4_t neg_z10 = vsub_s16(tmp5, tmp6); - int16x4_t z11 = vadd_s16(tmp4, tmp7); - int16x4_t z12 = vsub_s16(tmp4, tmp7); - - tmp7 = vadd_s16(z11, z13); /* phase 5 */ - int16x4_t z11_sub_z13 = vsub_s16(z11, z13); - tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1); - tmp11 = vadd_s16(tmp11, z11_sub_z13); - - int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10); - int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2); - z5 = vadd_s16(z5, z10_add_z12); - tmp10 = vqdmulh_lane_s16(z12, consts, 0); - tmp10 = vadd_s16(tmp10, z12); - tmp10 = vsub_s16(tmp10, z5); - tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3); - tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10)); - tmp12 = vadd_s16(tmp12, z5); - - tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */ - tmp5 = vsub_s16(tmp11, tmp6); - tmp4 = vadd_s16(tmp10, tmp5); - - row0 = vcombine_s16(vadd_s16(tmp0, tmp7), dcval); - row7 = vcombine_s16(vsub_s16(tmp0, tmp7), dcval); - row1 = vcombine_s16(vadd_s16(tmp1, tmp6), dcval); - row6 = vcombine_s16(vsub_s16(tmp1, tmp6), dcval); - row2 = vcombine_s16(vadd_s16(tmp2, tmp5), dcval); - row5 = vcombine_s16(vsub_s16(tmp2, tmp5), dcval); - row4 = vcombine_s16(vadd_s16(tmp3, tmp4), dcval); - row3 = vcombine_s16(vsub_s16(tmp3, tmp4), dcval); - } else { - /* Some AC coefficients are non-zero; full IDCT calculation required. */ - - /* Load quantization table. */ - int16x8_t quant_row1 = vld1q_s16(quantptr + 1 * DCTSIZE); - int16x8_t quant_row2 = vld1q_s16(quantptr + 2 * DCTSIZE); - int16x8_t quant_row3 = vld1q_s16(quantptr + 3 * DCTSIZE); - int16x8_t quant_row4 = vld1q_s16(quantptr + 4 * DCTSIZE); - int16x8_t quant_row5 = vld1q_s16(quantptr + 5 * DCTSIZE); - int16x8_t quant_row6 = vld1q_s16(quantptr + 6 * DCTSIZE); - int16x8_t quant_row7 = vld1q_s16(quantptr + 7 * DCTSIZE); - - /* Even part: dequantize DCT coefficients. */ - int16x8_t tmp0 = row0; - int16x8_t tmp1 = vmulq_s16(row2, quant_row2); - int16x8_t tmp2 = vmulq_s16(row4, quant_row4); - int16x8_t tmp3 = vmulq_s16(row6, quant_row6); - - int16x8_t tmp10 = vaddq_s16(tmp0, tmp2); /* phase 3 */ - int16x8_t tmp11 = vsubq_s16(tmp0, tmp2); - - int16x8_t tmp13 = vaddq_s16(tmp1, tmp3); /* phases 5-3 */ - int16x8_t tmp1_sub_tmp3 = vsubq_s16(tmp1, tmp3); - int16x8_t tmp12 = vqdmulhq_lane_s16(tmp1_sub_tmp3, consts, 1); - tmp12 = vaddq_s16(tmp12, tmp1_sub_tmp3); - tmp12 = vsubq_s16(tmp12, tmp13); - - tmp0 = vaddq_s16(tmp10, tmp13); /* phase 2 */ - tmp3 = vsubq_s16(tmp10, tmp13); - tmp1 = vaddq_s16(tmp11, tmp12); - tmp2 = vsubq_s16(tmp11, tmp12); - - /* Odd part: dequantize DCT coefficients. */ - int16x8_t tmp4 = vmulq_s16(row1, quant_row1); - int16x8_t tmp5 = vmulq_s16(row3, quant_row3); - int16x8_t tmp6 = vmulq_s16(row5, quant_row5); - int16x8_t tmp7 = vmulq_s16(row7, quant_row7); - - int16x8_t z13 = vaddq_s16(tmp6, tmp5); /* phase 6 */ - int16x8_t neg_z10 = vsubq_s16(tmp5, tmp6); - int16x8_t z11 = vaddq_s16(tmp4, tmp7); - int16x8_t z12 = vsubq_s16(tmp4, tmp7); - - tmp7 = vaddq_s16(z11, z13); /* phase 5 */ - int16x8_t z11_sub_z13 = vsubq_s16(z11, z13); - tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1); - tmp11 = vaddq_s16(tmp11, z11_sub_z13); - - int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10); - int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2); - z5 = vaddq_s16(z5, z10_add_z12); - tmp10 = vqdmulhq_lane_s16(z12, consts, 0); - tmp10 = vaddq_s16(tmp10, z12); - tmp10 = vsubq_s16(tmp10, z5); - tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3); - tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10)); - tmp12 = vaddq_s16(tmp12, z5); - - tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */ - tmp5 = vsubq_s16(tmp11, tmp6); - tmp4 = vaddq_s16(tmp10, tmp5); - - row0 = vaddq_s16(tmp0, tmp7); - row7 = vsubq_s16(tmp0, tmp7); - row1 = vaddq_s16(tmp1, tmp6); - row6 = vsubq_s16(tmp1, tmp6); - row2 = vaddq_s16(tmp2, tmp5); - row5 = vsubq_s16(tmp2, tmp5); - row4 = vaddq_s16(tmp3, tmp4); - row3 = vsubq_s16(tmp3, tmp4); - } - - /* Transpose rows to work on columns in pass 2. */ - int16x8x2_t rows_01 = vtrnq_s16(row0, row1); - int16x8x2_t rows_23 = vtrnq_s16(row2, row3); - int16x8x2_t rows_45 = vtrnq_s16(row4, row5); - int16x8x2_t rows_67 = vtrnq_s16(row6, row7); - - int32x4x2_t rows_0145_l = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[0]), - vreinterpretq_s32_s16(rows_45.val[0])); - int32x4x2_t rows_0145_h = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[1]), - vreinterpretq_s32_s16(rows_45.val[1])); - int32x4x2_t rows_2367_l = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[0]), - vreinterpretq_s32_s16(rows_67.val[0])); - int32x4x2_t rows_2367_h = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[1]), - vreinterpretq_s32_s16(rows_67.val[1])); - - int32x4x2_t cols_04 = vzipq_s32(rows_0145_l.val[0], rows_2367_l.val[0]); - int32x4x2_t cols_15 = vzipq_s32(rows_0145_h.val[0], rows_2367_h.val[0]); - int32x4x2_t cols_26 = vzipq_s32(rows_0145_l.val[1], rows_2367_l.val[1]); - int32x4x2_t cols_37 = vzipq_s32(rows_0145_h.val[1], rows_2367_h.val[1]); - - int16x8_t col0 = vreinterpretq_s16_s32(cols_04.val[0]); - int16x8_t col1 = vreinterpretq_s16_s32(cols_15.val[0]); - int16x8_t col2 = vreinterpretq_s16_s32(cols_26.val[0]); - int16x8_t col3 = vreinterpretq_s16_s32(cols_37.val[0]); - int16x8_t col4 = vreinterpretq_s16_s32(cols_04.val[1]); - int16x8_t col5 = vreinterpretq_s16_s32(cols_15.val[1]); - int16x8_t col6 = vreinterpretq_s16_s32(cols_26.val[1]); - int16x8_t col7 = vreinterpretq_s16_s32(cols_37.val[1]); - - /* 1-D IDCT, pass 2 */ - - /* Even part */ - int16x8_t tmp10 = vaddq_s16(col0, col4); - int16x8_t tmp11 = vsubq_s16(col0, col4); - - int16x8_t tmp13 = vaddq_s16(col2, col6); - int16x8_t col2_sub_col6 = vsubq_s16(col2, col6); - int16x8_t tmp12 = vqdmulhq_lane_s16(col2_sub_col6, consts, 1); - tmp12 = vaddq_s16(tmp12, col2_sub_col6); - tmp12 = vsubq_s16(tmp12, tmp13); - - int16x8_t tmp0 = vaddq_s16(tmp10, tmp13); - int16x8_t tmp3 = vsubq_s16(tmp10, tmp13); - int16x8_t tmp1 = vaddq_s16(tmp11, tmp12); - int16x8_t tmp2 = vsubq_s16(tmp11, tmp12); - - /* Odd part */ - int16x8_t z13 = vaddq_s16(col5, col3); - int16x8_t neg_z10 = vsubq_s16(col3, col5); - int16x8_t z11 = vaddq_s16(col1, col7); - int16x8_t z12 = vsubq_s16(col1, col7); - - int16x8_t tmp7 = vaddq_s16(z11, z13); /* phase 5 */ - int16x8_t z11_sub_z13 = vsubq_s16(z11, z13); - tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1); - tmp11 = vaddq_s16(tmp11, z11_sub_z13); - - int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10); - int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2); - z5 = vaddq_s16(z5, z10_add_z12); - tmp10 = vqdmulhq_lane_s16(z12, consts, 0); - tmp10 = vaddq_s16(tmp10, z12); - tmp10 = vsubq_s16(tmp10, z5); - tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3); - tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10)); - tmp12 = vaddq_s16(tmp12, z5); - - int16x8_t tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */ - int16x8_t tmp5 = vsubq_s16(tmp11, tmp6); - int16x8_t tmp4 = vaddq_s16(tmp10, tmp5); - - col0 = vaddq_s16(tmp0, tmp7); - col7 = vsubq_s16(tmp0, tmp7); - col1 = vaddq_s16(tmp1, tmp6); - col6 = vsubq_s16(tmp1, tmp6); - col2 = vaddq_s16(tmp2, tmp5); - col5 = vsubq_s16(tmp2, tmp5); - col4 = vaddq_s16(tmp3, tmp4); - col3 = vsubq_s16(tmp3, tmp4); - - /* Scale down by a factor of 8, narrowing to 8-bit. */ - int8x16_t cols_01_s8 = vcombine_s8(vqshrn_n_s16(col0, PASS1_BITS + 3), - vqshrn_n_s16(col1, PASS1_BITS + 3)); - int8x16_t cols_45_s8 = vcombine_s8(vqshrn_n_s16(col4, PASS1_BITS + 3), - vqshrn_n_s16(col5, PASS1_BITS + 3)); - int8x16_t cols_23_s8 = vcombine_s8(vqshrn_n_s16(col2, PASS1_BITS + 3), - vqshrn_n_s16(col3, PASS1_BITS + 3)); - int8x16_t cols_67_s8 = vcombine_s8(vqshrn_n_s16(col6, PASS1_BITS + 3), - vqshrn_n_s16(col7, PASS1_BITS + 3)); - /* Clamp to range [0-255]. */ - uint8x16_t cols_01 = - vreinterpretq_u8_s8 - (vaddq_s8(cols_01_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); - uint8x16_t cols_45 = - vreinterpretq_u8_s8 - (vaddq_s8(cols_45_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); - uint8x16_t cols_23 = - vreinterpretq_u8_s8 - (vaddq_s8(cols_23_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); - uint8x16_t cols_67 = - vreinterpretq_u8_s8 - (vaddq_s8(cols_67_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); - - /* Transpose block to prepare for store. */ - uint32x4x2_t cols_0415 = vzipq_u32(vreinterpretq_u32_u8(cols_01), - vreinterpretq_u32_u8(cols_45)); - uint32x4x2_t cols_2637 = vzipq_u32(vreinterpretq_u32_u8(cols_23), - vreinterpretq_u32_u8(cols_67)); - - uint8x16x2_t cols_0145 = vtrnq_u8(vreinterpretq_u8_u32(cols_0415.val[0]), - vreinterpretq_u8_u32(cols_0415.val[1])); - uint8x16x2_t cols_2367 = vtrnq_u8(vreinterpretq_u8_u32(cols_2637.val[0]), - vreinterpretq_u8_u32(cols_2637.val[1])); - uint16x8x2_t rows_0426 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[0]), - vreinterpretq_u16_u8(cols_2367.val[0])); - uint16x8x2_t rows_1537 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[1]), - vreinterpretq_u16_u8(cols_2367.val[1])); - - uint8x16_t rows_04 = vreinterpretq_u8_u16(rows_0426.val[0]); - uint8x16_t rows_15 = vreinterpretq_u8_u16(rows_1537.val[0]); - uint8x16_t rows_26 = vreinterpretq_u8_u16(rows_0426.val[1]); - uint8x16_t rows_37 = vreinterpretq_u8_u16(rows_1537.val[1]); - - JSAMPROW outptr0 = output_buf + DCTSIZE * 0; - JSAMPROW outptr1 = output_buf + DCTSIZE * 1; - JSAMPROW outptr2 = output_buf + DCTSIZE * 2; - JSAMPROW outptr3 = output_buf + DCTSIZE * 3; - JSAMPROW outptr4 = output_buf + DCTSIZE * 4; - JSAMPROW outptr5 = output_buf + DCTSIZE * 5; - JSAMPROW outptr6 = output_buf + DCTSIZE * 6; - JSAMPROW outptr7 = output_buf + DCTSIZE * 7; - - /* Store DCT block to memory. */ - vst1q_lane_u64((uint64_t *)outptr0, vreinterpretq_u64_u8(rows_04), 0); - vst1q_lane_u64((uint64_t *)outptr1, vreinterpretq_u64_u8(rows_15), 0); - vst1q_lane_u64((uint64_t *)outptr2, vreinterpretq_u64_u8(rows_26), 0); - vst1q_lane_u64((uint64_t *)outptr3, vreinterpretq_u64_u8(rows_37), 0); - vst1q_lane_u64((uint64_t *)outptr4, vreinterpretq_u64_u8(rows_04), 1); - vst1q_lane_u64((uint64_t *)outptr5, vreinterpretq_u64_u8(rows_15), 1); - vst1q_lane_u64((uint64_t *)outptr6, vreinterpretq_u64_u8(rows_26), 1); - vst1q_lane_u64((uint64_t *)outptr7, vreinterpretq_u64_u8(rows_37), 1); -} - -static int flss(uint16_t val) { - int bit; - - bit = 16; - - if (!val) - return 0; - - if (!(val & 0xff00)) { - bit -= 8; - val <<= 8; - } - if (!(val & 0xf000)) { - bit -= 4; - val <<= 4; - } - if (!(val & 0xc000)) { - bit -= 2; - val <<= 2; - } - if (!(val & 0x8000)) { - bit -= 1; - val <<= 1; - } - - return bit; -} - -static int compute_reciprocal(uint16_t divisor, DCTELEM *dtbl) { - UDCTELEM2 fq, fr; - UDCTELEM c; - int b, r; - - if (divisor == 1) { - /* divisor == 1 means unquantized, so these reciprocal/correction/shift - * values will cause the C quantization algorithm to act like the - * identity function. Since only the C quantization algorithm is used in - * these cases, the scale value is irrelevant. - */ - dtbl[DCTSIZE2 * 0] = (DCTELEM)1; /* reciprocal */ - dtbl[DCTSIZE2 * 1] = (DCTELEM)0; /* correction */ - dtbl[DCTSIZE2 * 2] = (DCTELEM)1; /* scale */ - dtbl[DCTSIZE2 * 3] = -(DCTELEM)(sizeof(DCTELEM) * 8); /* shift */ - return 0; - } - - b = flss(divisor) - 1; - r = sizeof(DCTELEM) * 8 + b; - - fq = ((UDCTELEM2)1 << r) / divisor; - fr = ((UDCTELEM2)1 << r) % divisor; - - c = divisor / 2; /* for rounding */ - - if (fr == 0) { /* divisor is power of two */ - /* fq will be one bit too large to fit in DCTELEM, so adjust */ - fq >>= 1; - r--; - } else if (fr <= (divisor / 2U)) { /* fractional part is < 0.5 */ - c++; - } else { /* fractional part is > 0.5 */ - fq++; - } - - dtbl[DCTSIZE2 * 0] = (DCTELEM)fq; /* reciprocal */ - dtbl[DCTSIZE2 * 1] = (DCTELEM)c; /* correction + roundfactor */ -#ifdef WITH_SIMD - dtbl[DCTSIZE2 * 2] = (DCTELEM)(1 << (sizeof(DCTELEM) * 8 * 2 - r)); /* scale */ -#else - dtbl[DCTSIZE2 * 2] = 1; -#endif - dtbl[DCTSIZE2 * 3] = (DCTELEM)r - sizeof(DCTELEM) * 8; /* shift */ - - if (r <= 16) return 0; - else return 1; -} - -#define DESCALE(x, n) RIGHT_SHIFT(x, n) - - -/* Multiply a DCTELEM variable by an JLONG constant, and immediately - * descale to yield a DCTELEM result. - */ - -#define MULTIPLY(var, const) ((DCTELEM)DESCALE((var) * (const), CONST_BITS)) -#define MULTIPLY16V16(var1, var2) ((var1) * (var2)) - -static DCTELEM std_luminance_quant_tbl[DCTSIZE2] = { - 16, 11, 10, 16, 24, 40, 51, 61, - 12, 12, 14, 19, 26, 58, 60, 55, - 14, 13, 16, 24, 40, 57, 69, 56, - 14, 17, 22, 29, 51, 87, 80, 62, - 18, 22, 37, 56, 68, 109, 103, 77, - 24, 35, 55, 64, 81, 104, 113, 92, - 49, 64, 78, 87, 103, 121, 120, 101, - 72, 92, 95, 98, 112, 100, 103, 99 -}; - -static int jpeg_quality_scaling(int quality) -/* Convert a user-specified quality rating to a percentage scaling factor - * for an underlying quantization table, using our recommended scaling curve. - * The input 'quality' factor should be 0 (terrible) to 100 (very good). - */ -{ - /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ - if (quality <= 0) quality = 1; - if (quality > 100) quality = 100; - - /* The basic table is used as-is (scaling 100) for a quality of 50. - * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; - * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table - * to make all the table entries 1 (hence, minimum quantization loss). - * Qualities 1..50 are converted to scaling percentage 5000/Q. - */ - if (quality < 50) - quality = 5000 / quality; - else - quality = 200 - quality * 2; - - return quality; -} - -static void jpeg_add_quant_table(DCTELEM *qtable, DCTELEM *basicTable, int scale_factor, bool forceBaseline) -/* Define a quantization table equal to the basic_table times - * a scale factor (given as a percentage). - * If force_baseline is TRUE, the computed quantization table entries - * are limited to 1..255 for JPEG baseline compatibility. - */ -{ - int i; - long temp; - - for (i = 0; i < DCTSIZE2; i++) { - temp = ((long)basicTable[i] * scale_factor + 50L) / 100L; - /* limit the values to the valid range */ - if (temp <= 0L) temp = 1L; - if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ - if (forceBaseline && temp > 255L) - temp = 255L; /* limit to baseline range if requested */ - qtable[i] = (uint16_t)temp; - } -} - -static void jpeg_set_quality(DCTELEM *qtable, int quality) -/* Set or change the 'quality' (quantization) setting, using default tables. - * This is the standard quality-adjusting entry point for typical user - * interfaces; only those who want detailed control over quantization tables - * would use the preceding three routines directly. - */ -{ - /* Convert user 0-100 rating to percentage scaling */ - quality = jpeg_quality_scaling(quality); - - /* Set up standard quality tables */ - jpeg_add_quant_table(qtable, std_luminance_quant_tbl, quality, false); -} - -static void getDivisors(DCTELEM *dtbl, DCTELEM *qtable) { -#define CONST_BITS 14 -#define RIGHT_SHIFT(x, shft) ((x) >> (shft)) - - static const int16_t aanscales[DCTSIZE2] = { - /* precomputed values scaled up by 14 bits */ - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, - 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, - 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, - 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, - 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 - }; - - for (int i = 0; i < DCTSIZE2; i++) { - if (!compute_reciprocal( - DESCALE(MULTIPLY16V16((JLONG)qtable[i], - (JLONG)aanscales[i]), - CONST_BITS - 3), &dtbl[i])) { - //fdct->quantize = quantize; - printf("here\n"); - } - } -} - -static void quantize(JCOEFPTR coef_block, DCTELEM *divisors, DCTELEM *workspace) -{ - int i; - DCTELEM temp; - JCOEFPTR output_ptr = coef_block; - - UDCTELEM recip, corr; - int shift; - UDCTELEM2 product; - - for (i = 0; i < DCTSIZE2; i++) { - temp = workspace[i]; - recip = divisors[i + DCTSIZE2 * 0]; - corr = divisors[i + DCTSIZE2 * 1]; - shift = divisors[i + DCTSIZE2 * 3]; - - if (temp < 0) { - temp = -temp; - product = (UDCTELEM2)(temp + corr) * recip; - product >>= shift + sizeof(DCTELEM) * 8; - temp = (DCTELEM)product; - temp = -temp; - } else { - product = (UDCTELEM2)(temp + corr) * recip; - product >>= shift + sizeof(DCTELEM) * 8; - temp = (DCTELEM)product; - } - output_ptr[i] = (JCOEF)temp; - } -} - -NSData *generateForwardDctData(int quality) { - NSMutableData *divisors = [[NSMutableData alloc] initWithLength:DCTSIZE2 * 4 * sizeof(DCTELEM)]; - - DCTELEM qtable[DCTSIZE2]; - jpeg_set_quality(qtable, quality); - - getDivisors((DCTELEM *)divisors.mutableBytes, qtable); - - return divisors; -} - -NSData *generateInverseDctData(int quality) { - NSMutableData *divisors = [[NSMutableData alloc] initWithLength:DCTSIZE2 * sizeof(IFAST_MULT_TYPE)]; - IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)divisors.mutableBytes; - - DCTELEM qtable[DCTSIZE2]; - jpeg_set_quality(qtable, quality); - -#define CONST_BITS 14 - static const int16_t aanscales[DCTSIZE2] = { - /* precomputed values scaled up by 14 bits */ - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, - 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, - 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, - 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, - 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 - }; - - for (int i = 0; i < DCTSIZE2; i++) { - ifmtbl[i] = (IFAST_MULT_TYPE) - DESCALE(MULTIPLY16V16((JLONG)qtable[i], - (JLONG)aanscales[i]), - CONST_BITS - IFAST_SCALE_BITS); - } - - return divisors; -} - -static const int zigZagInv[DCTSIZE2] = { - 0,1,8,16,9,2,3,10, - 17,24,32,25,18,11,4,5, - 12,19,26,33,40,48,41,34, - 27,20,13,6,7,14,21,28, - 35,42,49,56,57,50,43,36, - 29,22,15,23,30,37,44,51, - 58,59,52,45,38,31,39,46, - 53,60,61,54,47,55,62,63 -}; - -static const int zigZag[DCTSIZE2] = { - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 -}; - -void performForwardDct(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow, NSData *dctData) { - DCTELEM *divisors = (DCTELEM *)dctData.bytes; - - DCTELEM block[DCTSIZE2]; - JCOEF coefBlock[DCTSIZE2]; - - for (int y = 0; y < height; y += DCTSIZE) { - for (int x = 0; x < width; x += DCTSIZE) { - for (int blockY = 0; blockY < DCTSIZE; blockY++) { - for (int blockX = 0; blockX < DCTSIZE; blockX++) { - block[blockY * DCTSIZE + blockX] = ((DCTELEM)pixels[(y + blockY) * bytesPerRow + (x + blockX)]) - CENTERJSAMPLE; - } - } - - jsimd_fdct_ifast_neon(block); - - quantize(coefBlock, divisors, block); - - for (int blockY = 0; blockY < DCTSIZE; blockY++) { - for (int blockX = 0; blockX < DCTSIZE; blockX++) { - coefficients[(y + blockY) * bytesPerRow + (x + blockX)] = coefBlock[zigZagInv[blockY * DCTSIZE + blockX]]; - } - } - } - } -} - -void performInverseDct(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow, NSData *idctData) { - IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)idctData.bytes; - - DCTELEM coefficientBlock[DCTSIZE2]; - JSAMPLE pixelBlock[DCTSIZE2]; - - for (int y = 0; y < height; y += DCTSIZE) { - for (int x = 0; x < width; x += DCTSIZE) { - for (int blockY = 0; blockY < DCTSIZE; blockY++) { - for (int blockX = 0; blockX < DCTSIZE; blockX++) { - coefficientBlock[zigZag[blockY * DCTSIZE + blockX]] = coefficients[(y + blockY) * coefficientsPerRow + (x + blockX)]; - } - } - - jsimd_idct_ifast_neon(ifmtbl, coefficientBlock, pixelBlock); - - for (int blockY = 0; blockY < DCTSIZE; blockY++) { - for (int blockX = 0; blockX < DCTSIZE; blockX++) { - pixels[(y + blockY) * bytesPerRow + (x + blockX)] = pixelBlock[blockY * DCTSIZE + blockX]; - } - } - } - } -} diff --git a/submodules/TelegramUI/Components/AnimationCache/DCT/BUILD b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD similarity index 72% rename from submodules/TelegramUI/Components/AnimationCache/DCT/BUILD rename to submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD index f98db65bc2..030187e3e1 100644 --- a/submodules/TelegramUI/Components/AnimationCache/DCT/BUILD +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/BUILD @@ -1,10 +1,13 @@ objc_library( - name = "DCT", + name = "ImageDCT", enable_modules = True, - module_name = "DCT", + module_name = "ImageDCT", srcs = glob([ "Sources/**/*.m", + "Sources/**/*.mm", + "Sources/**/*.c", + "Sources/**/*.cpp", "Sources/**/*.h", ]), hdrs = glob([ diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/ImageDCT.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/ImageDCT.h new file mode 100644 index 0000000000..4f9a855d4f --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/ImageDCT.h @@ -0,0 +1,17 @@ +#ifndef DctImageTransform_h +#define DctImageTransform_h + +#import + +#import + +@interface ImageDCT : NSObject + +- (instancetype _Nonnull)initWithQuality:(NSInteger)quality; + +- (void)forwardWithPixels:(uint8_t const * _Nonnull)pixels coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct)); +- (void)inverseWithCoefficients:(int16_t const * _Nonnull)coefficients pixels:(uint8_t * _Nonnull)pixels width:(NSInteger)width height:(NSInteger)height coefficientsPerRow:(NSInteger)coefficientsPerRow bytesPerRow:(NSInteger)bytesPerRow __attribute__((objc_direct)); + +@end + +#endif /* DctImageTransform_h */ diff --git a/submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/YuvConversion.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h similarity index 100% rename from submodules/TelegramUI/Components/AnimationCache/DCT/PublicHeaders/DCT/YuvConversion.h rename to submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp new file mode 100644 index 0000000000..49e464e764 --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp @@ -0,0 +1,376 @@ +#import "DCT.h" + +#include "DCTCommon.h" + +#include + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ + +typedef unsigned short UDCTELEM; +typedef unsigned int UDCTELEM2; + +typedef long JLONG; + +#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ + +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ + +#define CENTERJSAMPLE 128 + +namespace { + +int flss(uint16_t val) { + int bit; + + bit = 16; + + if (!val) + return 0; + + if (!(val & 0xff00)) { + bit -= 8; + val <<= 8; + } + if (!(val & 0xf000)) { + bit -= 4; + val <<= 4; + } + if (!(val & 0xc000)) { + bit -= 2; + val <<= 2; + } + if (!(val & 0x8000)) { + bit -= 1; + val <<= 1; + } + + return bit; +} + +int compute_reciprocal(uint16_t divisor, DCTELEM *dtbl) { + UDCTELEM2 fq, fr; + UDCTELEM c; + int b, r; + + if (divisor == 1) { + /* divisor == 1 means unquantized, so these reciprocal/correction/shift + * values will cause the C quantization algorithm to act like the + * identity function. Since only the C quantization algorithm is used in + * these cases, the scale value is irrelevant. + */ + dtbl[DCTSIZE2 * 0] = (DCTELEM)1; /* reciprocal */ + dtbl[DCTSIZE2 * 1] = (DCTELEM)0; /* correction */ + dtbl[DCTSIZE2 * 2] = (DCTELEM)1; /* scale */ + dtbl[DCTSIZE2 * 3] = -(DCTELEM)(sizeof(DCTELEM) * 8); /* shift */ + return 0; + } + + b = flss(divisor) - 1; + r = sizeof(DCTELEM) * 8 + b; + + fq = ((UDCTELEM2)1 << r) / divisor; + fr = ((UDCTELEM2)1 << r) % divisor; + + c = divisor / 2; /* for rounding */ + + if (fr == 0) { /* divisor is power of two */ + /* fq will be one bit too large to fit in DCTELEM, so adjust */ + fq >>= 1; + r--; + } else if (fr <= (divisor / 2U)) { /* fractional part is < 0.5 */ + c++; + } else { /* fractional part is > 0.5 */ + fq++; + } + + dtbl[DCTSIZE2 * 0] = (DCTELEM)fq; /* reciprocal */ + dtbl[DCTSIZE2 * 1] = (DCTELEM)c; /* correction + roundfactor */ +#ifdef WITH_SIMD + dtbl[DCTSIZE2 * 2] = (DCTELEM)(1 << (sizeof(DCTELEM) * 8 * 2 - r)); /* scale */ +#else + dtbl[DCTSIZE2 * 2] = 1; +#endif + dtbl[DCTSIZE2 * 3] = (DCTELEM)r - sizeof(DCTELEM) * 8; /* shift */ + + if (r <= 16) return 0; + else return 1; +} + +#define DESCALE(x, n) RIGHT_SHIFT(x, n) + + +/* Multiply a DCTELEM variable by an JLONG constant, and immediately + * descale to yield a DCTELEM result. + */ + +#define MULTIPLY(var, const) ((DCTELEM)DESCALE((var) * (const), CONST_BITS)) +#define MULTIPLY16V16(var1, var2) ((var1) * (var2)) + +static DCTELEM std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99 +}; + +int jpeg_quality_scaling(int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table + * to make all the table entries 1 (hence, minimum quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality * 2; + + return quality; +} + +void jpeg_add_quant_table(DCTELEM *qtable, DCTELEM *basicTable, int scale_factor, bool forceBaseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + int i; + long temp; + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long)basicTable[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (forceBaseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + qtable[i] = (uint16_t)temp; + } +} + +void jpeg_set_quality(DCTELEM *qtable, int quality) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_add_quant_table(qtable, std_luminance_quant_tbl, quality, false); +} + +void getDivisors(DCTELEM *dtbl, DCTELEM *qtable) { +#define CONST_BITS 14 +#define RIGHT_SHIFT(x, shft) ((x) >> (shft)) + + static const int16_t aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + + for (int i = 0; i < DCTSIZE2; i++) { + if (!compute_reciprocal( + DESCALE(MULTIPLY16V16((JLONG)qtable[i], + (JLONG)aanscales[i]), + CONST_BITS - 3), &dtbl[i])) { + } + } +} + +void quantize(JCOEFPTR coef_block, DCTELEM *divisors, DCTELEM *workspace) +{ + int i; + DCTELEM temp; + JCOEFPTR output_ptr = coef_block; + + UDCTELEM recip, corr; + int shift; + UDCTELEM2 product; + + for (i = 0; i < DCTSIZE2; i++) { + temp = workspace[i]; + recip = divisors[i + DCTSIZE2 * 0]; + corr = divisors[i + DCTSIZE2 * 1]; + shift = divisors[i + DCTSIZE2 * 3]; + + if (temp < 0) { + temp = -temp; + product = (UDCTELEM2)(temp + corr) * recip; + product >>= shift + sizeof(DCTELEM) * 8; + temp = (DCTELEM)product; + temp = -temp; + } else { + product = (UDCTELEM2)(temp + corr) * recip; + product >>= shift + sizeof(DCTELEM) * 8; + temp = (DCTELEM)product; + } + output_ptr[i] = (JCOEF)temp; + } +} + +void generateForwardDctData(int quality, std::vector &data) { + data.resize(DCTSIZE2 * 4 * sizeof(DCTELEM)); + + DCTELEM qtable[DCTSIZE2]; + jpeg_set_quality(qtable, quality); + + getDivisors((DCTELEM *)data.data(), qtable); +} + +void generateInverseDctData(int quality, std::vector &data) { + data.resize(DCTSIZE2 * sizeof(IFAST_MULT_TYPE)); + IFAST_MULT_TYPE *ifmtbl = (IFAST_MULT_TYPE *)data.data(); + + DCTELEM qtable[DCTSIZE2]; + jpeg_set_quality(qtable, quality); + +#define CONST_BITS 14 + static const int16_t aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + + for (int i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((JLONG)qtable[i], + (JLONG)aanscales[i]), + CONST_BITS - IFAST_SCALE_BITS); + } +} + +static const int zigZagInv[DCTSIZE2] = { + 0,1,8,16,9,2,3,10, + 17,24,32,25,18,11,4,5, + 12,19,26,33,40,48,41,34, + 27,20,13,6,7,14,21,28, + 35,42,49,56,57,50,43,36, + 29,22,15,23,30,37,44,51, + 58,59,52,45,38,31,39,46, + 53,60,61,54,47,55,62,63 +}; + +static const int zigZag[DCTSIZE2] = { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + +void performForwardDct(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow, DCTELEM *divisors) { + DCTELEM block[DCTSIZE2]; + JCOEF coefBlock[DCTSIZE2]; + + for (int y = 0; y < height; y += DCTSIZE) { + for (int x = 0; x < width; x += DCTSIZE) { + for (int blockY = 0; blockY < DCTSIZE; blockY++) { + for (int blockX = 0; blockX < DCTSIZE; blockX++) { + block[blockY * DCTSIZE + blockX] = ((DCTELEM)pixels[(y + blockY) * bytesPerRow + (x + blockX)]) - CENTERJSAMPLE; + } + } + + dct_jpeg_fdct_ifast(block); + + quantize(coefBlock, divisors, block); + + for (int blockY = 0; blockY < DCTSIZE; blockY++) { + for (int blockX = 0; blockX < DCTSIZE; blockX++) { + coefficients[(y + blockY) * bytesPerRow + (x + blockX)] = coefBlock[zigZagInv[blockY * DCTSIZE + blockX]]; + } + } + } + } +} + +void performInverseDct(int16_t const * coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow, DctAuxiliaryData *auxiliaryData, IFAST_MULT_TYPE *ifmtbl) { + DCTELEM coefficientBlock[DCTSIZE2]; + JSAMPLE pixelBlock[DCTSIZE2]; + + for (int y = 0; y < height; y += DCTSIZE) { + for (int x = 0; x < width; x += DCTSIZE) { + for (int blockY = 0; blockY < DCTSIZE; blockY++) { + for (int blockX = 0; blockX < DCTSIZE; blockX++) { + coefficientBlock[zigZag[blockY * DCTSIZE + blockX]] = coefficients[(y + blockY) * coefficientsPerRow + (x + blockX)]; + } + } + + dct_jpeg_idct_ifast(auxiliaryData, ifmtbl, coefficientBlock, pixelBlock); + + for (int blockY = 0; blockY < DCTSIZE; blockY++) { + for (int blockX = 0; blockX < DCTSIZE; blockX++) { + pixels[(y + blockY) * bytesPerRow + (x + blockX)] = pixelBlock[blockY * DCTSIZE + blockX]; + } + } + } + } +} + +} + +namespace dct { + +class DCTInternal { +public: + DCTInternal(int quality) { + auxiliaryData = createDctAuxiliaryData(); + + generateForwardDctData(quality, forwardDctData); + generateInverseDctData(quality, inverseDctData); + } + + ~DCTInternal() { + freeDctAuxiliaryData(auxiliaryData); + } + +public: + struct DctAuxiliaryData *auxiliaryData = nullptr; + std::vector forwardDctData; + std::vector inverseDctData; +}; + +DCT::DCT(int quality) { + _internal = new DCTInternal(quality); +} + +DCT::~DCT() { + delete _internal; +} + +void DCT::forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow) { + performForwardDct(pixels, coefficients, width, height, bytesPerRow, (DCTELEM *)_internal->forwardDctData.data()); +} + +void DCT::inverse(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow) { + performInverseDct(coefficients, pixels, width, height, coefficientsPerRow, bytesPerRow, _internal->auxiliaryData, (IFAST_MULT_TYPE *)_internal->inverseDctData.data()); +} + +} diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.h new file mode 100644 index 0000000000..3b0ca4e772 --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.h @@ -0,0 +1,26 @@ +#ifndef DCT_H +#define DCT_H + +#include "DCTCommon.h" + +#include + +namespace dct { + +class DCTInternal; + +class DCT { +public: + DCT(int quality); + ~DCT(); + + void forward(uint8_t const *pixels, int16_t *coefficients, int width, int height, int bytesPerRow); + void inverse(int16_t const *coefficients, uint8_t *pixels, int width, int height, int coefficientsPerRow, int bytesPerRow); + +private: + DCTInternal *_internal; +}; + +} + +#endif diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCTCommon.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCTCommon.h new file mode 100644 index 0000000000..b57f76e8de --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCTCommon.h @@ -0,0 +1,27 @@ +#ifndef DCT_COMMON_H +#define DCT_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef short DCTELEM; + +typedef short JCOEF; +typedef JCOEF *JCOEFPTR; + +typedef unsigned char JSAMPLE; +typedef JSAMPLE *JSAMPROW; + +struct DctAuxiliaryData; +struct DctAuxiliaryData *createDctAuxiliaryData(); +void freeDctAuxiliaryData(struct DctAuxiliaryData *data); + +void dct_jpeg_idct_ifast(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JSAMPROW output_buf); +void dct_jpeg_fdct_ifast(DCTELEM *data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_C.c b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_C.c new file mode 100644 index 0000000000..c0846b6856 --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_C.c @@ -0,0 +1,399 @@ +#import "DCTCommon.h" + +#if !defined(__aarch64__) + +#include +#include + +typedef long JLONG; + +#define CONST_BITS 8 +#define PASS1_BITS 2 + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ + +#define FIX_0_382683433 ((JLONG)98) /* FIX(0.382683433) */ +#define FIX_0_541196100 ((JLONG)139) /* FIX(0.541196100) */ +#define FIX_0_707106781 ((JLONG)181) /* FIX(0.707106781) */ +#define FIX_1_306562965 ((JLONG)334) /* FIX(1.306562965) */ + +#define FIX_1_082392200 ((JLONG)277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((JLONG)362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((JLONG)473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((JLONG)669) /* FIX(2.613125930) */ + +#define RIGHT_SHIFT(x, shft) ((x) >> (shft)) +#define IRIGHT_SHIFT(x, shft) ((x) >> (shft)) +#define DESCALE(x, n) RIGHT_SHIFT(x, n) +#define IDESCALE(x, n) ((int)IRIGHT_SHIFT(x, n)) + +#define MULTIPLY(var, const) ((DCTELEM)DESCALE((var) * (const), CONST_BITS)) + +#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ + +#define DEQUANTIZE(coef, quantval) (((IFAST_MULT_TYPE)(coef)) * (quantval)) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +void dct_jpeg_fdct_ifast(DCTELEM *data) +{ + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z1, z2, z3, z4, z5, z11, z13; + DCTELEM *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE - 1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE - 1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE * 0] + dataptr[DCTSIZE * 7]; + tmp7 = dataptr[DCTSIZE * 0] - dataptr[DCTSIZE * 7]; + tmp1 = dataptr[DCTSIZE * 1] + dataptr[DCTSIZE * 6]; + tmp6 = dataptr[DCTSIZE * 1] - dataptr[DCTSIZE * 6]; + tmp2 = dataptr[DCTSIZE * 2] + dataptr[DCTSIZE * 5]; + tmp5 = dataptr[DCTSIZE * 2] - dataptr[DCTSIZE * 5]; + tmp3 = dataptr[DCTSIZE * 3] + dataptr[DCTSIZE * 4]; + tmp4 = dataptr[DCTSIZE * 3] - dataptr[DCTSIZE * 4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE * 0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE * 4] = tmp10 - tmp11; + + z1 = MULTIPLY(tmp12 + tmp13, FIX_0_707106781); /* c4 */ + dataptr[DCTSIZE * 2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE * 6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = MULTIPLY(tmp10 - tmp12, FIX_0_382683433); /* c6 */ + z2 = MULTIPLY(tmp10, FIX_0_541196100) + z5; /* c2-c6 */ + z4 = MULTIPLY(tmp12, FIX_1_306562965) + z5; /* c2+c6 */ + z3 = MULTIPLY(tmp11, FIX_0_707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE * 5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE * 3] = z13 - z2; + dataptr[DCTSIZE * 1] = z11 + z4; + dataptr[DCTSIZE * 7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +struct DctAuxiliaryData { + JSAMPLE *allocated_sample_range_limit; + JSAMPLE *sample_range_limit; +}; + +static void prepare_range_limit_table(struct DctAuxiliaryData *data) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE *table; + int i; + + table = (JSAMPLE *)malloc((5 * (MAXJSAMPLE + 1) + CENTERJSAMPLE) * sizeof(JSAMPLE)); + data->allocated_sample_range_limit = table; + table += (MAXJSAMPLE + 1); /* allow negative subscripts of simple table */ + data->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + memset(table - (MAXJSAMPLE + 1), 0, (MAXJSAMPLE + 1) * sizeof(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE)i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2 * (MAXJSAMPLE + 1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + memset(table + (2 * (MAXJSAMPLE + 1)), 0, + (2 * (MAXJSAMPLE + 1) - CENTERJSAMPLE) * sizeof(JSAMPLE)); + memcpy(table + (4 * (MAXJSAMPLE + 1) - CENTERJSAMPLE), + data->sample_range_limit, CENTERJSAMPLE * sizeof(JSAMPLE)); +} + +struct DctAuxiliaryData *createDctAuxiliaryData() { + struct DctAuxiliaryData *result = malloc(sizeof(struct DctAuxiliaryData)); + memset(result, 0, sizeof(struct DctAuxiliaryData)); + + prepare_range_limit_table(result); + + return result; +} + +void freeDctAuxiliaryData(struct DctAuxiliaryData *data) { + if (data) { + free(data->allocated_sample_range_limit); + free(data); + } +} + +void dct_jpeg_idct_ifast(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JSAMPROW output_buf) { + DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + DCTELEM tmp10, tmp11, tmp12, tmp13; + DCTELEM z5, z10, z11, z12, z13; + JCOEFPTR inptr; + IFAST_MULT_TYPE *quantptr; + int *wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(auxiliaryData); + int ctr; + int workspace[DCTSIZE2]; /* buffers data between passes */ + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if (inptr[DCTSIZE * 1] == 0 && inptr[DCTSIZE * 2] == 0 && + inptr[DCTSIZE * 3] == 0 && inptr[DCTSIZE * 4] == 0 && + inptr[DCTSIZE * 5] == 0 && inptr[DCTSIZE * 6] == 0 && + inptr[DCTSIZE * 7] == 0) { + /* AC terms all zero */ + int dcval = (int)DEQUANTIZE(inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0]); + + wsptr[DCTSIZE * 0] = dcval; + wsptr[DCTSIZE * 1] = dcval; + wsptr[DCTSIZE * 2] = dcval; + wsptr[DCTSIZE * 3] = dcval; + wsptr[DCTSIZE * 4] = dcval; + wsptr[DCTSIZE * 5] = dcval; + wsptr[DCTSIZE * 6] = dcval; + wsptr[DCTSIZE * 7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE * 0], quantptr[DCTSIZE * 0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE * 2], quantptr[DCTSIZE * 2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE * 4], quantptr[DCTSIZE * 4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE * 6], quantptr[DCTSIZE * 6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE * 1], quantptr[DCTSIZE * 1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE * 3], quantptr[DCTSIZE * 3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE * 5], quantptr[DCTSIZE * 5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE * 7], quantptr[DCTSIZE * 7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE * 0] = (int)(tmp0 + tmp7); + wsptr[DCTSIZE * 7] = (int)(tmp0 - tmp7); + wsptr[DCTSIZE * 1] = (int)(tmp1 + tmp6); + wsptr[DCTSIZE * 6] = (int)(tmp1 - tmp6); + wsptr[DCTSIZE * 2] = (int)(tmp2 + tmp5); + wsptr[DCTSIZE * 5] = (int)(tmp2 - tmp5); + wsptr[DCTSIZE * 4] = (int)(tmp3 + tmp4); + wsptr[DCTSIZE * 3] = (int)(tmp3 - tmp4); + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3, */ + /* and also undo the PASS1_BITS scaling. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf + ctr * DCTSIZE; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * On machines with very fast multiplication, it's possible that the + * test takes more time than it's worth. In that case this section + * may be commented out. + */ + +#ifndef NO_ZERO_ROW_TEST + if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + /* AC terms all zero */ + JSAMPLE dcval = + range_limit[IDESCALE(wsptr[0], PASS1_BITS + 3) & RANGE_MASK]; + + outptr[0] = dcval; + outptr[1] = dcval; + outptr[2] = dcval; + outptr[3] = dcval; + outptr[4] = dcval; + outptr[5] = dcval; + outptr[6] = dcval; + outptr[7] = dcval; + + wsptr += DCTSIZE; /* advance pointer to next row */ + continue; + } +#endif + + /* Even part */ + + tmp10 = ((DCTELEM)wsptr[0] + (DCTELEM)wsptr[4]); + tmp11 = ((DCTELEM)wsptr[0] - (DCTELEM)wsptr[4]); + + tmp13 = ((DCTELEM)wsptr[2] + (DCTELEM)wsptr[6]); + tmp12 = + MULTIPLY((DCTELEM)wsptr[2] - (DCTELEM)wsptr[6], FIX_1_414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = (DCTELEM)wsptr[5] + (DCTELEM)wsptr[3]; + z10 = (DCTELEM)wsptr[5] - (DCTELEM)wsptr[3]; + z11 = (DCTELEM)wsptr[1] + (DCTELEM)wsptr[7]; + z12 = (DCTELEM)wsptr[1] - (DCTELEM)wsptr[7]; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ + + z5 = MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ + tmp10 = MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ + tmp12 = MULTIPLY(z10, -FIX_2_613125930) + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = + range_limit[IDESCALE(tmp0 + tmp7, PASS1_BITS + 3) & RANGE_MASK]; + outptr[7] = + range_limit[IDESCALE(tmp0 - tmp7, PASS1_BITS + 3) & RANGE_MASK]; + outptr[1] = + range_limit[IDESCALE(tmp1 + tmp6, PASS1_BITS + 3) & RANGE_MASK]; + outptr[6] = + range_limit[IDESCALE(tmp1 - tmp6, PASS1_BITS + 3) & RANGE_MASK]; + outptr[2] = + range_limit[IDESCALE(tmp2 + tmp5, PASS1_BITS + 3) & RANGE_MASK]; + outptr[5] = + range_limit[IDESCALE(tmp2 - tmp5, PASS1_BITS + 3) & RANGE_MASK]; + outptr[4] = + range_limit[IDESCALE(tmp3 + tmp4, PASS1_BITS + 3) & RANGE_MASK]; + outptr[3] = + range_limit[IDESCALE(tmp3 - tmp4, PASS1_BITS + 3) & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_Neon.c b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_Neon.c new file mode 100644 index 0000000000..5ccd6e154b --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT_Neon.c @@ -0,0 +1,677 @@ +#import "DCTCommon.h" + +#include + +#if defined(__aarch64__) + +typedef long JLONG; + +#define GETJSAMPLE(value) ((int)(value)) + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + +#define MULTIPLIER short /* prefer 16-bit with SIMD for parellelism */ +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ + +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + */ + +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +#include + +/* jsimd_idct_ifast_neon() performs dequantization and a fast, not so accurate + * inverse DCT (Discrete Cosine Transform) on one block of coefficients. It + * uses the same calculations and produces exactly the same output as IJG's + * original jpeg_idct_ifast() function, which can be found in jidctfst.c. + * + * Scaled integer constants are used to avoid floating-point arithmetic: + * 0.082392200 = 2688 * 2^-15 + * 0.414213562 = 13568 * 2^-15 + * 0.847759065 = 27776 * 2^-15 + * 0.613125930 = 20096 * 2^-15 + * + * See jidctfst.c for further details of the IDCT algorithm. Where possible, + * the variable names and comments here in jsimd_idct_ifast_neon() match up + * with those in jpeg_idct_ifast(). + */ + +#define PASS1_BITS 2 + +#define F_0_082 2688 +#define F_0_414 13568 +#define F_0_847 27776 +#define F_0_613 20096 + + +__attribute__((aligned(16))) static const int16_t jsimd_idct_ifast_neon_consts[] = { + F_0_082, F_0_414, F_0_847, F_0_613 +}; + +#define F_0_382 12544 +#define F_0_541 17792 +#define F_0_707 23168 +#define F_0_306 9984 + + +__attribute__((aligned(16))) static const int16_t jsimd_fdct_ifast_neon_consts[] = { + F_0_382, F_0_541, F_0_707, F_0_306 +}; + +void dct_jpeg_fdct_ifast(DCTELEM *data) { + /* Load an 8x8 block of samples into Neon registers. De-interleaving loads + * are used, followed by vuzp to transpose the block such that we have a + * column of samples per vector - allowing all rows to be processed at once. + */ + int16x8x4_t data1 = vld4q_s16(data); + int16x8x4_t data2 = vld4q_s16(data + 4 * DCTSIZE); + + int16x8x2_t cols_04 = vuzpq_s16(data1.val[0], data2.val[0]); + int16x8x2_t cols_15 = vuzpq_s16(data1.val[1], data2.val[1]); + int16x8x2_t cols_26 = vuzpq_s16(data1.val[2], data2.val[2]); + int16x8x2_t cols_37 = vuzpq_s16(data1.val[3], data2.val[3]); + + int16x8_t col0 = cols_04.val[0]; + int16x8_t col1 = cols_15.val[0]; + int16x8_t col2 = cols_26.val[0]; + int16x8_t col3 = cols_37.val[0]; + int16x8_t col4 = cols_04.val[1]; + int16x8_t col5 = cols_15.val[1]; + int16x8_t col6 = cols_26.val[1]; + int16x8_t col7 = cols_37.val[1]; + + /* Pass 1: process rows. */ + + /* Load DCT conversion constants. */ + const int16x4_t consts = vld1_s16(jsimd_fdct_ifast_neon_consts); + + int16x8_t tmp0 = vaddq_s16(col0, col7); + int16x8_t tmp7 = vsubq_s16(col0, col7); + int16x8_t tmp1 = vaddq_s16(col1, col6); + int16x8_t tmp6 = vsubq_s16(col1, col6); + int16x8_t tmp2 = vaddq_s16(col2, col5); + int16x8_t tmp5 = vsubq_s16(col2, col5); + int16x8_t tmp3 = vaddq_s16(col3, col4); + int16x8_t tmp4 = vsubq_s16(col3, col4); + + /* Even part */ + int16x8_t tmp10 = vaddq_s16(tmp0, tmp3); /* phase 2 */ + int16x8_t tmp13 = vsubq_s16(tmp0, tmp3); + int16x8_t tmp11 = vaddq_s16(tmp1, tmp2); + int16x8_t tmp12 = vsubq_s16(tmp1, tmp2); + + col0 = vaddq_s16(tmp10, tmp11); /* phase 3 */ + col4 = vsubq_s16(tmp10, tmp11); + + int16x8_t z1 = vqdmulhq_lane_s16(vaddq_s16(tmp12, tmp13), consts, 2); + col2 = vaddq_s16(tmp13, z1); /* phase 5 */ + col6 = vsubq_s16(tmp13, z1); + + /* Odd part */ + tmp10 = vaddq_s16(tmp4, tmp5); /* phase 2 */ + tmp11 = vaddq_s16(tmp5, tmp6); + tmp12 = vaddq_s16(tmp6, tmp7); + + int16x8_t z5 = vqdmulhq_lane_s16(vsubq_s16(tmp10, tmp12), consts, 0); + int16x8_t z2 = vqdmulhq_lane_s16(tmp10, consts, 1); + z2 = vaddq_s16(z2, z5); + int16x8_t z4 = vqdmulhq_lane_s16(tmp12, consts, 3); + z5 = vaddq_s16(tmp12, z5); + z4 = vaddq_s16(z4, z5); + int16x8_t z3 = vqdmulhq_lane_s16(tmp11, consts, 2); + + int16x8_t z11 = vaddq_s16(tmp7, z3); /* phase 5 */ + int16x8_t z13 = vsubq_s16(tmp7, z3); + + col5 = vaddq_s16(z13, z2); /* phase 6 */ + col3 = vsubq_s16(z13, z2); + col1 = vaddq_s16(z11, z4); + col7 = vsubq_s16(z11, z4); + + /* Transpose to work on columns in pass 2. */ + int16x8x2_t cols_01 = vtrnq_s16(col0, col1); + int16x8x2_t cols_23 = vtrnq_s16(col2, col3); + int16x8x2_t cols_45 = vtrnq_s16(col4, col5); + int16x8x2_t cols_67 = vtrnq_s16(col6, col7); + + int32x4x2_t cols_0145_l = vtrnq_s32(vreinterpretq_s32_s16(cols_01.val[0]), + vreinterpretq_s32_s16(cols_45.val[0])); + int32x4x2_t cols_0145_h = vtrnq_s32(vreinterpretq_s32_s16(cols_01.val[1]), + vreinterpretq_s32_s16(cols_45.val[1])); + int32x4x2_t cols_2367_l = vtrnq_s32(vreinterpretq_s32_s16(cols_23.val[0]), + vreinterpretq_s32_s16(cols_67.val[0])); + int32x4x2_t cols_2367_h = vtrnq_s32(vreinterpretq_s32_s16(cols_23.val[1]), + vreinterpretq_s32_s16(cols_67.val[1])); + + int32x4x2_t rows_04 = vzipq_s32(cols_0145_l.val[0], cols_2367_l.val[0]); + int32x4x2_t rows_15 = vzipq_s32(cols_0145_h.val[0], cols_2367_h.val[0]); + int32x4x2_t rows_26 = vzipq_s32(cols_0145_l.val[1], cols_2367_l.val[1]); + int32x4x2_t rows_37 = vzipq_s32(cols_0145_h.val[1], cols_2367_h.val[1]); + + int16x8_t row0 = vreinterpretq_s16_s32(rows_04.val[0]); + int16x8_t row1 = vreinterpretq_s16_s32(rows_15.val[0]); + int16x8_t row2 = vreinterpretq_s16_s32(rows_26.val[0]); + int16x8_t row3 = vreinterpretq_s16_s32(rows_37.val[0]); + int16x8_t row4 = vreinterpretq_s16_s32(rows_04.val[1]); + int16x8_t row5 = vreinterpretq_s16_s32(rows_15.val[1]); + int16x8_t row6 = vreinterpretq_s16_s32(rows_26.val[1]); + int16x8_t row7 = vreinterpretq_s16_s32(rows_37.val[1]); + + /* Pass 2: process columns. */ + + tmp0 = vaddq_s16(row0, row7); + tmp7 = vsubq_s16(row0, row7); + tmp1 = vaddq_s16(row1, row6); + tmp6 = vsubq_s16(row1, row6); + tmp2 = vaddq_s16(row2, row5); + tmp5 = vsubq_s16(row2, row5); + tmp3 = vaddq_s16(row3, row4); + tmp4 = vsubq_s16(row3, row4); + + /* Even part */ + tmp10 = vaddq_s16(tmp0, tmp3); /* phase 2 */ + tmp13 = vsubq_s16(tmp0, tmp3); + tmp11 = vaddq_s16(tmp1, tmp2); + tmp12 = vsubq_s16(tmp1, tmp2); + + row0 = vaddq_s16(tmp10, tmp11); /* phase 3 */ + row4 = vsubq_s16(tmp10, tmp11); + + z1 = vqdmulhq_lane_s16(vaddq_s16(tmp12, tmp13), consts, 2); + row2 = vaddq_s16(tmp13, z1); /* phase 5 */ + row6 = vsubq_s16(tmp13, z1); + + /* Odd part */ + tmp10 = vaddq_s16(tmp4, tmp5); /* phase 2 */ + tmp11 = vaddq_s16(tmp5, tmp6); + tmp12 = vaddq_s16(tmp6, tmp7); + + z5 = vqdmulhq_lane_s16(vsubq_s16(tmp10, tmp12), consts, 0); + z2 = vqdmulhq_lane_s16(tmp10, consts, 1); + z2 = vaddq_s16(z2, z5); + z4 = vqdmulhq_lane_s16(tmp12, consts, 3); + z5 = vaddq_s16(tmp12, z5); + z4 = vaddq_s16(z4, z5); + z3 = vqdmulhq_lane_s16(tmp11, consts, 2); + + z11 = vaddq_s16(tmp7, z3); /* phase 5 */ + z13 = vsubq_s16(tmp7, z3); + + row5 = vaddq_s16(z13, z2); /* phase 6 */ + row3 = vsubq_s16(z13, z2); + row1 = vaddq_s16(z11, z4); + row7 = vsubq_s16(z11, z4); + + vst1q_s16(data + 0 * DCTSIZE, row0); + vst1q_s16(data + 1 * DCTSIZE, row1); + vst1q_s16(data + 2 * DCTSIZE, row2); + vst1q_s16(data + 3 * DCTSIZE, row3); + vst1q_s16(data + 4 * DCTSIZE, row4); + vst1q_s16(data + 5 * DCTSIZE, row5); + vst1q_s16(data + 6 * DCTSIZE, row6); + vst1q_s16(data + 7 * DCTSIZE, row7); +} + +struct DctAuxiliaryData { +}; + +struct DctAuxiliaryData *createDctAuxiliaryData() { + struct DctAuxiliaryData *result = malloc(sizeof(struct DctAuxiliaryData)); + return result; +} + +void freeDctAuxiliaryData(struct DctAuxiliaryData *data) { + if (data) { + free(data); + } +} + +void dct_jpeg_idct_ifast(struct DctAuxiliaryData *auxiliaryData, void *dct_table, JCOEFPTR coef_block, JSAMPROW output_buf) +{ + IFAST_MULT_TYPE *quantptr = dct_table; + + /* Load DCT coefficients. */ + int16x8_t row0 = vld1q_s16(coef_block + 0 * DCTSIZE); + int16x8_t row1 = vld1q_s16(coef_block + 1 * DCTSIZE); + int16x8_t row2 = vld1q_s16(coef_block + 2 * DCTSIZE); + int16x8_t row3 = vld1q_s16(coef_block + 3 * DCTSIZE); + int16x8_t row4 = vld1q_s16(coef_block + 4 * DCTSIZE); + int16x8_t row5 = vld1q_s16(coef_block + 5 * DCTSIZE); + int16x8_t row6 = vld1q_s16(coef_block + 6 * DCTSIZE); + int16x8_t row7 = vld1q_s16(coef_block + 7 * DCTSIZE); + + /* Load quantization table values for DC coefficients. */ + int16x8_t quant_row0 = vld1q_s16(quantptr + 0 * DCTSIZE); + /* Dequantize DC coefficients. */ + row0 = vmulq_s16(row0, quant_row0); + + /* Construct bitmap to test if all AC coefficients are 0. */ + int16x8_t bitmap = vorrq_s16(row1, row2); + bitmap = vorrq_s16(bitmap, row3); + bitmap = vorrq_s16(bitmap, row4); + bitmap = vorrq_s16(bitmap, row5); + bitmap = vorrq_s16(bitmap, row6); + bitmap = vorrq_s16(bitmap, row7); + + int64_t left_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 0); + int64_t right_ac_bitmap = vgetq_lane_s64(vreinterpretq_s64_s16(bitmap), 1); + + /* Load IDCT conversion constants. */ + const int16x4_t consts = vld1_s16(jsimd_idct_ifast_neon_consts); + + if (left_ac_bitmap == 0 && right_ac_bitmap == 0) { + /* All AC coefficients are zero. + * Compute DC values and duplicate into vectors. + */ + int16x8_t dcval = row0; + row1 = dcval; + row2 = dcval; + row3 = dcval; + row4 = dcval; + row5 = dcval; + row6 = dcval; + row7 = dcval; + } else if (left_ac_bitmap == 0) { + /* AC coefficients are zero for columns 0, 1, 2, and 3. + * Use DC values for these columns. + */ + int16x4_t dcval = vget_low_s16(row0); + + /* Commence regular fast IDCT computation for columns 4, 5, 6, and 7. */ + + /* Load quantization table. */ + int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE + 4); + int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE + 4); + int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE + 4); + int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE + 4); + int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE + 4); + int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE + 4); + int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE + 4); + + /* Even part: dequantize DCT coefficients. */ + int16x4_t tmp0 = vget_high_s16(row0); + int16x4_t tmp1 = vmul_s16(vget_high_s16(row2), quant_row2); + int16x4_t tmp2 = vmul_s16(vget_high_s16(row4), quant_row4); + int16x4_t tmp3 = vmul_s16(vget_high_s16(row6), quant_row6); + + int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */ + int16x4_t tmp11 = vsub_s16(tmp0, tmp2); + + int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */ + int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3); + int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1); + tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3); + tmp12 = vsub_s16(tmp12, tmp13); + + tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */ + tmp3 = vsub_s16(tmp10, tmp13); + tmp1 = vadd_s16(tmp11, tmp12); + tmp2 = vsub_s16(tmp11, tmp12); + + /* Odd part: dequantize DCT coefficients. */ + int16x4_t tmp4 = vmul_s16(vget_high_s16(row1), quant_row1); + int16x4_t tmp5 = vmul_s16(vget_high_s16(row3), quant_row3); + int16x4_t tmp6 = vmul_s16(vget_high_s16(row5), quant_row5); + int16x4_t tmp7 = vmul_s16(vget_high_s16(row7), quant_row7); + + int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */ + int16x4_t neg_z10 = vsub_s16(tmp5, tmp6); + int16x4_t z11 = vadd_s16(tmp4, tmp7); + int16x4_t z12 = vsub_s16(tmp4, tmp7); + + tmp7 = vadd_s16(z11, z13); /* phase 5 */ + int16x4_t z11_sub_z13 = vsub_s16(z11, z13); + tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1); + tmp11 = vadd_s16(tmp11, z11_sub_z13); + + int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10); + int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2); + z5 = vadd_s16(z5, z10_add_z12); + tmp10 = vqdmulh_lane_s16(z12, consts, 0); + tmp10 = vadd_s16(tmp10, z12); + tmp10 = vsub_s16(tmp10, z5); + tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3); + tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10)); + tmp12 = vadd_s16(tmp12, z5); + + tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */ + tmp5 = vsub_s16(tmp11, tmp6); + tmp4 = vadd_s16(tmp10, tmp5); + + row0 = vcombine_s16(dcval, vadd_s16(tmp0, tmp7)); + row7 = vcombine_s16(dcval, vsub_s16(tmp0, tmp7)); + row1 = vcombine_s16(dcval, vadd_s16(tmp1, tmp6)); + row6 = vcombine_s16(dcval, vsub_s16(tmp1, tmp6)); + row2 = vcombine_s16(dcval, vadd_s16(tmp2, tmp5)); + row5 = vcombine_s16(dcval, vsub_s16(tmp2, tmp5)); + row4 = vcombine_s16(dcval, vadd_s16(tmp3, tmp4)); + row3 = vcombine_s16(dcval, vsub_s16(tmp3, tmp4)); + } else if (right_ac_bitmap == 0) { + /* AC coefficients are zero for columns 4, 5, 6, and 7. + * Use DC values for these columns. + */ + int16x4_t dcval = vget_high_s16(row0); + + /* Commence regular fast IDCT computation for columns 0, 1, 2, and 3. */ + + /* Load quantization table. */ + int16x4_t quant_row1 = vld1_s16(quantptr + 1 * DCTSIZE); + int16x4_t quant_row2 = vld1_s16(quantptr + 2 * DCTSIZE); + int16x4_t quant_row3 = vld1_s16(quantptr + 3 * DCTSIZE); + int16x4_t quant_row4 = vld1_s16(quantptr + 4 * DCTSIZE); + int16x4_t quant_row5 = vld1_s16(quantptr + 5 * DCTSIZE); + int16x4_t quant_row6 = vld1_s16(quantptr + 6 * DCTSIZE); + int16x4_t quant_row7 = vld1_s16(quantptr + 7 * DCTSIZE); + + /* Even part: dequantize DCT coefficients. */ + int16x4_t tmp0 = vget_low_s16(row0); + int16x4_t tmp1 = vmul_s16(vget_low_s16(row2), quant_row2); + int16x4_t tmp2 = vmul_s16(vget_low_s16(row4), quant_row4); + int16x4_t tmp3 = vmul_s16(vget_low_s16(row6), quant_row6); + + int16x4_t tmp10 = vadd_s16(tmp0, tmp2); /* phase 3 */ + int16x4_t tmp11 = vsub_s16(tmp0, tmp2); + + int16x4_t tmp13 = vadd_s16(tmp1, tmp3); /* phases 5-3 */ + int16x4_t tmp1_sub_tmp3 = vsub_s16(tmp1, tmp3); + int16x4_t tmp12 = vqdmulh_lane_s16(tmp1_sub_tmp3, consts, 1); + tmp12 = vadd_s16(tmp12, tmp1_sub_tmp3); + tmp12 = vsub_s16(tmp12, tmp13); + + tmp0 = vadd_s16(tmp10, tmp13); /* phase 2 */ + tmp3 = vsub_s16(tmp10, tmp13); + tmp1 = vadd_s16(tmp11, tmp12); + tmp2 = vsub_s16(tmp11, tmp12); + + /* Odd part: dequantize DCT coefficients. */ + int16x4_t tmp4 = vmul_s16(vget_low_s16(row1), quant_row1); + int16x4_t tmp5 = vmul_s16(vget_low_s16(row3), quant_row3); + int16x4_t tmp6 = vmul_s16(vget_low_s16(row5), quant_row5); + int16x4_t tmp7 = vmul_s16(vget_low_s16(row7), quant_row7); + + int16x4_t z13 = vadd_s16(tmp6, tmp5); /* phase 6 */ + int16x4_t neg_z10 = vsub_s16(tmp5, tmp6); + int16x4_t z11 = vadd_s16(tmp4, tmp7); + int16x4_t z12 = vsub_s16(tmp4, tmp7); + + tmp7 = vadd_s16(z11, z13); /* phase 5 */ + int16x4_t z11_sub_z13 = vsub_s16(z11, z13); + tmp11 = vqdmulh_lane_s16(z11_sub_z13, consts, 1); + tmp11 = vadd_s16(tmp11, z11_sub_z13); + + int16x4_t z10_add_z12 = vsub_s16(z12, neg_z10); + int16x4_t z5 = vqdmulh_lane_s16(z10_add_z12, consts, 2); + z5 = vadd_s16(z5, z10_add_z12); + tmp10 = vqdmulh_lane_s16(z12, consts, 0); + tmp10 = vadd_s16(tmp10, z12); + tmp10 = vsub_s16(tmp10, z5); + tmp12 = vqdmulh_lane_s16(neg_z10, consts, 3); + tmp12 = vadd_s16(tmp12, vadd_s16(neg_z10, neg_z10)); + tmp12 = vadd_s16(tmp12, z5); + + tmp6 = vsub_s16(tmp12, tmp7); /* phase 2 */ + tmp5 = vsub_s16(tmp11, tmp6); + tmp4 = vadd_s16(tmp10, tmp5); + + row0 = vcombine_s16(vadd_s16(tmp0, tmp7), dcval); + row7 = vcombine_s16(vsub_s16(tmp0, tmp7), dcval); + row1 = vcombine_s16(vadd_s16(tmp1, tmp6), dcval); + row6 = vcombine_s16(vsub_s16(tmp1, tmp6), dcval); + row2 = vcombine_s16(vadd_s16(tmp2, tmp5), dcval); + row5 = vcombine_s16(vsub_s16(tmp2, tmp5), dcval); + row4 = vcombine_s16(vadd_s16(tmp3, tmp4), dcval); + row3 = vcombine_s16(vsub_s16(tmp3, tmp4), dcval); + } else { + /* Some AC coefficients are non-zero; full IDCT calculation required. */ + + /* Load quantization table. */ + int16x8_t quant_row1 = vld1q_s16(quantptr + 1 * DCTSIZE); + int16x8_t quant_row2 = vld1q_s16(quantptr + 2 * DCTSIZE); + int16x8_t quant_row3 = vld1q_s16(quantptr + 3 * DCTSIZE); + int16x8_t quant_row4 = vld1q_s16(quantptr + 4 * DCTSIZE); + int16x8_t quant_row5 = vld1q_s16(quantptr + 5 * DCTSIZE); + int16x8_t quant_row6 = vld1q_s16(quantptr + 6 * DCTSIZE); + int16x8_t quant_row7 = vld1q_s16(quantptr + 7 * DCTSIZE); + + /* Even part: dequantize DCT coefficients. */ + int16x8_t tmp0 = row0; + int16x8_t tmp1 = vmulq_s16(row2, quant_row2); + int16x8_t tmp2 = vmulq_s16(row4, quant_row4); + int16x8_t tmp3 = vmulq_s16(row6, quant_row6); + + int16x8_t tmp10 = vaddq_s16(tmp0, tmp2); /* phase 3 */ + int16x8_t tmp11 = vsubq_s16(tmp0, tmp2); + + int16x8_t tmp13 = vaddq_s16(tmp1, tmp3); /* phases 5-3 */ + int16x8_t tmp1_sub_tmp3 = vsubq_s16(tmp1, tmp3); + int16x8_t tmp12 = vqdmulhq_lane_s16(tmp1_sub_tmp3, consts, 1); + tmp12 = vaddq_s16(tmp12, tmp1_sub_tmp3); + tmp12 = vsubq_s16(tmp12, tmp13); + + tmp0 = vaddq_s16(tmp10, tmp13); /* phase 2 */ + tmp3 = vsubq_s16(tmp10, tmp13); + tmp1 = vaddq_s16(tmp11, tmp12); + tmp2 = vsubq_s16(tmp11, tmp12); + + /* Odd part: dequantize DCT coefficients. */ + int16x8_t tmp4 = vmulq_s16(row1, quant_row1); + int16x8_t tmp5 = vmulq_s16(row3, quant_row3); + int16x8_t tmp6 = vmulq_s16(row5, quant_row5); + int16x8_t tmp7 = vmulq_s16(row7, quant_row7); + + int16x8_t z13 = vaddq_s16(tmp6, tmp5); /* phase 6 */ + int16x8_t neg_z10 = vsubq_s16(tmp5, tmp6); + int16x8_t z11 = vaddq_s16(tmp4, tmp7); + int16x8_t z12 = vsubq_s16(tmp4, tmp7); + + tmp7 = vaddq_s16(z11, z13); /* phase 5 */ + int16x8_t z11_sub_z13 = vsubq_s16(z11, z13); + tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1); + tmp11 = vaddq_s16(tmp11, z11_sub_z13); + + int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10); + int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2); + z5 = vaddq_s16(z5, z10_add_z12); + tmp10 = vqdmulhq_lane_s16(z12, consts, 0); + tmp10 = vaddq_s16(tmp10, z12); + tmp10 = vsubq_s16(tmp10, z5); + tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3); + tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10)); + tmp12 = vaddq_s16(tmp12, z5); + + tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */ + tmp5 = vsubq_s16(tmp11, tmp6); + tmp4 = vaddq_s16(tmp10, tmp5); + + row0 = vaddq_s16(tmp0, tmp7); + row7 = vsubq_s16(tmp0, tmp7); + row1 = vaddq_s16(tmp1, tmp6); + row6 = vsubq_s16(tmp1, tmp6); + row2 = vaddq_s16(tmp2, tmp5); + row5 = vsubq_s16(tmp2, tmp5); + row4 = vaddq_s16(tmp3, tmp4); + row3 = vsubq_s16(tmp3, tmp4); + } + + /* Transpose rows to work on columns in pass 2. */ + int16x8x2_t rows_01 = vtrnq_s16(row0, row1); + int16x8x2_t rows_23 = vtrnq_s16(row2, row3); + int16x8x2_t rows_45 = vtrnq_s16(row4, row5); + int16x8x2_t rows_67 = vtrnq_s16(row6, row7); + + int32x4x2_t rows_0145_l = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[0]), + vreinterpretq_s32_s16(rows_45.val[0])); + int32x4x2_t rows_0145_h = vtrnq_s32(vreinterpretq_s32_s16(rows_01.val[1]), + vreinterpretq_s32_s16(rows_45.val[1])); + int32x4x2_t rows_2367_l = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[0]), + vreinterpretq_s32_s16(rows_67.val[0])); + int32x4x2_t rows_2367_h = vtrnq_s32(vreinterpretq_s32_s16(rows_23.val[1]), + vreinterpretq_s32_s16(rows_67.val[1])); + + int32x4x2_t cols_04 = vzipq_s32(rows_0145_l.val[0], rows_2367_l.val[0]); + int32x4x2_t cols_15 = vzipq_s32(rows_0145_h.val[0], rows_2367_h.val[0]); + int32x4x2_t cols_26 = vzipq_s32(rows_0145_l.val[1], rows_2367_l.val[1]); + int32x4x2_t cols_37 = vzipq_s32(rows_0145_h.val[1], rows_2367_h.val[1]); + + int16x8_t col0 = vreinterpretq_s16_s32(cols_04.val[0]); + int16x8_t col1 = vreinterpretq_s16_s32(cols_15.val[0]); + int16x8_t col2 = vreinterpretq_s16_s32(cols_26.val[0]); + int16x8_t col3 = vreinterpretq_s16_s32(cols_37.val[0]); + int16x8_t col4 = vreinterpretq_s16_s32(cols_04.val[1]); + int16x8_t col5 = vreinterpretq_s16_s32(cols_15.val[1]); + int16x8_t col6 = vreinterpretq_s16_s32(cols_26.val[1]); + int16x8_t col7 = vreinterpretq_s16_s32(cols_37.val[1]); + + /* 1-D IDCT, pass 2 */ + + /* Even part */ + int16x8_t tmp10 = vaddq_s16(col0, col4); + int16x8_t tmp11 = vsubq_s16(col0, col4); + + int16x8_t tmp13 = vaddq_s16(col2, col6); + int16x8_t col2_sub_col6 = vsubq_s16(col2, col6); + int16x8_t tmp12 = vqdmulhq_lane_s16(col2_sub_col6, consts, 1); + tmp12 = vaddq_s16(tmp12, col2_sub_col6); + tmp12 = vsubq_s16(tmp12, tmp13); + + int16x8_t tmp0 = vaddq_s16(tmp10, tmp13); + int16x8_t tmp3 = vsubq_s16(tmp10, tmp13); + int16x8_t tmp1 = vaddq_s16(tmp11, tmp12); + int16x8_t tmp2 = vsubq_s16(tmp11, tmp12); + + /* Odd part */ + int16x8_t z13 = vaddq_s16(col5, col3); + int16x8_t neg_z10 = vsubq_s16(col3, col5); + int16x8_t z11 = vaddq_s16(col1, col7); + int16x8_t z12 = vsubq_s16(col1, col7); + + int16x8_t tmp7 = vaddq_s16(z11, z13); /* phase 5 */ + int16x8_t z11_sub_z13 = vsubq_s16(z11, z13); + tmp11 = vqdmulhq_lane_s16(z11_sub_z13, consts, 1); + tmp11 = vaddq_s16(tmp11, z11_sub_z13); + + int16x8_t z10_add_z12 = vsubq_s16(z12, neg_z10); + int16x8_t z5 = vqdmulhq_lane_s16(z10_add_z12, consts, 2); + z5 = vaddq_s16(z5, z10_add_z12); + tmp10 = vqdmulhq_lane_s16(z12, consts, 0); + tmp10 = vaddq_s16(tmp10, z12); + tmp10 = vsubq_s16(tmp10, z5); + tmp12 = vqdmulhq_lane_s16(neg_z10, consts, 3); + tmp12 = vaddq_s16(tmp12, vaddq_s16(neg_z10, neg_z10)); + tmp12 = vaddq_s16(tmp12, z5); + + int16x8_t tmp6 = vsubq_s16(tmp12, tmp7); /* phase 2 */ + int16x8_t tmp5 = vsubq_s16(tmp11, tmp6); + int16x8_t tmp4 = vaddq_s16(tmp10, tmp5); + + col0 = vaddq_s16(tmp0, tmp7); + col7 = vsubq_s16(tmp0, tmp7); + col1 = vaddq_s16(tmp1, tmp6); + col6 = vsubq_s16(tmp1, tmp6); + col2 = vaddq_s16(tmp2, tmp5); + col5 = vsubq_s16(tmp2, tmp5); + col4 = vaddq_s16(tmp3, tmp4); + col3 = vsubq_s16(tmp3, tmp4); + + /* Scale down by a factor of 8, narrowing to 8-bit. */ + int8x16_t cols_01_s8 = vcombine_s8(vqshrn_n_s16(col0, PASS1_BITS + 3), + vqshrn_n_s16(col1, PASS1_BITS + 3)); + int8x16_t cols_45_s8 = vcombine_s8(vqshrn_n_s16(col4, PASS1_BITS + 3), + vqshrn_n_s16(col5, PASS1_BITS + 3)); + int8x16_t cols_23_s8 = vcombine_s8(vqshrn_n_s16(col2, PASS1_BITS + 3), + vqshrn_n_s16(col3, PASS1_BITS + 3)); + int8x16_t cols_67_s8 = vcombine_s8(vqshrn_n_s16(col6, PASS1_BITS + 3), + vqshrn_n_s16(col7, PASS1_BITS + 3)); + /* Clamp to range [0-255]. */ + uint8x16_t cols_01 = + vreinterpretq_u8_s8 + (vaddq_s8(cols_01_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); + uint8x16_t cols_45 = + vreinterpretq_u8_s8 + (vaddq_s8(cols_45_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); + uint8x16_t cols_23 = + vreinterpretq_u8_s8 + (vaddq_s8(cols_23_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); + uint8x16_t cols_67 = + vreinterpretq_u8_s8 + (vaddq_s8(cols_67_s8, vreinterpretq_s8_u8(vdupq_n_u8(CENTERJSAMPLE)))); + + /* Transpose block to prepare for store. */ + uint32x4x2_t cols_0415 = vzipq_u32(vreinterpretq_u32_u8(cols_01), + vreinterpretq_u32_u8(cols_45)); + uint32x4x2_t cols_2637 = vzipq_u32(vreinterpretq_u32_u8(cols_23), + vreinterpretq_u32_u8(cols_67)); + + uint8x16x2_t cols_0145 = vtrnq_u8(vreinterpretq_u8_u32(cols_0415.val[0]), + vreinterpretq_u8_u32(cols_0415.val[1])); + uint8x16x2_t cols_2367 = vtrnq_u8(vreinterpretq_u8_u32(cols_2637.val[0]), + vreinterpretq_u8_u32(cols_2637.val[1])); + uint16x8x2_t rows_0426 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[0]), + vreinterpretq_u16_u8(cols_2367.val[0])); + uint16x8x2_t rows_1537 = vtrnq_u16(vreinterpretq_u16_u8(cols_0145.val[1]), + vreinterpretq_u16_u8(cols_2367.val[1])); + + uint8x16_t rows_04 = vreinterpretq_u8_u16(rows_0426.val[0]); + uint8x16_t rows_15 = vreinterpretq_u8_u16(rows_1537.val[0]); + uint8x16_t rows_26 = vreinterpretq_u8_u16(rows_0426.val[1]); + uint8x16_t rows_37 = vreinterpretq_u8_u16(rows_1537.val[1]); + + JSAMPROW outptr0 = output_buf + DCTSIZE * 0; + JSAMPROW outptr1 = output_buf + DCTSIZE * 1; + JSAMPROW outptr2 = output_buf + DCTSIZE * 2; + JSAMPROW outptr3 = output_buf + DCTSIZE * 3; + JSAMPROW outptr4 = output_buf + DCTSIZE * 4; + JSAMPROW outptr5 = output_buf + DCTSIZE * 5; + JSAMPROW outptr6 = output_buf + DCTSIZE * 6; + JSAMPROW outptr7 = output_buf + DCTSIZE * 7; + + /* Store DCT block to memory. */ + vst1q_lane_u64((uint64_t *)outptr0, vreinterpretq_u64_u8(rows_04), 0); + vst1q_lane_u64((uint64_t *)outptr1, vreinterpretq_u64_u8(rows_15), 0); + vst1q_lane_u64((uint64_t *)outptr2, vreinterpretq_u64_u8(rows_26), 0); + vst1q_lane_u64((uint64_t *)outptr3, vreinterpretq_u64_u8(rows_37), 0); + vst1q_lane_u64((uint64_t *)outptr4, vreinterpretq_u64_u8(rows_04), 1); + vst1q_lane_u64((uint64_t *)outptr5, vreinterpretq_u64_u8(rows_15), 1); + vst1q_lane_u64((uint64_t *)outptr6, vreinterpretq_u64_u8(rows_26), 1); + vst1q_lane_u64((uint64_t *)outptr7, vreinterpretq_u64_u8(rows_37), 1); +} + +#endif diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/ImageDCT.mm b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/ImageDCT.mm new file mode 100644 index 0000000000..3d39105531 --- /dev/null +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/ImageDCT.mm @@ -0,0 +1,31 @@ +#import + +#import + +#include "DCT.h" + +@interface ImageDCT () { + std::unique_ptr _dct; +} + +@end + +@implementation ImageDCT + +- (instancetype _Nonnull)initWithQuality:(NSInteger)quality { + self = [super init]; + if (self != nil) { + _dct = std::unique_ptr(new dct::DCT((int)quality)); + } + return self; +} + +- (void)forwardWithPixels:(uint8_t const * _Nonnull)pixels coefficients:(int16_t * _Nonnull)coefficients width:(NSInteger)width height:(NSInteger)height bytesPerRow:(NSInteger)bytesPerRow { + _dct->forward(pixels, coefficients, (int)width, (int)height, (int)bytesPerRow); +} + +- (void)inverseWithCoefficients:(int16_t const * _Nonnull)coefficients pixels:(uint8_t * _Nonnull)pixels width:(NSInteger)width height:(NSInteger)height coefficientsPerRow:(NSInteger)coefficientsPerRow bytesPerRow:(NSInteger)bytesPerRow { + _dct->inverse(coefficients, pixels, (int)width, (int)height, (int)coefficientsPerRow, (int)bytesPerRow); +} + +@end diff --git a/submodules/TelegramUI/Components/AnimationCache/DCT/Sources/YuvConversion.m b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m similarity index 98% rename from submodules/TelegramUI/Components/AnimationCache/DCT/Sources/YuvConversion.m rename to submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m index 25b244d5e1..41bd589754 100644 --- a/submodules/TelegramUI/Components/AnimationCache/DCT/Sources/YuvConversion.m +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m @@ -1,4 +1,4 @@ -#import +#import #import #import diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift index a30d8fd48f..120dc7ae8f 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift @@ -1,6 +1,6 @@ import Foundation import UIKit -import DCT +import ImageDCT final class ImagePlane { let width: Int @@ -134,13 +134,11 @@ extension ImageYUVA420 { final class DctData { let quality: Int - let dctData: Data - let idctData: Data + let dct: ImageDCT init(quality: Int) { self.quality = quality - self.dctData = generateForwardDctData(Int32(quality))! - self.idctData = generateInverseDctData(Int32(quality))! + self.dct = ImageDCT(quality: quality) } } @@ -174,7 +172,7 @@ extension ImageYUVA420 { targetPlane.data.withUnsafeMutableBytes { bytes in let coefficients = bytes.baseAddress!.assumingMemoryBound(to: UInt16.self) - performForwardDct(sourcePixels, coefficients, Int32(sourcePlane.width), Int32(sourcePlane.height), Int32(sourcePlane.bytesPerRow), dctData.dctData) + dctData.dct.forward(withPixels: sourcePixels, coefficients: coefficients, width: sourcePlane.width, height: sourcePlane.height, bytesPerRow: sourcePlane.bytesPerRow) } } } @@ -217,7 +215,7 @@ extension DctCoefficientsYUVA420 { targetPlane.data.withUnsafeMutableBytes { bytes in let pixels = bytes.baseAddress!.assumingMemoryBound(to: UInt8.self) - performInverseDct(coefficients, pixels, Int32(sourcePlane.width), Int32(sourcePlane.height), Int32(targetPlane.bytesPerRow), Int32(sourcePlane.width), dctData.idctData) + dctData.dct.inverse(withCoefficients: coefficients, pixels: pixels, width: sourcePlane.width, height: sourcePlane.height, coefficientsPerRow: targetPlane.width, bytesPerRow: targetPlane.width) } } } diff --git a/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD b/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD new file mode 100644 index 0000000000..7813794065 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatInputPanelContainer/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInputPanelContainer", + module_name = "ChatInputPanelContainer", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatInputPanelContainer/Sources/ChatInputPanelContainer.swift b/submodules/TelegramUI/Components/ChatInputPanelContainer/Sources/ChatInputPanelContainer.swift new file mode 100644 index 0000000000..6b2a4df214 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatInputPanelContainer/Sources/ChatInputPanelContainer.swift @@ -0,0 +1,113 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit + +private final class ExpansionPanRecognizer: UIPanGestureRecognizer, UIGestureRecognizerDelegate { + private var targetScrollView: UIScrollView? + + override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.delegate = self + } + + override func reset() { + self.targetScrollView = nil + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + /*if let scrollView = otherGestureRecognizer.view as? UIScrollView { + if scrollView.bounds.height > 200.0 { + self.targetScrollView = scrollView + scrollView.contentOffset = CGPoint() + } + }*/ + + return false + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if let targetScrollView = self.targetScrollView { + targetScrollView.contentOffset = CGPoint() + } + } +} + +public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate { + public var expansionUpdated: ((ContainedViewLayoutTransition) -> Void)? + + private var expansionRecognizer: ExpansionPanRecognizer? + + private var scrollableDistance: CGFloat? + public private(set) var initialExpansionFraction: CGFloat = 0.0 + public private(set) var expansionFraction: CGFloat = 0.0 + + override public init() { + super.init() + + let expansionRecognizer = ExpansionPanRecognizer(target: self, action: #selector(self.panGesture(_:))) + self.expansionRecognizer = expansionRecognizer + self.view.addGestureRecognizer(expansionRecognizer) + expansionRecognizer.isEnabled = false + } + + @objc private func panGesture(_ recognizer: ExpansionPanRecognizer) { + switch recognizer.state { + case .began: + guard let _ = self.scrollableDistance else { + return + } + self.initialExpansionFraction = self.expansionFraction + case .changed: + guard let scrollableDistance = self.scrollableDistance else { + return + } + + let delta = -recognizer.translation(in: self.view).y / scrollableDistance + + self.expansionFraction = max(0.0, min(1.0, self.initialExpansionFraction + delta)) + self.expansionUpdated?(.immediate) + case .ended, .cancelled: + guard let _ = self.scrollableDistance else { + return + } + + let velocity = recognizer.velocity(in: self.view) + if abs(self.initialExpansionFraction - self.expansionFraction) > 0.25 { + if self.initialExpansionFraction < 0.5 { + self.expansionFraction = 1.0 + } else { + self.expansionFraction = 0.0 + } + } else if abs(velocity.y) > 100.0 { + if velocity.y < 0.0 { + self.expansionFraction = 1.0 + } else { + self.expansionFraction = 0.0 + } + } else { + if self.initialExpansionFraction < 0.5 { + self.expansionFraction = 0.0 + } else { + self.expansionFraction = 1.0 + } + } + self.expansionUpdated?(.animated(duration: 0.4, curve: .spring)) + default: + break + } + } + + public func update(size: CGSize, scrollableDistance: CGFloat, isExpansionEnabled: Bool, transition: ContainedViewLayoutTransition) { + self.expansionRecognizer?.isEnabled = isExpansionEnabled + + self.scrollableDistance = scrollableDistance + } + + public func collapse() { + self.expansionFraction = 0.0 + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/BUILD b/submodules/TelegramUI/Components/EntityKeyboard/BUILD index 5947ec2a2e..2c3681c3ec 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboard/BUILD @@ -33,6 +33,11 @@ swift_library( "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/PhotoResources:PhotoResources", "//submodules/StickerResources:StickerResources", + "//submodules/AppBundle:AppBundle", + "//submodules/ContextUI:ContextUI", + "//submodules/PremiumUI:PremiumUI", + "//submodules/StickerPackPreviewUI:StickerPackPreviewUI", + "//submodules/UndoUI:UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 5c03454d8a..132efd729c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -17,6 +17,38 @@ import SwiftSignalKit import ShimmerEffect import PagerComponent import StickerResources +import AppBundle +import ContextUI +import PremiumUI +import StickerPackPreviewUI +import UndoUI + +private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) + +private final class PremiumBadgeView: BlurredBackgroundView { + private let iconLayer: SimpleLayer + + init() { + self.iconLayer = SimpleLayer() + self.iconLayer.contents = premiumBadgeIcon?.cgImage + + super.init(color: .clear, enableBlur: true) + + self.layer.addSublayer(self.iconLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(backgroundColor: UIColor, size: CGSize) { + self.updateColor(color: backgroundColor, transition: .immediate) + + self.iconLayer.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: 2.0, dy: 2.0) + + super.update(size: size, cornerRadius: min(size.width / 2.0, size.height / 2.0), transition: .immediate) + } +} public final class EmojiPagerContentComponent: Component { public typealias EnvironmentType = (EntityKeyboardChildEnvironment, PagerComponentChildEnvironment) @@ -25,25 +57,45 @@ public final class EmojiPagerContentComponent: Component { public let performItemAction: (Item, UIView, CGRect, CALayer) -> Void public let deleteBackwards: () -> Void public let openStickerSettings: () -> Void + public let pushController: (ViewController) -> Void + public let presentController: (ViewController) -> Void + public let presentGlobalOverlayController: (ViewController) -> Void + public let navigationController: () -> NavigationController? + public let sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void)? + public let chatPeerId: PeerId? public init( performItemAction: @escaping (Item, UIView, CGRect, CALayer) -> Void, deleteBackwards: @escaping () -> Void, - openStickerSettings: @escaping () -> Void + openStickerSettings: @escaping () -> Void, + pushController: @escaping (ViewController) -> Void, + presentController: @escaping (ViewController) -> Void, + presentGlobalOverlayController: @escaping (ViewController) -> Void, + navigationController: @escaping () -> NavigationController?, + sendSticker: ((FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Void)?, + chatPeerId: PeerId? ) { self.performItemAction = performItemAction self.deleteBackwards = deleteBackwards self.openStickerSettings = openStickerSettings + self.pushController = pushController + self.presentController = presentController + self.presentGlobalOverlayController = presentGlobalOverlayController + self.navigationController = navigationController + self.sendSticker = sendSticker + self.chatPeerId = chatPeerId } } public final class Item: Equatable { public let emoji: String public let file: TelegramMediaFile + public let stickerPackItem: StickerPackItem? - public init(emoji: String, file: TelegramMediaFile) { + public init(emoji: String, file: TelegramMediaFile, stickerPackItem: StickerPackItem?) { self.emoji = emoji self.file = file + self.stickerPackItem = stickerPackItem } public static func ==(lhs: Item, rhs: Item) -> Bool { @@ -56,6 +108,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.file.fileId != rhs.file.fileId { return false } + if lhs.stickerPackItem?.file.fileId != rhs.stickerPackItem?.file.fileId { + return false + } return true } @@ -268,6 +323,7 @@ public final class EmojiPagerContentComponent: Component { private let size: CGSize private var disposable: Disposable? private var fetchDisposable: Disposable? + private var premiumBadgeView: PremiumBadgeView? private var isInHierarchyValue: Bool = false public var isVisibleForAnimations: Bool = false { @@ -288,6 +344,8 @@ public final class EmojiPagerContentComponent: Component { cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor, + blurredBadgeColor: UIColor, + displayPremiumBadgeIfAvailable: Bool, pointSize: CGSize ) { self.item = item @@ -365,6 +423,17 @@ public final class EmojiPagerContentComponent: Component { } } }) + + self.fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: isSmall)).start() + } + + if displayPremiumBadgeIfAvailable && file.isPremiumSticker { + let premiumBadgeView = PremiumBadgeView() + let badgeSize = CGSize(width: 20.0, height: 20.0) + premiumBadgeView.frame = CGRect(origin: CGPoint(x: pointSize.width - badgeSize.width, y: pointSize.height - badgeSize.height), size: badgeSize) + premiumBadgeView.update(backgroundColor: blurredBadgeColor, size: badgeSize) + self.premiumBadgeView = premiumBadgeView + self.addSublayer(premiumBadgeView.layer) } } @@ -434,6 +503,10 @@ public final class EmojiPagerContentComponent: Component { private var theme: PresentationTheme? private var itemLayout: ItemLayout? + private var currentContextGestureItemKey: ItemLayer.Key? + + private weak var peekController: PeekController? + override init(frame: CGRect) { self.scrollView = UIScrollView() @@ -452,6 +525,178 @@ public final class EmojiPagerContentComponent: Component { self.addSubview(self.scrollView) self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + + /*self.useSublayerTransformForActivation = false + self.shouldBegin = { [weak self] point in + guard let strongSelf = self else { + return false + } + if let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1] { + strongSelf.currentContextGestureItemKey = item.1 + strongSelf.targetLayerForActivationProgress = itemLayer + return true + } else { + return false + } + } + self.contextGesture?.cancelGesturesOnActivation = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.scrollView.panGestureRecognizer.state = .failed + }*/ + + let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in + guard let strongSelf = self, let component = strongSelf.component else { + return nil + } + guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1] else { + return nil + } + + let context = component.context + let accountPeerId = context.account.peerId + return combineLatest( + context.engine.stickers.isStickerSaved(id: item.0.file.fileId), + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in + var hasPremium = false + if case let .user(user) = peer, user.isPremium { + hasPremium = true + } + return hasPremium + } + ) + |> deliverOnMainQueue + |> map { [weak itemLayer] isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in + guard let strongSelf = self, let component = strongSelf.component, let itemLayer = itemLayer else { + return nil + } + var menuItems: [ContextMenuItem] = [] + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + if let sendSticker = component.inputInteraction.sendSticker, let chatPeerId = component.inputInteraction.chatPeerId { + if chatPeerId != component.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController { + if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + sendSticker(.standalone(media: item.0.file), true, false, nil, false, animationNode.view, animationNode.bounds, nil) + } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { + sendSticker(.standalone(media: item.0.file), true, false, nil, false, imageNode.view, imageNode.bounds, nil) + } + } + f(.default) + }))) + } + + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController { + if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + let _ = sendSticker(.standalone(media: item.0.file), false, true, nil, false, animationNode.view, animationNode.bounds, nil) + } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { + let _ = sendSticker(.standalone(media: item.0.file), false, true, nil, false, imageNode.view, imageNode.bounds, nil) + } + } + f(.default) + }))) + } + + menuItems.append( + .action(ContextMenuActionItem(text: isStarred ? presentationData.strings.Stickers_RemoveFromFavorites : presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (context.engine.stickers.toggleStickerSaved(file: item.0.file, saved: !isStarred) + |> deliverOnMainQueue).start(next: { result in + switch result { + case .generic: + component.inputInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: item.0.file, title: nil, text: !isStarred ? presentationData.strings.Conversation_StickerAddedToFavorites : presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: false, action: { _ in return false })) + case let .limitExceeded(limit, premiumLimit): + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + let text: String + if limit == premiumLimit || premiumConfiguration.isPremiumDisabled { + text = presentationData.strings.Premium_MaxFavedStickersFinalText + } else { + text = presentationData.strings.Premium_MaxFavedStickersText("\(premiumLimit)").string + } + component.inputInteraction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: item.0.file, title: presentationData.strings.Premium_MaxFavedStickersTitle("\(limit)").string, text: text, undoText: nil), elevatedLayout: false, action: { action in + if case .info = action { + let controller = PremiumIntroScreen(context: context, source: .savedStickers) + component.inputInteraction.pushController(controller) + return true + } + return false + })) + } + }) + })) + ) + menuItems.append( + .action(ContextMenuActionItem(text: presentationData.strings.StickerPack_ViewPack, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.default) + + loop: for attribute in item.0.file.attributes { + switch attribute { + case let .Sticker(_, packReference, _): + if let packReference = packReference { + let controller = StickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: component.inputInteraction.navigationController(), sendSticker: { file, sourceView, sourceRect in + //return component.inputInteraction.sendSticker(file, false, false, nil, false, sourceNode, sourceRect, nil) + return false + }) + + component.inputInteraction.navigationController()?.view.window?.endEditing(true) + component.inputInteraction.presentController(controller) + } + break loop + default: + break + } + } + })) + ) + + return (strongSelf, strongSelf.scrollView.convert(itemLayer.frame, to: strongSelf), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(item.0.file), isLocked: item.0.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { + let controller = PremiumIntroScreen(context: context, source: .stickers) + component.inputInteraction.pushController(controller) + })) + } + }, present: { [weak self] content, sourceView, sourceRect in + guard let strongSelf = self, let component = strongSelf.component else { + return nil + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) + }) + /*controller.visibilityUpdated = { [weak self] visible in + self?.previewingStickersPromise.set(visible) + self?.requestDisableStickerAnimations?(visible) + self?.simulateUpdateLayout(isVisible: !visible) + }*/ + strongSelf.peekController = controller + component.inputInteraction.presentGlobalOverlayController(controller) + return controller + }, updateContent: { [weak self] content in + guard let strongSelf = self else { + return + } + + let _ = strongSelf + + /*var item: StickerPreviewPeekItem? + if let content = content as? StickerPreviewPeekContent { + item = content.item + } + strongSelf.updatePreviewingItem(item: item, animated: true)*/ + }) + self.addGestureRecognizer(peekRecognizer) } required init?(coder: NSCoder) { @@ -600,12 +845,26 @@ public final class EmojiPagerContentComponent: Component { if let current = self.visibleItemLayers[itemId] { itemLayer = current } else { - itemLayer = ItemLayer(item: item, context: component.context, groupId: "keyboard", attemptSynchronousLoad: attemptSynchronousLoads, file: item.file, cache: component.animationCache, renderer: component.animationRenderer, placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1), pointSize: CGSize(width: itemLayout.itemSize, height: itemLayout.itemSize)) + itemLayer = ItemLayer( + item: item, + context: component.context, + groupId: "keyboard", + attemptSynchronousLoad: attemptSynchronousLoads, + file: item.file, + cache: component.animationCache, + renderer: component.animationRenderer, + placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1), + blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5), + displayPremiumBadgeIfAvailable: true, + pointSize: CGSize(width: itemLayout.itemSize, height: itemLayout.itemSize) + ) self.scrollView.layer.addSublayer(itemLayer) self.visibleItemLayers[itemId] = itemLayer } - itemLayer.frame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index) + let itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: index) + itemLayer.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY) + itemLayer.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) itemLayer.isVisibleForAnimations = true } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index 3bc7213fdd..5fbe61559c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -31,6 +31,7 @@ public final class EntityKeyboardComponent: Component { public let emojiContent: EmojiPagerContentComponent public let stickerContent: EmojiPagerContentComponent public let gifContent: GifPagerContentComponent + public let defaultToEmojiTab: Bool public let externalTopPanelContainer: UIView? public let topPanelExtensionUpdated: (CGFloat, Transition) -> Void @@ -40,6 +41,7 @@ public final class EntityKeyboardComponent: Component { emojiContent: EmojiPagerContentComponent, stickerContent: EmojiPagerContentComponent, gifContent: GifPagerContentComponent, + defaultToEmojiTab: Bool, externalTopPanelContainer: UIView?, topPanelExtensionUpdated: @escaping (CGFloat, Transition) -> Void ) { @@ -48,6 +50,7 @@ public final class EntityKeyboardComponent: Component { self.emojiContent = emojiContent self.stickerContent = stickerContent self.gifContent = gifContent + self.defaultToEmojiTab = defaultToEmojiTab self.externalTopPanelContainer = externalTopPanelContainer self.topPanelExtensionUpdated = topPanelExtensionUpdated } @@ -68,6 +71,9 @@ public final class EntityKeyboardComponent: Component { if lhs.gifContent != rhs.gifContent { return false } + if lhs.defaultToEmojiTab != rhs.defaultToEmojiTab { + return false + } if lhs.externalTopPanelContainer != rhs.externalTopPanelContainer { return false } @@ -232,7 +238,7 @@ public final class EntityKeyboardComponent: Component { contentTopPanels: contentTopPanels, contentIcons: contentIcons, contentAccessoryRightButtons: contentAccessoryRightButtons, - defaultId: "emoji", + defaultId: component.defaultToEmojiTab ? "emoji" : "stickers", contentBackground: AnyComponent(BlurredBackgroundComponent( color: component.theme.chat.inputMediaPanel.stickersBackgroundColor.withMultipliedAlpha(0.75) )), diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index 1a3630a28c..69ee408867 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -63,7 +63,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { let itemLayer = EmojiPagerContentComponent.View.ItemLayer( item: EmojiPagerContentComponent.Item( emoji: "", - file: component.file + file: component.file, + stickerPackItem: nil ), context: component.context, groupId: "topPanel", @@ -72,6 +73,8 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { cache: component.animationCache, renderer: component.animationRenderer, placeholderColor: .lightGray, + blurredBadgeColor: .clear, + displayPremiumBadgeIfAvailable: false, pointSize: CGSize(width: 28.0, height: 28.0) ) self.itemLayer = itemLayer diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5ee132edaf..f498039eb7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1523,6 +1523,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { + strongSelf.chatDisplayNode.inputPanelContainerNode.collapse() strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in var current = current current = current.updatedInterfaceState { interfaceState in @@ -5313,7 +5314,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } override public func loadDisplayNode() { - self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, backgroundNode: self.chatBackgroundNode, controller: self) + self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self) if let currentItem = self.tempVoicePlaylistCurrentItem { self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem) @@ -9926,6 +9927,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.updateSlowmodeStatus() + switch updatedChatPresentationInterfaceState.inputMode { + case .media: + break + default: + self.chatDisplayNode.inputPanelContainerNode.collapse() + } + if self.isNodeLoaded { self.chatDisplayNode.updateChatPresentationInterfaceState(updatedChatPresentationInterfaceState, transition: transition, interactive: interactive, completion: completion) } else { @@ -9976,20 +9984,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - switch updatedChatPresentationInterfaceState.mode { - case .standard: - if self.hasEmbeddedTitleContent { - self.statusBar.statusBarStyle = .White - } else { - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - } - self.deferScreenEdgeGestures = [] - case .overlay: - self.deferScreenEdgeGestures = [.top] - case .inline: - self.statusBar.statusBarStyle = .Ignore - } - if saveInterfaceState { self.saveInterfaceState(includeScrollState: false) } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 82f9694478..3f67c5b134 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -18,6 +18,7 @@ import WallpaperBackgroundNode import GridMessageSelectionNode import SparseItemGrid import ChatPresentationInterfaceState +import ChatInputPanelContainer final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -68,6 +69,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private weak var controller: ChatControllerImpl? let navigationBar: NavigationBar? + let statusBar: StatusBar? private var backgroundEffectNode: ASDisplayNode? private var containerBackgroundNode: ASImageNode? @@ -81,6 +83,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } } + let contentContainerNode: ASDisplayNode + let contentDimNode: ASDisplayNode let backgroundNode: WallpaperBackgroundNode let historyNode: ChatHistoryListNode var blurredHistoryNode: ASImageNode? @@ -97,7 +101,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var searchNavigationNode: ChatSearchNavigationContentNode? - let inputPanelContainerNode: SparseNode + private var navigationModalFrame: NavigationModalFrame? + + let inputPanelContainerNode: ChatInputPanelContainer + private let inputPanelClippingNode: SparseNode private let inputPanelBackgroundNode: NavigationBackgroundNode private var intrinsicInputPanelBackgroundNodeSize: CGSize? private let inputPanelBackgroundSeparatorNode: ASDisplayNode @@ -240,17 +247,24 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var onLayoutCompletions: [(ContainedViewLayoutTransition) -> Void] = [] - init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, backgroundNode: WallpaperBackgroundNode, controller: ChatControllerImpl?) { + init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, statusBar: StatusBar?, backgroundNode: WallpaperBackgroundNode, controller: ChatControllerImpl?) { self.context = context self.chatLocation = chatLocation self.controllerInteraction = controllerInteraction self.chatPresentationInterfaceState = chatPresentationInterfaceState self.automaticMediaDownloadSettings = automaticMediaDownloadSettings self.navigationBar = navigationBar + self.statusBar = statusBar self.controller = controller self.backgroundNode = backgroundNode + self.contentContainerNode = ASDisplayNode() + self.contentDimNode = ASDisplayNode() + self.contentDimNode.isUserInteractionEnabled = false + self.contentDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.2) + self.contentDimNode.alpha = 0.0 + self.titleAccessoryPanelContainer = ChatControllerTitlePanelNodeContainer() self.titleAccessoryPanelContainer.clipsToBounds = true @@ -369,7 +383,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners) - self.inputPanelContainerNode = SparseNode() + self.inputPanelContainerNode = ChatInputPanelContainer() + self.inputPanelClippingNode = SparseNode() + if case let .color(color) = self.chatPresentationInterfaceState.chatWallpaper, UIColor(rgb: color).isEqual(self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) { self.inputPanelBackgroundNode = NavigationBackgroundNode(color: self.chatPresentationInterfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) self.usePlainInputSeparator = true @@ -501,25 +517,36 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) } self.historyNode.enableExtractedBackgrounds = true - //self.historyNode.verticalScrollIndicatorColor = .clear - self.addSubnode(self.backgroundNode) - self.addSubnode(self.historyNodeContainer) - - self.addSubnode(self.inputPanelContainerNode) - self.inputPanelContainerNode.addSubnode(self.inputPanelBackgroundNode) - self.inputPanelContainerNode.addSubnode(self.inputPanelBackgroundSeparatorNode) - self.inputPanelContainerNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode) - - self.addSubnode(self.inputContextPanelContainer) - + self.addSubnode(self.contentContainerNode) + self.contentContainerNode.addSubnode(self.backgroundNode) + self.contentContainerNode.addSubnode(self.historyNodeContainer) + if let navigationBar = self.navigationBar { - self.addSubnode(navigationBar) + self.contentContainerNode.addSubnode(navigationBar) } + self.inputPanelContainerNode.expansionUpdated = { [weak self] transition in + guard let strongSelf = self else { + return + } + + strongSelf.requestLayout(transition) + } + + self.addSubnode(self.inputPanelContainerNode) + + self.inputPanelContainerNode.addSubnode(self.inputPanelClippingNode) + self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundNode) + self.inputPanelClippingNode.addSubnode(self.inputPanelBackgroundSeparatorNode) + self.inputPanelClippingNode.addSubnode(self.inputPanelBottomBackgroundSeparatorNode) + + self.contentContainerNode.addSubnode(self.inputContextPanelContainer) + self.addSubnode(self.messageTransitionNode) - self.addSubnode(self.navigateButtons) - self.addSubnode(self.presentationContextMarker) + self.contentContainerNode.addSubnode(self.navigateButtons) + self.contentContainerNode.addSubnode(self.presentationContextMarker) + self.contentContainerNode.addSubnode(self.contentDimNode) self.navigationBar?.additionalContentNode.addSubnode(self.titleAccessoryPanelContainer) @@ -707,6 +734,22 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } else { transition = protoTransition } + + if let statusBar = self.statusBar { + switch self.chatPresentationInterfaceState.mode { + case .standard: + if self.inputPanelContainerNode.expansionFraction > 0.3 { + statusBar.updateStatusBarStyle(.White, animated: true) + } else { + statusBar.updateStatusBarStyle(self.chatPresentationInterfaceState.theme.rootController.statusBarStyle.style, animated: true) + } + self.controller?.deferScreenEdgeGestures = [] + case .overlay: + self.controller?.deferScreenEdgeGestures = [.top] + case .inline: + statusBar.statusBarStyle = .Ignore + } + } var previousListBottomInset: CGFloat? if !self.historyNode.frame.isEmpty { @@ -715,6 +758,62 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.messageTransitionNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.contentContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) + + let visibleRootModalDismissProgress: CGFloat = 1.0 - self.inputPanelContainerNode.expansionFraction + if self.inputPanelContainerNode.expansionFraction != 0.0 { + let navigationModalFrame: NavigationModalFrame + var animateFromFraction: CGFloat? + if let current = self.navigationModalFrame { + navigationModalFrame = current + } else { + animateFromFraction = 1.0 + navigationModalFrame = NavigationModalFrame() + self.navigationModalFrame = navigationModalFrame + self.insertSubnode(navigationModalFrame, aboveSubnode: self.contentContainerNode) + } + if transition.isAnimated, let animateFromFraction = animateFromFraction, animateFromFraction != 1.0 - self.inputPanelContainerNode.expansionFraction { + navigationModalFrame.updateDismissal(transition: transition, progress: animateFromFraction, additionalProgress: 0.0, completion: {}) + } + navigationModalFrame.updateDismissal(transition: transition, progress: 1.0 - self.inputPanelContainerNode.expansionFraction, additionalProgress: 0.0, completion: {}) + + self.inputPanelClippingNode.clipsToBounds = true + transition.updateCornerRadius(node: self.inputPanelClippingNode, cornerRadius: self.inputPanelContainerNode.expansionFraction * 10.0) + } else { + if let navigationModalFrame = self.navigationModalFrame { + self.navigationModalFrame = nil + navigationModalFrame.updateDismissal(transition: transition, progress: 1.0, additionalProgress: 0.0, completion: { [weak navigationModalFrame] in + navigationModalFrame?.removeFromSupernode() + }) + } + transition.updateCornerRadius(node: self.inputPanelClippingNode, cornerRadius: 0.0, completion: { [weak self] completed in + guard let strongSelf = self, completed else { + return + } + strongSelf.inputPanelClippingNode.clipsToBounds = false + }) + } + + transition.updateAlpha(node: self.contentDimNode, alpha: self.inputPanelContainerNode.expansionFraction) + + var topInset: CGFloat = 0.0 + if let statusBarHeight = layout.statusBarHeight { + topInset += statusBarHeight + } + + let maxScale: CGFloat + let maxOffset: CGFloat + maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width + maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0) + + let scale = 1.0 * visibleRootModalDismissProgress + (1.0 - visibleRootModalDismissProgress) * maxScale + let offset = (1.0 - visibleRootModalDismissProgress) * maxOffset + transition.updateSublayerTransformScaleAndOffset(node: self.contentContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset), beginWithCurrentState: true) + + if let navigationModalFrame = self.navigationModalFrame { + navigationModalFrame.update(layout: layout, transition: transition) + } + self.scheduledLayoutTransitionRequest = nil if case .overlay = self.chatPresentationInterfaceState.mode { if self.backgroundEffectNode == nil { @@ -783,10 +882,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let containerNode = self.containerNode { self.containerNode = nil containerNode.removeFromSupernode() - self.insertSubnode(self.backgroundNode, at: 0) - self.insertSubnode(self.historyNodeContainer, aboveSubnode: self.backgroundNode) + self.contentContainerNode.insertSubnode(self.backgroundNode, at: 0) + self.contentContainerNode.insertSubnode(self.historyNodeContainer, aboveSubnode: self.backgroundNode) if let restrictedNode = self.restrictedNode { - self.insertSubnode(restrictedNode, aboveSubnode: self.historyNodeContainer) + self.contentContainerNode.insertSubnode(restrictedNode, aboveSubnode: self.historyNodeContainer) } self.navigationBar?.isHidden = false } @@ -886,7 +985,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if self.chatImportStatusPanel != importStatusPanelNode { dismissedImportStatusPanelNode = self.chatImportStatusPanel self.chatImportStatusPanel = importStatusPanelNode - self.addSubnode(importStatusPanelNode) + self.contentContainerNode.addSubnode(importStatusPanelNode) } importStatusPanelHeight = importStatusPanelNode.update(context: self.context, progress: CGFloat(importState.progress), presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: self.chatPresentationInterfaceState.theme, wallpaper: self.chatPresentationInterfaceState.chatWallpaper), fontSize: self.chatPresentationInterfaceState.fontSize, strings: self.chatPresentationInterfaceState.strings, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), width: layout.size.width) @@ -903,85 +1002,21 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputPanelNodeBaseHeight += secondaryInputPanelNode.minimalHeight(interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) } - let maximumInputNodeHeight = layout.size.height - max(navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0), layout.safeInsets.top) - inputPanelNodeBaseHeight + let previewing: Bool + if case .standard(true) = self.chatPresentationInterfaceState.mode { + previewing = true + } else { + previewing = false + } - var dismissedInputNode: ChatInputNode? - var dismissedInputNodeInputBackgroundExtension: CGFloat = 0.0 - var dismissedInputNodeExternalTopPanelContainer: UIView? - var immediatelyLayoutInputNodeAndAnimateAppearance = false - var inputNodeHeightAndOverflow: (CGFloat, CGFloat)? - if let inputNode = inputNodeForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentNode: self.inputNode, interfaceInteraction: self.interfaceInteraction, inputMediaNode: self.inputMediaNode, controllerInteraction: self.controllerInteraction, inputPanelNode: self.inputPanelNode, makeMediaInputNode: { + let inputNodeForState = inputNodeForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentNode: self.inputNode, interfaceInteraction: self.interfaceInteraction, inputMediaNode: self.inputMediaNode, controllerInteraction: self.controllerInteraction, inputPanelNode: self.inputPanelNode, makeMediaInputNode: { return self.makeMediaInputNode() - }) { - if self.inputMediaNode != nil { - if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { - if inputPanelNode.isFocused { - self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) - } - } - } - if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil { - self.inputMediaNode = inputMediaNode - inputMediaNode.requestDisableStickerAnimations = { [weak self] disabled in - self?.controller?.disableStickerAnimations = disabled - } - } - if self.inputNode != inputNode { - inputNode.topBackgroundExtensionUpdated = { [weak self] transition in - self?.updateInputPanelBackgroundExtension(transition: transition) - } - - dismissedInputNode = self.inputNode - if let inputNode = self.inputNode { - dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension - } - dismissedInputNodeExternalTopPanelContainer = self.inputNode?.externalTopPanelContainer - self.inputNode = inputNode - inputNode.alpha = 1.0 - inputNode.layer.removeAnimation(forKey: "opacity") - immediatelyLayoutInputNodeAndAnimateAppearance = true - - if self.inputMediaNode != nil { - if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { - self.inputPanelContainerNode.insertSubnode(inputNode, belowSubnode: inputPanelNode) - } else { - self.inputPanelContainerNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) - } - } else { - self.inputPanelContainerNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) - } - - if let externalTopPanelContainer = inputNode.externalTopPanelContainer { - if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { - self.inputPanelContainerNode.view.insertSubview(externalTopPanelContainer, belowSubview: inputPanelNode.view) - } else { - self.inputPanelContainerNode.view.addSubview(externalTopPanelContainer) - } - } - } - inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus) - } else if let inputNode = self.inputNode { - dismissedInputNode = inputNode - dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension - dismissedInputNodeExternalTopPanelContainer = inputNode.externalTopPanelContainer - self.inputNode = nil - } - - var effectiveInputNodeHeight: CGFloat? - if let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow { - if let upperInputPositionBound = self.upperInputPositionBound { - effectiveInputNodeHeight = max(0.0, min(layout.size.height - max(0.0, upperInputPositionBound), inputNodeHeightAndOverflow.0)) - } else { - effectiveInputNodeHeight = inputNodeHeightAndOverflow.0 - } - } + }) var insets: UIEdgeInsets - var bottomOverflowOffset: CGFloat = 0.0 - if let effectiveInputNodeHeight = effectiveInputNodeHeight, let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow { + if inputNodeForState != nil { insets = layout.insets(options: []) - insets.bottom = max(effectiveInputNodeHeight, insets.bottom) - bottomOverflowOffset = inputNodeHeightAndOverflow.1 + insets.bottom = max(insets.bottom, layout.standardInputHeight) } else { insets = layout.insets(options: [.input]) } @@ -992,17 +1027,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { insets.top += navigationBarHeight } - var wrappingInsets = UIEdgeInsets() - if case .overlay = self.chatPresentationInterfaceState.mode { - let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 8.0 + layout.safeInsets.left) - wrappingInsets.left = floor((layout.size.width - containerWidth) / 2.0) - wrappingInsets.right = wrappingInsets.left - - wrappingInsets.top = 8.0 - if let statusBarHeight = layout.statusBarHeight, CGFloat(40.0).isLess(than: statusBarHeight) { - wrappingInsets.top += statusBarHeight - } - } + var inputPanelSize: CGSize? + var immediatelyLayoutInputPanelAndAnimateAppearance = false + var secondaryInputPanelSize: CGSize? + var immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = false + var inputPanelNodeHandlesTransition = false var dismissedInputPanelNode: ASDisplayNode? var dismissedSecondaryInputPanelNode: ASDisplayNode? @@ -1010,27 +1039,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var dismissedInputContextPanelNode: ChatInputContextPanelNode? var dismissedOverlayContextPanelNode: ChatInputContextPanelNode? - let previewing: Bool - if case .standard(true) = self.chatPresentationInterfaceState.mode { - previewing = true - } else { - previewing = false - } - - var isSelectionEnabled = true - if previewing { - isSelectionEnabled = false - } else if case .pinnedMessages = self.chatPresentationInterfaceState.subject { - isSelectionEnabled = false - } - self.historyNode.isSelectionGestureEnabled = isSelectionEnabled - - var inputPanelSize: CGSize? - var immediatelyLayoutInputPanelAndAnimateAppearance = false - var secondaryInputPanelSize: CGSize? - var immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = false - var inputPanelNodeHandlesTransition = false - let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, interfaceInteraction: self.interfaceInteraction) if let inputPanelNode = inputPanelNodes.primary, !previewing { @@ -1054,7 +1062,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inputPanelNode = inputPanelNode if inputPanelNode.supernode !== self { immediatelyLayoutInputPanelAndAnimateAppearance = true - self.inputPanelContainerNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + self.inputPanelClippingNode.insertSubnode(inputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) } } else { let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom - 120.0, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) @@ -1073,7 +1081,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.secondaryInputPanelNode = secondaryInputPanelNode if secondaryInputPanelNode.supernode == nil { immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = true - self.inputPanelContainerNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode) } } else { let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - insets.bottom, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics) @@ -1083,6 +1091,164 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode self.secondaryInputPanelNode = nil } + + var accessoryPanelSize: CGSize? + var immediatelyLayoutAccessoryPanelAndAnimateAppearance = false + if let accessoryPanelNode = accessoryPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.accessoryPanelNode, interfaceInteraction: self.interfaceInteraction) { + accessoryPanelSize = accessoryPanelNode.measure(CGSize(width: layout.size.width, height: layout.size.height)) + + accessoryPanelNode.updateState(size: layout.size, inset: layout.safeInsets.left, interfaceState: self.chatPresentationInterfaceState) + + if accessoryPanelNode !== self.accessoryPanelNode { + dismissedAccessoryPanelNode = self.accessoryPanelNode + self.accessoryPanelNode = accessoryPanelNode + + if let inputPanelNode = self.inputPanelNode { + self.inputPanelClippingNode.insertSubnode(accessoryPanelNode, belowSubnode: inputPanelNode) + } else { + self.inputPanelClippingNode.insertSubnode(accessoryPanelNode, aboveSubnode: self.inputPanelBackgroundNode) + } + accessoryPanelNode.animateIn() + + accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in + if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode { + if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedReplyMessageId(nil) }) + } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil) }) + } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { + strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) + } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { + strongSelf.dismissUrlPreview() + } + } + } + + immediatelyLayoutAccessoryPanelAndAnimateAppearance = true + } + } else if let accessoryPanelNode = self.accessoryPanelNode { + dismissedAccessoryPanelNode = accessoryPanelNode + self.accessoryPanelNode = nil + } + + var maximumInputNodeHeight = layout.size.height - max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top) - 10.0 + if let inputPanelSize = inputPanelSize { + maximumInputNodeHeight -= inputPanelSize.height + } + if let secondaryInputPanelSize = secondaryInputPanelSize { + maximumInputNodeHeight -= secondaryInputPanelSize.height + } + if let accessoryPanelSize = accessoryPanelSize { + maximumInputNodeHeight -= accessoryPanelSize.height + } + + var dismissedInputNode: ChatInputNode? + var dismissedInputNodeInputBackgroundExtension: CGFloat = 0.0 + var dismissedInputNodeExternalTopPanelContainer: UIView? + var immediatelyLayoutInputNodeAndAnimateAppearance = false + var inputNodeHeightAndOverflow: (CGFloat, CGFloat)? + if let inputNode = inputNodeForState { + if self.inputMediaNode != nil { + if let inputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode { + if inputPanelNode.isFocused { + self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) + } + } + } + if let inputMediaNode = inputNode as? ChatMediaInputNode, self.inputMediaNode == nil { + self.inputMediaNode = inputMediaNode + inputMediaNode.requestDisableStickerAnimations = { [weak self] disabled in + self?.controller?.disableStickerAnimations = disabled + } + } + if self.inputNode != inputNode { + inputNode.topBackgroundExtensionUpdated = { [weak self] transition in + self?.updateInputPanelBackgroundExtension(transition: transition) + } + inputNode.expansionFractionUpdated = { [weak self] transition in + self?.updateInputPanelBackgroundExpansion(transition: transition) + } + + dismissedInputNode = self.inputNode + if let inputNode = self.inputNode { + dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension + } + dismissedInputNodeExternalTopPanelContainer = self.inputNode?.externalTopPanelContainer + self.inputNode = inputNode + inputNode.alpha = 1.0 + inputNode.layer.removeAnimation(forKey: "opacity") + immediatelyLayoutInputNodeAndAnimateAppearance = true + + if self.inputMediaNode != nil { + if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { + self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: inputPanelNode) + } else { + self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) + } + } else { + self.inputPanelClippingNode.insertSubnode(inputNode, belowSubnode: self.inputPanelBackgroundNode) + } + + if let externalTopPanelContainer = inputNode.externalTopPanelContainer { + if let inputPanelNode = self.inputPanelNode, inputPanelNode.supernode != nil { + self.inputPanelClippingNode.view.insertSubview(externalTopPanelContainer, belowSubview: inputPanelNode.view) + } else { + self.inputPanelClippingNode.view.addSubview(externalTopPanelContainer) + } + } + } + + let inputHeight = layout.standardInputHeight + self.inputPanelContainerNode.expansionFraction * (maximumInputNodeHeight - layout.standardInputHeight) + + let heightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: inputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: self.isInFocus) + + let boundedHeight = min(heightAndOverflow.0, layout.standardInputHeight) + + inputNodeHeightAndOverflow = ( + boundedHeight, + max(0.0, inputHeight - boundedHeight) + ) + } else if let inputNode = self.inputNode { + dismissedInputNode = inputNode + dismissedInputNodeInputBackgroundExtension = inputNode.topBackgroundExtension + dismissedInputNodeExternalTopPanelContainer = inputNode.externalTopPanelContainer + self.inputNode = nil + } + + var effectiveInputNodeHeight: CGFloat? + if let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow { + if let upperInputPositionBound = self.upperInputPositionBound { + effectiveInputNodeHeight = max(0.0, min(layout.size.height - max(0.0, upperInputPositionBound), inputNodeHeightAndOverflow.0)) + } else { + effectiveInputNodeHeight = inputNodeHeightAndOverflow.0 + } + } + + var bottomOverflowOffset: CGFloat = 0.0 + if let effectiveInputNodeHeight = effectiveInputNodeHeight, let inputNodeHeightAndOverflow = inputNodeHeightAndOverflow { + insets.bottom = max(effectiveInputNodeHeight, insets.bottom) + bottomOverflowOffset = inputNodeHeightAndOverflow.1 + } + + var wrappingInsets = UIEdgeInsets() + if case .overlay = self.chatPresentationInterfaceState.mode { + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 8.0 + layout.safeInsets.left) + wrappingInsets.left = floor((layout.size.width - containerWidth) / 2.0) + wrappingInsets.right = wrappingInsets.left + + wrappingInsets.top = 8.0 + if let statusBarHeight = layout.statusBarHeight, CGFloat(40.0).isLess(than: statusBarHeight) { + wrappingInsets.top += statusBarHeight + } + } + + var isSelectionEnabled = true + if previewing { + isSelectionEnabled = false + } else if case .pinnedMessages = self.chatPresentationInterfaceState.subject { + isSelectionEnabled = false + } + self.historyNode.isSelectionGestureEnabled = isSelectionEnabled if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false) @@ -1106,7 +1272,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { insets.top += panelHeight } - let contentBounds = CGRect(x: 0.0, y: -bottomOverflowOffset, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom) + let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom) if let backgroundEffectNode = self.backgroundEffectNode { transition.updateFrame(node: backgroundEffectNode, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -1133,45 +1299,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - var accessoryPanelSize: CGSize? - var immediatelyLayoutAccessoryPanelAndAnimateAppearance = false - if let accessoryPanelNode = accessoryPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.accessoryPanelNode, interfaceInteraction: self.interfaceInteraction) { - accessoryPanelSize = accessoryPanelNode.measure(CGSize(width: layout.size.width, height: layout.size.height)) - - accessoryPanelNode.updateState(size: layout.size, inset: layout.safeInsets.left, interfaceState: self.chatPresentationInterfaceState) - - if accessoryPanelNode !== self.accessoryPanelNode { - dismissedAccessoryPanelNode = self.accessoryPanelNode - self.accessoryPanelNode = accessoryPanelNode - - if let inputPanelNode = self.inputPanelNode { - self.inputPanelContainerNode.insertSubnode(accessoryPanelNode, belowSubnode: inputPanelNode) - } else { - self.inputPanelContainerNode.insertSubnode(accessoryPanelNode, aboveSubnode: self.inputPanelBackgroundNode) - } - accessoryPanelNode.animateIn() - - accessoryPanelNode.dismiss = { [weak self, weak accessoryPanelNode] in - if let strongSelf = self, let accessoryPanelNode = accessoryPanelNode, strongSelf.accessoryPanelNode === accessoryPanelNode { - if let _ = accessoryPanelNode as? ReplyAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedReplyMessageId(nil) }) - } else if let _ = accessoryPanelNode as? ForwardAccessoryPanelNode { - strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { $0.withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil) }) - } else if let _ = accessoryPanelNode as? EditAccessoryPanelNode { - strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) - } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { - strongSelf.dismissUrlPreview() - } - } - } - - immediatelyLayoutAccessoryPanelAndAnimateAppearance = true - } - } else if let accessoryPanelNode = self.accessoryPanelNode { - dismissedAccessoryPanelNode = accessoryPanelNode - self.accessoryPanelNode = nil - } - var immediatelyLayoutInputContextPanelAndAnimateAppearance = false if let inputContextPanelNode = inputContextPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputContextPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction) { if inputContextPanelNode !== self.inputContextPanelNode { @@ -1192,7 +1319,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { dismissedOverlayContextPanelNode = self.overlayContextPanelNode self.overlayContextPanelNode = overlayContextPanelNode - self.addSubnode(overlayContextPanelNode) + self.contentContainerNode.addSubnode(overlayContextPanelNode) immediatelyLayoutOverlayContextPanelAndAnimateAppearance = true } } else if let overlayContextPanelNode = self.overlayContextPanelNode { @@ -1345,7 +1472,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { expandedInputDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) expandedInputDimNode.alpha = 0.0 self.expandedInputDimNode = expandedInputDimNode - self.insertSubnode(expandedInputDimNode, aboveSubnode: self.historyNodeContainer) + self.contentContainerNode.insertSubnode(expandedInputDimNode, aboveSubnode: self.historyNodeContainer) transition.updateAlpha(node: expandedInputDimNode, alpha: 1.0) expandedInputDimNode.frame = exandedFrame transition.animatePositionAdditive(node: expandedInputDimNode, offset: CGPoint(x: 0.0, y: previousInputPanelOrigin.y - inputPanelOrigin)) @@ -1382,7 +1509,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { }) let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition) - var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 6.0 - bottomOverflowOffset), size: navigateButtonsSize) + var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 6.0), size: navigateButtonsSize) if case .overlay = self.chatPresentationInterfaceState.mode { navigateButtonsFrame = navigateButtonsFrame.offsetBy(dx: -8.0, dy: -8.0) } @@ -1403,10 +1530,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { apparentNavigateButtonsFrame.origin.y -= 16.0 } + var isInputExpansionEnabled = false + if case .media = self.chatPresentationInterfaceState.inputMode { + isInputExpansionEnabled = true + } + let previousInputPanelBackgroundFrame = self.inputPanelBackgroundNode.frame transition.updateFrame(node: self.inputPanelContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + self.inputPanelContainerNode.update(size: layout.size, scrollableDistance: max(0.0, maximumInputNodeHeight - layout.standardInputHeight), isExpansionEnabled: isInputExpansionEnabled, transition: transition) + transition.updatePosition(node: self.inputPanelClippingNode, position: CGRect(origin: apparentInputBackgroundFrame.origin, size: layout.size).center) + transition.updateBounds(node: self.inputPanelClippingNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: layout.size)) transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame) + transition.updateFrame(node: self.contentDimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: apparentInputBackgroundFrame.origin.y))) + let intrinsicInputPanelBackgroundNodeSize = CGSize(width: apparentInputBackgroundFrame.size.width, height: apparentInputBackgroundFrame.size.height) self.intrinsicInputPanelBackgroundNodeSize = intrinsicInputPanelBackgroundNodeSize var inputPanelBackgroundExtension: CGFloat = 0.0 @@ -1795,6 +1932,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { transition.updateFrame(node: self.inputPanelBottomBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.inputPanelBackgroundNode.frame.maxY + extensionValue), size: CGSize(width: self.inputPanelBackgroundNode.bounds.width, height: UIScreenPixel))) } + private func updateInputPanelBackgroundExpansion(transition: ContainedViewLayoutTransition) { + self.requestLayout(transition) + } + private func notifyTransitionCompletionListeners(transition: ContainedViewLayoutTransition) { if !self.onLayoutCompletions.isEmpty { let onLayoutCompletions = self.onLayoutCompletions @@ -2107,7 +2248,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } let _ = peerId - let inputNode = ChatEntityKeyboardInputNode(context: self.context, currentInputData: inputMediaNodeData, updatedInputData: self.inputMediaNodeDataPromise.get()) + let inputNode = ChatEntityKeyboardInputNode(context: self.context, currentInputData: inputMediaNodeData, updatedInputData: self.inputMediaNodeDataPromise.get(), defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty) return inputNode } @@ -2116,10 +2257,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if !self.didInitializeInputMediaNodeDataPromise, let interfaceInteraction = self.interfaceInteraction { self.didInitializeInputMediaNodeDataPromise = true - self.inputMediaNodeDataPromise.set(ChatEntityKeyboardInputNode.inputData(context: self.context, interfaceInteraction: interfaceInteraction, controllerInteraction: self.controllerInteraction)) + self.inputMediaNodeDataPromise.set(ChatEntityKeyboardInputNode.inputData(context: self.context, interfaceInteraction: interfaceInteraction, controllerInteraction: self.controllerInteraction, chatPeerId: self.chatLocation.peerId)) } - if self.inputMediaNode == nil && !self.context.sharedContext.immediateExperimentalUISettings.inlineStickers { + if self.inputMediaNode == nil && !"".isEmpty { let peerId: PeerId? = self.chatPresentationInterfaceState.chatLocation.peerId let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { @@ -2420,7 +2561,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let dropDimNode = ASDisplayNode() dropDimNode.backgroundColor = self.chatPresentationInterfaceState.theme.chatList.backgroundColor.withAlphaComponent(0.35) self.dropDimNode = dropDimNode - self.addSubnode(dropDimNode) + self.contentContainerNode.addSubnode(dropDimNode) if let (layout, _) = self.validLayout { dropDimNode.frame = CGRect(origin: CGPoint(), size: layout.size) dropDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -2713,7 +2854,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } func updatePlainInputSeparator(transition: ContainedViewLayoutTransition) { - let resolvedValue: CGFloat + var resolvedValue: CGFloat if self.accessoryPanelNode != nil { resolvedValue = 1.0 } else if self.usePlainInputSeparator { @@ -2722,6 +2863,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { resolvedValue = 1.0 } + resolvedValue = resolvedValue * (1.0 - self.inputPanelContainerNode.expansionFraction) + if resolvedValue != self.inputPanelBackgroundSeparatorNode.alpha { transition.updateAlpha(node: self.inputPanelBackgroundSeparatorNode, alpha: resolvedValue, beginWithCurrentState: true) } diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 0fbdb124a7..d3e258ebe2 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -32,7 +32,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } } - static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction) -> Signal { + static func inputData(context: AccountContext, interfaceInteraction: ChatPanelInterfaceInteraction, controllerInteraction: ChatControllerInteraction, chatPeerId: PeerId?) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -67,7 +67,35 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { interfaceInteraction.backwardsDeleteText() }, openStickerSettings: { - } + }, + pushController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.navigationController()?.pushViewController(controller) + }, + presentController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentController(controller, nil) + }, + presentGlobalOverlayController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentGlobalOverlayController(controller, nil) + }, + navigationController: { [weak controllerInteraction] in + return controllerInteraction?.navigationController() + }, + sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in + guard let controllerInteraction = controllerInteraction else { + return + } + let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) + }, + chatPeerId: chatPeerId ) let stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak interfaceInteraction] item, view, rect, layer in @@ -89,7 +117,35 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let controller = installedStickerPacksController(context: context, mode: .modal) controller.navigationPresentation = .modal controllerInteraction.navigationController()?.pushViewController(controller) - } + }, + pushController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.navigationController()?.pushViewController(controller) + }, + presentController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentController(controller, nil) + }, + presentGlobalOverlayController: { [weak controllerInteraction] controller in + guard let controllerInteraction = controllerInteraction else { + return + } + controllerInteraction.presentGlobalOverlayController(controller, nil) + }, + navigationController: { [weak controllerInteraction] in + return controllerInteraction?.navigationController() + }, + sendSticker: { [weak controllerInteraction] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in + guard let controllerInteraction = controllerInteraction else { + return + } + let _ = controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer) + }, + chatPeerId: chatPeerId ) let gifInputInteraction = GifPagerContentComponent.InputInteraction( performItemAction: { [weak controllerInteraction] item, view, rect in @@ -134,7 +190,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { if emojiCollectionIds.contains(entry.index.collectionId) { let resultItem = EmojiPagerContentComponent.Item( emoji: "", - file: item.file + file: item.file, + stickerPackItem: nil ) let groupId = entry.index.collectionId @@ -221,7 +278,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let resultItem = EmojiPagerContentComponent.Item( emoji: "", - file: item.file + file: item.file, + stickerPackItem: nil ) let groupId = "saved" @@ -246,7 +304,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let resultItem = EmojiPagerContentComponent.Item( emoji: "", - file: item.media + file: item.media, + stickerPackItem: nil ) let groupId = "recent" @@ -294,7 +353,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let resultItem = EmojiPagerContentComponent.Item( emoji: "", - file: item.media + file: item.media, + stickerPackItem: nil ) let groupId = "premium" @@ -313,7 +373,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } let resultItem = EmojiPagerContentComponent.Item( emoji: "", - file: item.file + file: item.file, + stickerPackItem: item ) let groupId = entry.index.collectionId if let groupIndex = itemGroupIndexById[groupId] { @@ -385,11 +446,16 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { private let entityKeyboardView: ComponentHostView + private let defaultToEmojiTab: Bool private var currentInputData: InputData private var inputDataDisposable: Disposable? - init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal) { + private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool)? + + init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool) { self.currentInputData = currentInputData + self.defaultToEmojiTab = defaultToEmojiTab + self.entityKeyboardView = ComponentHostView() super.init() @@ -404,6 +470,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { return } strongSelf.currentInputData = inputData + strongSelf.performLayout() }) } @@ -411,7 +478,18 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { self.inputDataDisposable?.dispose() } + private func performLayout() { + guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.currentState else { + return + } + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) + } + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { + self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) + + let expandedHeight = standardInputHeight + self.expansionFraction * (maximumHeight - standardInputHeight) + let entityKeyboardSize = self.entityKeyboardView.update( transition: Transition(transition), component: AnyComponent(EntityKeyboardComponent( @@ -420,6 +498,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { emojiContent: self.currentInputData.emoji, stickerContent: self.currentInputData.stickers, gifContent: self.currentInputData.gifs, + defaultToEmojiTab: self.defaultToEmojiTab, externalTopPanelContainer: self.externalTopPanelContainer, topPanelExtensionUpdated: { [weak self] topPanelExtension, transition in guard let strongSelf = self else { @@ -432,10 +511,10 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { } )), environment: {}, - containerSize: CGSize(width: width, height: standardInputHeight) + containerSize: CGSize(width: width, height: expandedHeight) ) transition.updateFrame(view: self.entityKeyboardView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize)) - return (standardInputHeight, 0.0) + return (expandedHeight, 0.0) } } diff --git a/submodules/TelegramUI/Sources/ChatInputNode.swift b/submodules/TelegramUI/Sources/ChatInputNode.swift index d365208a9f..da22776da3 100644 --- a/submodules/TelegramUI/Sources/ChatInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatInputNode.swift @@ -16,6 +16,9 @@ class ChatInputNode: ASDisplayNode { var topBackgroundExtension: CGFloat = 41.0 var topBackgroundExtensionUpdated: ((ContainedViewLayoutTransition) -> Void)? + var expansionFraction: CGFloat = 0.0 + var expansionFractionUpdated: ((ContainedViewLayoutTransition) -> Void)? + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { return (0.0, 0.0) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 031c020cfa..a988e0bdc1 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -12,7 +12,7 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState: } switch chatPresentationInterfaceState.inputMode { case .media: - if context.sharedContext.immediateExperimentalUISettings.inlineStickers { + if "".isEmpty { if let currentNode = currentNode as? ChatEntityKeyboardInputNode { return currentNode } else if let inputMediaNode = inputMediaNode { diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index f6cdf73996..4870710465 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -958,7 +958,7 @@ final class ChatMediaInputNode: ChatInputNode { getItemIsPreviewedImpl = { [weak self] item in if let strongSelf = self { - return strongSelf.inputNodeInteraction.previewedStickerPackItem == .pack(item) + return strongSelf.inputNodeInteraction.previewedStickerPackItem == .pack(item.file) } return false } @@ -1592,7 +1592,7 @@ final class ChatMediaInputNode: ChatInputNode { if let item = item as? StickerPreviewPeekItem { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout { @@ -1694,7 +1694,7 @@ final class ChatMediaInputNode: ChatInputNode { } } }))) - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -1744,7 +1744,7 @@ final class ChatMediaInputNode: ChatInputNode { } ) |> deliverOnMainQueue - |> map { isStarred, hasPremium -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout { @@ -1849,7 +1849,7 @@ final class ChatMediaInputNode: ChatInputNode { } })) ) - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -1866,11 +1866,11 @@ final class ChatMediaInputNode: ChatInputNode { } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) controller.visibilityUpdated = { [weak self] visible in self?.previewingStickersPromise.set(visible) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift index 8c9fb1451f..06bdc56632 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift @@ -454,7 +454,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { func updatePreviewing(animated: Bool) { var isPreviewing = false if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction { - isPreviewing = interaction.previewedStickerPackItem == .pack(item) + isPreviewing = interaction.previewedStickerPackItem == .pack(item.file) } if self.currentIsPreviewing != isPreviewing { self.currentIsPreviewing = isPreviewing diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index bf079a44e7..8476cc2a3d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -10,6 +10,14 @@ import Postbox import TelegramCore import ReactionSelectionNode +private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { + if let presentationLayer = fromView.layer.presentation() { + return presentationLayer.convert(rect, to: toView?.layer) + } else { + return fromView.layer.convert(rect, to: toView?.layer) + } +} + private final class OverlayTransitionContainerNode: ViewControllerTracingNode { override init() { super.init() @@ -430,7 +438,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode { sourceAbsoluteRect = sourceItemNode.view.convert(sourceItemNode.imageNode.frame, to: self.view) case let .universal(sourceContainerView, sourceRect, sourceLayer): stickerSource = Sticker(imageNode: nil, animationNode: nil, placeholderNode: nil, imageLayer: sourceLayer, relativeSourceRect: sourceLayer.frame) - sourceAbsoluteRect = sourceContainerView.convert(sourceRect, to: self.view) + sourceAbsoluteRect = convertAnimatingSourceRect(sourceRect, fromView: sourceContainerView, toView: self.view) case let .inputPanelSearch(sourceItemNode): stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: nil, imageLayer: nil, relativeSourceRect: sourceItemNode.imageNode.frame) sourceAbsoluteRect = sourceItemNode.view.convert(sourceItemNode.imageNode.frame, to: self.view) diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 59a51c5c83..e7ba397f5e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1840,7 +1840,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } else { mediaInputDisabled = false } - self.actionButtons.micButton.fadeDisabled = mediaInputDisabled + + var mediaInputIsActive = false + if case .media = interfaceState.inputMode { + mediaInputIsActive = true + } + + self.actionButtons.micButton.fadeDisabled = mediaInputDisabled || mediaInputIsActive self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: transition.isAnimated) @@ -2294,7 +2300,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } if mediaInputIsActive { - hideMicButton = true + //hideMicButton = true } if hideMicButton { diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift index 2c5e72a1c4..cdae308f0c 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift @@ -478,7 +478,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { if let item = item as? StickerPreviewPeekItem { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] menuItems = [ @@ -550,7 +550,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } })) ] - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -570,7 +570,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { if let (itemNode, item) = itemNodeAndItem { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] menuItems = [ @@ -638,7 +638,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } })) ] - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -651,10 +651,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { - let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) strongSelf.peekController = controller strongSelf.controller?.presentInGlobalOverlay(controller) @@ -1180,7 +1180,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { let _ = strongSelf.sendSticker?(file, sourceView, sourceRect) } }, getItemIsPreviewed: { item in - return inputNodeInteraction.previewedStickerPackItem == .pack(item) + return inputNodeInteraction.previewedStickerPackItem == .pack(item.file) }) self._ready.set(.single(Void())) @@ -1426,7 +1426,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { return (itemNode, StickerPreviewPeekItem.found(stickerItem)) } else if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { if let (node, item) = itemNode.itemAt(point: self.view.convert(point, to: itemNode.view)) { - return (node, StickerPreviewPeekItem.pack(item)) + return (node, StickerPreviewPeekItem.pack(item.file)) } } } diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index a705670b73..c66d66445e 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -144,7 +144,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont return nil } - var selectedItemNodeAndContent: (ASDisplayNode, PeekControllerContent)? + var selectedItemNodeAndContent: (UIView, CGRect, PeekControllerContent)? strongSelf.listView.forEachItemNode { itemNode in if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item { if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker { @@ -175,7 +175,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont } }))) } - selectedItemNodeAndContent = (itemNode, StickerPreviewPeekContent(account: item.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in + selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: item.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -228,18 +228,18 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont f(.default) let _ = item.resultSelected(item.result, itemNode, itemNode.bounds) }))) - selectedItemNodeAndContent = (itemNode, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems)) + selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems)) } } } return .single(selectedItemNodeAndContent) } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil) return controller diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 930cdacc64..8eeca26f7b 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -16,7 +16,7 @@ import PremiumUI import UndoUI final class HorizontalStickersChatContextPanelInteraction { - var previewedStickerItem: StickerPackItem? + var previewedStickerItem: TelegramMediaFile? } private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? { @@ -173,7 +173,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { if let itemNode = strongSelf.gridNode.itemNodeAtPoint(strongSelf.view.convert(point, to: strongSelf.gridNode.view)) as? HorizontalStickerGridItemNode, let item = itemNode.stickerItem { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { var menuItems: [ContextMenuItem] = [] menuItems = [ @@ -241,7 +241,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { } })) ] - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -255,11 +255,11 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil) return controller @@ -267,11 +267,11 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { return nil }, updateContent: { [weak self] content in if let strongSelf = self { - var item: StickerPackItem? + var file: TelegramMediaFile? if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item { - item = contentItem + file = contentItem } - strongSelf.updatePreviewingItem(item: item, animated: true) + strongSelf.updatePreviewingItem(file: file, animated: true) } })) } @@ -366,9 +366,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { return super.hitTest(point, with: event) } - private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) { - if self.stickersInteraction.previewedStickerItem != item { - self.stickersInteraction.previewedStickerItem = item + private func updatePreviewingItem(file: TelegramMediaFile?, animated: Bool) { + if self.stickersInteraction.previewedStickerItem?.fileId != file?.fileId { + self.stickersInteraction.previewedStickerItem = file self.gridNode.forEachItemNode { itemNode in if let itemNode = itemNode as? HorizontalStickerGridItemNode { diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index 9a0280d1eb..4cac521101 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -42,7 +42,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie private weak var peekController: PeekController? - var previewedStickerItem: StickerPackItem? + var previewedStickerItem: TelegramMediaFile? var updateBackgroundOffset: ((CGFloat, Bool, ContainedViewLayoutTransition) -> Void)? var sendSticker: ((FileMediaReference, UIView, CGRect) -> Void)? @@ -101,7 +101,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie if let itemNode = selectedNode, let item = itemNode.stickerItem { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() { var menuItems: [ContextMenuItem] = [] @@ -196,7 +196,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie } })) ) - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() else { return } @@ -210,11 +210,11 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) controller.visibilityUpdated = { [weak self] visible in self?.previewingStickersPromise.set(visible) @@ -226,18 +226,18 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie return nil }, updateContent: { [weak self] content in if let strongSelf = self { - var item: StickerPackItem? + var item: TelegramMediaFile? if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item { item = contentItem } - strongSelf.updatePreviewingItem(item: item, animated: true) + strongSelf.updatePreviewingItem(file: item, animated: true) } })) } - private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) { - if self.previewedStickerItem != item { - self.previewedStickerItem = item + private func updatePreviewingItem(file: TelegramMediaFile?, animated: Bool) { + if self.previewedStickerItem?.fileId != file?.fileId { + self.previewedStickerItem = file for (_, itemNode) in self.itemNodes { itemNode.updatePreviewing(animated: animated) @@ -433,7 +433,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie file: item.file, theme: self.theme, isPreviewed: { [weak self] item in - return item.file.fileId == self?.previewedStickerItem?.file.fileId + return item.file.fileId == self?.previewedStickerItem?.fileId }, sendSticker: { [weak self] file, view, rect in self?.sendSticker?(file, view, rect) } diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift index 92ea1c0b39..b78c2e6063 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift @@ -186,7 +186,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { self.strings = strings self.trendingPane = ChatMediaInputTrendingPane(context: context, controllerInteraction: controllerInteraction, getItemIsPreviewed: { [weak inputNodeInteraction] item in - return inputNodeInteraction?.previewedStickerPackItem == .pack(item) + return inputNodeInteraction?.previewedStickerPackItem == .pack(item.file) }, isPane: false) self.gridNode = GridNode() @@ -324,7 +324,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { let _ = strongSelf.controllerInteraction.sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) } }, getItemIsPreviewed: { item in - return inputNodeInteraction.previewedStickerPackItem == .pack(item) + return inputNodeInteraction.previewedStickerPackItem == .pack(item.file) }) self._ready.set(self.trendingPane.ready) @@ -561,7 +561,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? { if !self.trendingPane.isHidden { if let (itemNode, item) = self.trendingPane.itemAt(point: self.view.convert(point, to: self.trendingPane.view)) { - return (itemNode, StickerPreviewPeekItem.pack(item)) + return (itemNode, StickerPreviewPeekItem.pack(item.file)) } } else { if let itemNode = self.gridNode.itemNodeAtPoint(self.view.convert(point, to: self.gridNode.view)) { @@ -569,7 +569,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { return (itemNode, StickerPreviewPeekItem.found(stickerItem)) } else if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { if let (node, item) = itemNode.itemAt(point: self.view.convert(point, to: itemNode.view)) { - return (node, StickerPreviewPeekItem.pack(item)) + return (node, StickerPreviewPeekItem.pack(item.file)) } } } diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift index d313b5cb28..ea088a38f6 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelItem.swift @@ -162,7 +162,7 @@ final class StickersChatInputContextPanelItemNode: ListViewItemNode { var previewingIndex: Int? = nil for i in 0 ..< item.files.count { - if item.stickersInteraction.previewedStickerItem == self.stickerItem(at: i) { + if item.stickersInteraction.previewedStickerItem?.fileId == self.stickerItem(at: i)?.file.fileId { previewingIndex = i break } diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift index f72a055c94..1de8bf6eda 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift @@ -20,7 +20,7 @@ private struct StickersChatInputContextPanelEntryStableId: Hashable { } final class StickersChatInputContextPanelInteraction { - var previewedStickerItem: StickerPackItem? + var previewedStickerItem: TelegramMediaFile? } private struct StickersChatInputContextPanelEntry: Identifiable, Comparable { @@ -129,7 +129,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { if let (item, itemNode) = stickersNode.stickerItem(at: point) { return strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId) |> deliverOnMainQueue - |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + |> map { isStarred -> (UIView, CGRect, PeekControllerContent)? in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { var menuItems: [ContextMenuItem] = [] menuItems = [ @@ -197,7 +197,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { } })) ] - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -212,11 +212,11 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { } } return nil - }, present: { [weak self] content, sourceNode in + }, present: { [weak self] content, sourceView, sourceRect in if let strongSelf = self { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = PeekController(presentationData: presentationData, content: content, sourceNode: { - return sourceNode + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) }) strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil) return controller @@ -224,18 +224,18 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { return nil }, updateContent: { [weak self] content in if let strongSelf = self { - var item: StickerPackItem? + var item: TelegramMediaFile? if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item { item = contentItem } - strongSelf.updatePreviewingItem(item: item, animated: true) + strongSelf.updatePreviewingItem(file: item, animated: true) } })) } - private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) { - if self.stickersInteraction.previewedStickerItem != item { - self.stickersInteraction.previewedStickerItem = item + private func updatePreviewingItem(file: TelegramMediaFile?, animated: Bool) { + if self.stickersInteraction.previewedStickerItem?.fileId != file?.fileId { + self.stickersInteraction.previewedStickerItem = file self.listView.forEachItemNode { itemNode in if let itemNode = itemNode as? StickersChatInputContextPanelItemNode {