import Foundation import UIKit import AsyncDisplayKit import Display import TelegramPresentationData import ManagedAnimationNode import ContextUI public final class MoreButtonNode: ASDisplayNode { public class MoreIconNode: ManagedAnimationNode { public enum State: Equatable { case more case search } private let duration: Double = 0.21 public var iconState: State = .search init() { super.init(size: CGSize(width: 30.0, height: 30.0)) self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0)) } func play() { if case .more = self.iconState { self.trackTo(item: ManagedAnimationItem(source: .local("anim_moredots"), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76)) } } public func enqueueState(_ state: State, animated: Bool) { guard self.iconState != state else { return } let previousState = self.iconState self.iconState = state let source = ManagedAnimationSource.local("anim_moretosearch") let totalLength: Int = 90 if animated { switch previousState { case .more: switch state { case .more: break case .search: self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: totalLength), duration: self.duration)) } case .search: switch state { case .more: self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: 0), duration: self.duration)) case .search: break } } } else { switch state { case .more: self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: 0), duration: 0.0)) case .search: self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: totalLength), duration: 0.0)) } } } } public var action: ((ASDisplayNode, ContextGesture?) -> Void)? private let containerNode: ContextControllerSourceNode public let contextSourceNode: ContextReferenceContentNode private let buttonNode: HighlightableButtonNode public let iconNode: MoreIconNode public var theme: PresentationTheme { didSet { self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor } } public init(theme: PresentationTheme) { self.theme = theme self.contextSourceNode = ContextReferenceContentNode() self.containerNode = ContextControllerSourceNode() self.containerNode.animateScale = false self.buttonNode = HighlightableButtonNode() self.iconNode = MoreIconNode() self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor super.init() self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.containerNode) self.containerNode.addSubnode(self.contextSourceNode) self.contextSourceNode.addSubnode(self.iconNode) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.containerNode.activated = { [weak self] gesture, _ in guard let strongSelf = self else { return } if case .more = strongSelf.iconNode.iconState { strongSelf.action?(strongSelf.contextSourceNode, gesture) } } } @objc private func buttonPressed() { self.action?(self.contextSourceNode, nil) if case .more = self.iconNode.iconState { self.iconNode.play() } } override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let animationSize = CGSize(width: 30.0, height: 30.0) let inset: CGFloat = 0.0 self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 6.0, y: floor((constrainedSize.height - animationSize.height) / 2.0) + 1.0), size: animationSize) let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height) let bounds = CGRect(origin: CGPoint(), size: size) self.buttonNode.frame = bounds self.containerNode.frame = bounds self.contextSourceNode.frame = bounds return size } }