// // NavigationViewController.swift // TGUIKit // // Created by keepcoder on 15/09/16. // Copyright © 2016 Telegram. All rights reserved. // import Cocoa import SwiftSignalKit import AppKit public enum ViewControllerStyle { case push case pop case none } open class NavigationHeaderView : View { public private(set) weak var header:NavigationHeader? public let ready:Promise = Promise() public private(set) var height: CGFloat public init(_ header:NavigationHeader) { self.header = header self.height = header.height super.init(frame: NSMakeRect(0, 0, 0, header.height)) self.autoresizingMask = [.width] } open func update(with contextObject: Any) { self.header?.contextObject = contextObject } open func destroy() { self.header?.contextObject = nil } open func hide(_ animated: Bool) { destroy() self.header?.hide(animated) } required public init(frame frameRect: NSRect) { fatalError("init(frame:) has not been implemented") } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } open class NavigationHeader { public var contextObject: Any? = nil fileprivate weak var supplyHeader:NavigationHeader? fileprivate weak var simpleHeader:NavigationHeader? public let height:CGFloat fileprivate var realHeight: CGFloat = 0 let initializer:(NavigationHeader, Any, NavigationHeaderView?)->(NavigationHeaderView, CGFloat) weak var navigation:NavigationViewController? fileprivate let disposable:MetaDisposable = MetaDisposable() fileprivate(set) var isShown:Bool = false public var needShown:Bool = false public init(_ height:CGFloat, initializer:@escaping(NavigationHeader, Any, NavigationHeaderView?)->(NavigationHeaderView, CGFloat)) { self.height = height self.initializer = initializer } fileprivate var _view:NavigationHeaderView? public var view:NavigationHeaderView { return _view! } deinit { disposable.dispose() } open func show(_ animated:Bool, contextObject: Any) { assert(navigation != nil) self.contextObject = contextObject let initialized = initializer(self, contextObject, self._view) if isShown { if initialized.0 === _view { return } else if _view != nil { hide(animated) } } isShown = true if let navigation = navigation { readyShow(navigation, animated: animated, initialized: initialized) } } func readyShow(_ navigation: NavigationViewController, animated: Bool, initialized: (NavigationHeaderView, CGFloat)) { self._view = initialized.0 self.realHeight = initialized.1 let view = self.view let realHeight = self.realHeight let height = self.height view.frame = NSMakeRect(0, -height, navigation.containerView.frame.width, realHeight) disposable.set((view.ready.get() |> filter {$0} |> take(1)).start(next: { [weak navigation, weak self, weak view] (ready) in if let navigation = navigation, let view = view { var contentInset = height + navigation.controller.barHeight self?.needShown = true if let header = self?.supplyHeader, header.needShown { navigation.containerView.addSubview(view, positioned: .below, relativeTo: header.view) } else { navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) } var inset:CGFloat = 0 var barInset: CGFloat = height if let supplyHeader = self?.supplyHeader, supplyHeader.needShown { inset += supplyHeader.height contentInset += supplyHeader.height barInset += supplyHeader.height } let transition: ContainedViewLayoutTransition if animated { transition = .animated(duration: 0.2, curve: .easeOut) } else { transition = .immediate } let size = navigation.navigationBar.frame.size transition.updateFrame(view: navigation.navigationBar, frame: CGRect(origin: NSMakePoint(0, barInset), size: size)) transition.updateFrame(view: view, frame: NSMakeRect(0, inset, view.frame.width, view.frame.height)) navigation.controller.updateFrame(NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset), transition: transition) } })) } open func hide(_ animated:Bool) { assert(navigation != nil) self.contextObject = nil if !isShown { return } needShown = false isShown = false if let navigation = navigation, let view = _view { _view = nil performSubviewPosRemoval(view, pos: NSMakePoint(0, -height), animated: animated, duration: 0.2, timingFunction: .easeOut) var barInset: CGFloat = 0 var inset:CGFloat = navigation.controller.barHeight if let supplyHeader = supplyHeader, supplyHeader.needShown { inset += supplyHeader.height barInset += supplyHeader.height } let transition: ContainedViewLayoutTransition if animated { transition = .animated(duration: 0.2, curve: .easeOut) } else { transition = .immediate } let size = navigation.navigationBar.frame.size transition.updateFrame(view: navigation.navigationBar, frame: CGRect(origin: NSMakePoint(0, barInset), size: size)) navigation.controller.updateFrame(CGRect(origin: NSMakePoint(0, inset), size: NSMakeSize(navigation.controller.frame.width, navigation.frame.height - inset)), transition: transition) } } } public class CallNavigationHeader : NavigationHeader { override func readyShow(_ navigation: NavigationViewController, animated: Bool, initialized: (NavigationHeaderView, CGFloat)) { self._view = initialized.0 self.realHeight = initialized.1 self._view = initialized.0 self.realHeight = initialized.1 let view = self.view let realHeight = self.realHeight let height = self.height view.frame = NSMakeRect(0, -realHeight, navigation.containerView.frame.width, realHeight) disposable.set((view.ready.get() |> take(1)).start(next: { [weak navigation, weak view, weak self] (ready) in if let navigation = navigation, let view = view { self?.needShown = true var contentInset = height + navigation.controller.barHeight if let simpleHeader = self?.simpleHeader, simpleHeader.needShown { navigation.containerView.addSubview(view, positioned: .above, relativeTo: simpleHeader.view) } else { navigation.containerView.addSubview(view, positioned: .above, relativeTo: navigation.controller.view) } var barInset: CGFloat = height let transition: ContainedViewLayoutTransition if animated { transition = .animated(duration: 0.2, curve: .easeOut) } else { transition = .immediate } if let simpleHeader = self?.simpleHeader, simpleHeader.needShown { let view = simpleHeader.view let size = view.frame.size transition.updateFrame(view: view, frame: CGRect(origin: NSMakePoint(0, height), size: size)) contentInset += simpleHeader.height barInset += simpleHeader.height } let size = navigation.navigationBar.frame.size transition.updateFrame(view: navigation.navigationBar, frame: CGRect(origin: NSMakePoint(0, barInset), size: size)) transition.updateFrame(view: view, frame: CGRect(origin: .zero, size: view.frame.size)) navigation.controller.updateFrame(NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset), transition: transition) } })) } public override func hide(_ animated:Bool) { assert(navigation != nil) if !isShown { return } needShown = false isShown = false if let navigation = navigation, let view = _view { _view = nil performSubviewPosRemoval(view, pos: NSMakePoint(0, -realHeight), animated: animated, duration: 0.2, timingFunction: .easeOut) let transition: ContainedViewLayoutTransition if animated { transition = .animated(duration: 0.2, curve: .easeOut) } else { transition = .immediate } var contentInset: CGFloat = navigation.controller.barHeight if let header = simpleHeader, header.needShown { transition.updateFrame(view: header.view, frame: CGRect.init(origin: NSMakePoint(0, 0), size: header.view.frame.size)) contentInset += header.height } transition.updateFrame(view: navigation.navigationBar, frame: CGRect.init(origin: NSMakePoint(0, contentInset - navigation.navigationBar.frame.height), size: navigation.navigationBar.frame.size)) navigation.controller.updateFrame(NSMakeRect(0, contentInset, navigation.controller.frame.width, navigation.frame.height - contentInset), transition: transition) } } } enum NavigationShadowDirection { case left, right } final class NavigationShadowView : View { required init(frame frameRect: NSRect) { super.init(frame: frameRect) } var direction: NavigationShadowDirection = .left { didSet { needsDisplay = true } } var getColor:()->NSColor = { presentation.colors.background } override func draw(_ layer: CALayer, in ctx: CGContext) { super.draw(layer, in: ctx) ctx.clear(NSMakeRect(0, 0, frame.width, frame.height)) ctx.setStrokeColor(getColor().cgColor) ctx.setShadow(offset: CGSize.zero, blur: 8, color: .black) ctx.setBlendMode(.multiply) ctx.strokeLineSegments(between: [CGPoint(x: bounds.width, y: 0), CGPoint(x: bounds.width, y: bounds.height)]) // let colorSpace = CGColorSpaceCreateDeviceRGB() // let array = [NSColor.clear.cgColor, NSColor.black.withAlphaComponent(0.2).cgColor] // let gradient = CGGradient(colorsSpace: colorSpace, colors: NSArray(array: array), locations: nil)! // // ctx.drawLinearGradient(gradient, start: NSMakePoint(0, 0), end: CGPoint(x: frame.width, y: 0), options: CGGradientDrawingOptions()) } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } open class NavigationViewController: ViewController, CALayerDelegate,CAAnimationDelegate { public var applyAppearOnLoad: Bool = true public var canAddControllers: Bool = true public private(set) var modalAction:NavigationModalAction? let shadowView:NavigationShadowView = NavigationShadowView(frame: NSMakeRect(0, 0, 20, 0)) let navigationRightBorder: View = View() let navigationLeftBorder: View = View() var stack:[ViewController] = [ViewController]() public var cleanupAfterDeinit: Bool = true var lock:Bool = false { didSet { var bp:Int = 0 bp += 1 } } public var hasBarRightBorder: Bool = false { didSet { navigationRightBorder.isEventLess = true navigationRightBorder.isHidden = !hasBarRightBorder navigationRightBorder.backgroundColor = presentation.colors.border } } public var hasBarLeftBorder: Bool = false { didSet { navigationLeftBorder.isEventLess = true navigationLeftBorder.isHidden = !hasBarLeftBorder navigationLeftBorder.backgroundColor = presentation.colors.border } } private let _window: Window open override var window: Window? { return _window } public var empty:ViewController { didSet { empty.navigationController = self let prev = self.stack.last self.stack.remove(at: 0) self.stack.insert(empty, at: 0) var controllerInset:CGFloat = 0 if let header = header, header.needShown { controllerInset += header.height } if let header = callHeader, header.needShown { controllerInset += header.height } let rect = NSMakeRect(0, controllerInset, self.bounds.width, self.bounds.height - controllerInset - bar.height) empty.loadViewIfNeeded(rect) if prev == oldValue { controller = empty oldValue.removeFromSuperview() containerView.addSubview(empty.view) if let header = header, header.needShown { header.view.removeFromSuperview() containerView.addSubview(header.view, positioned: .below, relativeTo: navigationBar) } if let header = callHeader, header.needShown { header.view.removeFromSuperview() containerView.addSubview(header.view, positioned: .below, relativeTo: navigationBar) } } empty.view.frame = rect } } open var isLocked:Bool { return lock } public private(set) var controller:ViewController { didSet { currentControllerDidChange() } } func _setController(_ controller:ViewController) { self.controller = controller } private(set) public var navigationBar:NavigationBarView = NavigationBarView() let pushDisposable:MetaDisposable = MetaDisposable() let popDisposable:MetaDisposable = MetaDisposable() private(set) public var header:NavigationHeader? private(set) public var callHeader:CallNavigationHeader? var containerView:BackgroundView = BackgroundView(frame: NSZeroRect) public var backgroundMode: TableBackgroundMode { get { return containerView.backgroundMode } set { containerView.backgroundMode = newValue } } public func doBackgroundAction() { DispatchQueue.main.async { [weak self] in self?.containerView.doAction() } } public func set(header:NavigationHeader?) { self.header?.hide(false) header?.navigation = self header?.supplyHeader = callHeader callHeader?.simpleHeader = header self.header = header } public func set(callHeader:CallNavigationHeader?) { self.callHeader?.hide(false) callHeader?.navigation = self header?.supplyHeader = callHeader callHeader?.simpleHeader = header self.callHeader = callHeader } open override func loadView() { super.loadView(); } public var navigationBarLeftPosition: CGFloat = 0 { didSet { navigationBar.needsLayout = true } } open override func viewDidLoad() { super.viewDidLoad() containerView.frame = bounds //self.view.autoresizesSubviews = true //containerView.autoresizesSubviews = true //containerView.autoresizingMask = [.width, .height] self.view.addSubview(containerView, positioned: .below, relativeTo: self.view.subviews.first) controller._frameRect = bounds if self.applyAppearOnLoad { controller.viewWillAppear(false) } controller.navigationController = self containerView.addSubview(navigationBar) navigationBar.frame = NSMakeRect(0, 0, NSWidth(containerView.frame), controller.barHeight) controller.view.frame = NSMakeRect(0, controller.barHeight , NSWidth(containerView.frame), NSHeight(containerView.frame) - controller.barHeight) navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: .none, animationStyle: controller.animationStyle, liveSwiping: false) containerView.addSubview(controller.view) self.view.addSubview(navigationRightBorder) self.view.addSubview(navigationLeftBorder) // self.view.addSubview(navigationLeftBorder) navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) navigationLeftBorder.frame = NSMakeRect(-.borderSize, 0, .borderSize, frame.height) if self.applyAppearOnLoad { Queue.mainQueue().justDispatch { self.controller.viewDidAppear(false) } } } var barInset: CGFloat { var barInset:CGFloat = 0 if let header = header, header.needShown { barInset += header.height } if let header = callHeader, header.needShown { barInset += header.height } return barInset } override open func swapNavigationBar(leftView: BarView?, centerView: BarView?, rightView: BarView?, animation: NavigationBarSwapAnimation) { navigationBar.frame = NSMakeRect(0, self.navigationBar.frame.minY, containerView.frame.width, controller.barHeight) if let leftView = leftView { navigationBar.switchLeftView(leftView, animation: animation) } if let centerView = centerView { navigationBar.switchCenterView(centerView, animation: animation) } if let rightView = rightView { navigationBar.switchRightView(rightView, animation: animation) } } var containerSize: NSSize { return frame.size } open override func viewDidResized(_ size: NSSize) { super.viewDidResized(size) let settings = self.controller.stake let keepLeft: CGFloat = size.width == settings.keepLeft ? 0 : settings.keepLeft let keepTop = size.width == settings.keepLeft ? 0 : settings.keepTop() let width = containerSize.width - keepLeft let point = NSMakePoint(keepLeft, 0) containerView.frame = NSMakeRect(0, 0, containerSize.width, containerSize.height) navigationBar.frame = NSMakeRect(point.x, navigationBar.frame.minY, width, controller.bar.height) navigationRightBorder.frame = NSMakeRect(size.width - .borderSize, keepTop, .borderSize, frame.height) navigationLeftBorder.frame = NSMakeRect(point.x - .borderSize, keepTop, .borderSize, size.height) if let header = callHeader, header.needShown { header.view.setFrameSize(NSMakeSize(width, header.realHeight)) } if let header = header, header.needShown { header.view.setFrameSize(NSMakeSize(width, header.realHeight)) } if controller.isLoaded() { controller.frame = NSMakeRect(point.x, barInset + controller.barHeight + keepTop, width, containerSize.height - barInset - controller.barHeight) if let tied = controller.tied { containerView.addSubview(tied.view, positioned: .below, relativeTo: controller.view) tied.view.frame = NSMakeRect(0, barInset + tied.bar.height, containerSize.width, containerSize.height - barInset - tied.bar.height) if tied.widthOnDisappear != nil { tied.widthOnDisappear = containerSize.width } } } } public func cancelCurrentController() { pushDisposable.set(nil) } open override var canBecomeResponder: Bool { return false } open func currentControllerDidChange() { } public override var backgroundColor: NSColor { set { self.view.background = newValue navigationBar.backgroundColor = newValue } get { return self.view.background } } public init(_ empty:ViewController, _ window: Window) { self.empty = empty self.controller = empty self._window = window self.stack.append(controller) super.init() navigationBar.navigation = self bar = .init(height: 0) shadowView.getColor = { [weak self] in self?.controller.backgroundColor ?? presentation.colors.background } } public var stackCount:Int { return stack.count } deinit { self.popDisposable.dispose() self.pushDisposable.dispose() if cleanupAfterDeinit { while !stack.isEmpty { let value = stack.removeFirst() value.removeFromSuperview() } } } public func stackInsert(_ controller:ViewController, at: Int) -> Void { controller.navigationController = self controller.loadViewIfNeeded(self.bounds) stack.insert(controller, at: at) } open func push(_ controller:ViewController, _ animated:Bool = true, style: ViewControllerStyle? = nil) -> Void { // if isLocked { // return // } if !self.canAddControllers { return } if controller.abolishWhenNavigationSame, controller.className == self.controller.className { return } controller.navigationController = self controller.loadViewIfNeeded(self.bounds) self.pushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in if let strongSelf = self { controller.navigationController = strongSelf if let index = strongSelf.stack.firstIndex(of: controller) { strongSelf.stack.remove(at: index) } strongSelf.stack.append(controller) let newStyle:ViewControllerStyle if let style = style { newStyle = style } else { newStyle = animated && strongSelf.stack.count > 1 ? .push : .none } CATransaction.begin() strongSelf.show(controller, newStyle) CATransaction.commit() } })) } func show(_ controller:ViewController,_ style:ViewControllerStyle) -> Void { lock = true guard let window = self.window else {return} if style == .none { window.abortSwiping() } defer { CATransaction.commit() } CATransaction.begin() let keepLeft = frame.width == controller.stake.keepLeft ? 0 : controller.stake.keepLeft let previous:ViewController = self.controller; controller.navigationController = self previous.setToNextController(controller, style: style) controller.setToPreviousController(previous, style: style) let keepTop = frame.width == controller.stake.keepLeft ? 0 : controller.stake.keepTop() var contentInset = controller.barHeight + keepTop let size = CGSize(width: containerView.frame.width - keepLeft, height: containerView.frame.height) let point = NSMakePoint(keepLeft, contentInset) if(previous === controller && stackCount > 1) { previous.viewWillDisappear(false) previous.viewDidDisappear(false) controller.viewWillAppear(false) controller.viewDidAppear(false) _ = controller.becomeFirstResponder() lock = false navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, keepTop, .borderSize, frame.height) navigationLeftBorder.frame = NSMakeRect(keepLeft - .borderSize, keepTop, .borderSize, frame.height) return; } var barInset:CGFloat = 0 if let header = callHeader, header.needShown { header.view.frame = NSMakeRect(point.x, 0, size.width, header.realHeight) contentInset += header.height barInset += header.height } if let header = header, header.needShown { header.view.frame = NSMakeRect(point.x, barInset, size.width, header.realHeight) containerView.addSubview(header.view, positioned: .below, relativeTo: self.navigationBar) contentInset += header.height barInset += header.height } let animatePosBar: Bool = controller.bar.height != previous.bar.height && style != .none self.navigationBar.frame = NSMakeRect(point.x, barInset, size.width, animatePosBar && controller.bar.height == 0 ? previous.bar.height : controller.bar.height) self.navigationBar.layer?.removeAllAnimations() if keepLeft > 0 || previous.stake.keepIn { controller.tied = previous } else { controller.tied = nil } previous.tied = nil // controller.view.removeFromSuperview() let popInteractiveInset: CGFloat? = window.inLiveSwiping ? controller.frame.minX : nil controller.view.frame = NSMakeRect(point.x, contentInset, size.width, size.height - contentInset) let reloadHeaders = { [weak self] in if let header = self?.callHeader, header.needShown { self?.containerView.addSubview(header.view, positioned: .above, relativeTo: controller.view) } if let header = self?.header, header.needShown { self?.containerView.addSubview(header.view, positioned: .above, relativeTo: controller.view) } } var pfrom:CGFloat = 0, pto:CGFloat = 0, nto:CGFloat = 0, nfrom:CGFloat = 0; var sto: CGFloat = 0 switch style { case .push: controller.view.disableHierarchyDynamicContent() previous.viewWillDisappear(true); controller.viewWillAppear(true); nfrom = popInteractiveInset != nil ? popInteractiveInset! : frame.width nto = keepLeft pfrom = previous.view.frame.minX if previous.stake.keepLeft > 0 && previous.stake.keepLeft != frame.width { pto = frame.maxX } else if keepLeft > 0 { pto = 0 } else { pto = -round(NSWidth(self.frame)/3.0) } previous.view.setFrameOrigin(NSMakePoint(pfrom, previous.frame.minY)) controller.view.setFrameOrigin(NSMakePoint(nfrom, controller.frame.minY)) containerView.addSubview(controller.view, positioned: .above, relativeTo: previous.view) sto = -shadowView.frame.width + keepLeft if controller.isOpaque { addShadowView(.right, controller: controller, updateOrigin: shadowView.superview == nil) } if animatePosBar { navigationBar.layer?.animatePosition(from: NSMakePoint(nfrom, navigationBar.frame.minY), to: NSMakePoint(nto, barInset), duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function) } case .pop: controller.view.disableHierarchyDynamicContent() previous.viewWillDisappear(true); controller.viewWillAppear(true); if let popInteractiveInset = popInteractiveInset { nfrom = popInteractiveInset } else if previous.stake.keepLeft > 0 && previous.stake.keepLeft != frame.width { nfrom = 0 } else { nfrom = -round(frame.width/3.0) } nto = 0 pfrom = previous.view.frame.minX pto = frame.width previous.view.setFrameOrigin(NSMakePoint(pfrom, previous.frame.minY)) controller.view.setFrameOrigin(NSMakePoint(nfrom, controller.frame.minY)) containerView.addSubview(controller.view, positioned: .below, relativeTo: previous.view) if animatePosBar { navigationBar.layer?.animatePosition(from: NSMakePoint(pfrom, barInset), to: NSMakePoint(pto, barInset), duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function, removeOnCompletion: false, completion: { [weak controller, weak navigationBar] completed in if let controller = controller { navigationBar?.frame = NSMakeRect(0, barInset, controller.frame.width, controller.bar.height) } navigationBar?.layer?.removeAllAnimations() }) } sto = frame.width if controller.isOpaque { addShadowView(.left, controller: previous, updateOrigin: shadowView.superview == nil) } case .none: previous.viewWillDisappear(false); previous.view.removeFromSuperview() containerView.addSubview(controller.view) self.controller = controller controller.viewWillAppear(false); previous.viewDidDisappear(false); controller.viewDidAppear(false); _ = controller.becomeFirstResponder(); self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: style, animationStyle: controller.animationStyle, liveSwiping: false) lock = false navigationBar.removeFromSuperview() navigationBar.frame = NSMakeRect(point.x, barInset, controller.frame.width, controller.bar.height) navigationBar.removeFromSuperview() containerView.addSubview(navigationBar, positioned: .below, relativeTo: self.controller.view) reloadHeaders() navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, keepTop, .borderSize, frame.height) navigationLeftBorder.frame = NSMakeRect(keepLeft - .borderSize, keepTop, .borderSize, frame.height) return // without animations } self.controller = controller let prevBackgroundView: NSView = containerView.copy(previous.bgMode) let nextBackgroundView: NSView = containerView.copy(controller.bgMode) if !controller.isOpaque { controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: controller.view.subviews.first) nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -controller.view.frame.minY)) } if !previous.isOpaque { previous.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: previous.view.subviews.first) prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -previous.view.frame.minY)) } if let index = stack.firstIndex(of: previous) { if previous.removeAfterDisapper { self.stack.remove(at: index) } else if previous.stake.keepLeft > 0 { self.stack.remove(at: index) } } containerView.addSubview(navigationBar, positioned: .below, relativeTo: self.controller.view) reloadHeaders() navigationRightBorder.frame = NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height) let n_left_previous = navigationLeftBorder.frame.minY if keepLeft > 0 { navigationLeftBorder.frame = NSMakeRect(keepLeft - .borderSize, keepTop, .borderSize, frame.height) navigationLeftBorder.layer?.animatePosition(from: NSMakePoint(nfrom, keepTop), to: navigationLeftBorder.frame.origin, duration: controller.animationStyle.duration, timingFunction: controller.animationStyle.function) } else { navigationLeftBorder.frame = NSMakeRect(pto - .borderSize, n_left_previous, .borderSize, frame.height) navigationLeftBorder.layer?.animatePosition(from: NSMakePoint(pfrom, n_left_previous), to: navigationLeftBorder.frame.origin, duration: controller.animationStyle.duration, timingFunction: controller.animationStyle.function) } shadowView.change(opacity: shadowView.direction == .left || keepLeft > 0 ? 0.2 : 1, animated: true, duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function) shadowView.change(pos: NSMakePoint(sto, shadowView.frame.minY), animated: true, duration: previous.animationStyle.duration, timingFunction: previous.animationStyle.function, completion: { [weak self] completed in if completed { self?.shadowView.removeFromSuperview() } }) if !animatePosBar || (animatePosBar && style == .push) { self.navigationBar.switchViews(left: controller.leftBarView, center: controller.centerBarView, right: controller.rightBarView, controller: controller, style: animatePosBar ? .none : style, animationStyle: controller.animationStyle, liveSwiping: window.inLiveSwiping) } previous.view._change(pos: NSMakePoint(pto, previous.frame.minY), animated: true, duration: controller.animationStyle.duration, timingFunction: .spring, completion: { [weak prevBackgroundView] completed in if completed, keepLeft == 0 || previous.stake.keepLeft > 0 { previous.view.removeFromSuperview() previous.viewDidDisappear(true); prevBackgroundView?.removeFromSuperview() } }); controller.view._change(pos: NSMakePoint(nto, controller.frame.minY), animated: true, duration: controller.animationStyle.duration, timingFunction: .spring, completion: { [weak nextBackgroundView, weak self] completed in guard let `self` = self else { return } if completed { controller.viewDidAppear(true); _ = controller.becomeFirstResponder() nextBackgroundView?.removeFromSuperview() } self.navigationRightBorder.frame = NSMakeRect(self.frame.width - .borderSize, 0, .borderSize, self.frame.height) self.lock = false controller.view.restoreHierarchyDynamicContent() }) } public func first(_ f:(ViewController)->Bool) -> ViewController? { return self.stack.first(where: f) } func addShadowView(_ shadowDirection: NavigationShadowDirection, controller: ViewController, updateOrigin: Bool = true) { if updateOrigin { shadowView.layer?.opacity = shadowDirection == .left ? 1.0 : 0.0 } shadowView.layer?.removeAllAnimations() var shadowInset: CGFloat = 0 if let callHeader = callHeader, callHeader.isShown { shadowInset += callHeader.height } let keepTop = frame.width == controller.stake.keepLeft ? 0 : controller.stake.keepTop() let keepLeft = frame.width == controller.stake.keepLeft ? 0 : controller.stake.keepLeft shadowInset += keepTop let leftInset: CGFloat = keepLeft shadowView.direction = shadowDirection shadowView.frame = NSMakeRect(updateOrigin ? (shadowDirection == .left ? -shadowView.frame.width + leftInset : containerView.frame.width) : shadowView.frame.minX, shadowInset, shadowView.frame.width, containerView.frame.height - shadowInset) containerView.addSubview(shadowView, positioned: .below, relativeTo: navigationBar) } open override func updateLocalizationAndTheme(theme: PresentationTheme) { super.updateLocalizationAndTheme(theme: theme) navigationBar.updateLocalizationAndTheme(theme: theme) if let callHeader = callHeader, callHeader.needShown { callHeader.view.updateLocalizationAndTheme(theme: theme) } if let header = header, header.needShown { header.view.updateLocalizationAndTheme(theme: theme) } navigationRightBorder.backgroundColor = presentation.colors.border for controller in self.stack { if controller != self.controller, controller.isLoaded() { controller.leftBarView.updateLocalizationAndTheme(theme: theme) controller.centerBarView.updateLocalizationAndTheme(theme: theme) controller.rightBarView.updateLocalizationAndTheme(theme: theme) } } } open func invokeBack(animated:Bool = true, forceAnimated: Bool = false, animationStyle: ViewControllerStyle = .pop, checkLock: Bool = true) { if !isLocked, let last = stack.last, !checkLock || last.invokeNavigationBack() { if stackCount > 1 { let controller = stack[stackCount - 2] stack.removeLast() last.didRemovedFromStack() show(controller, animated || forceAnimated ? animationStyle : .none) } else { doSomethingOnEmptyBack?() } } } open func back(animated:Bool = true, forceAnimated: Bool = false, animationStyle: ViewControllerStyle = .pop) -> Void { self.invokeBack(animated: animated, forceAnimated: forceAnimated, animationStyle: animationStyle, checkLock: true) } public var doSomethingOnEmptyBack: (()->Void)? = nil public func to( index:Int? = nil) -> Void { if stackCount > 1, let index = index { if index < 0 { gotoEmpty(false) } else { let controller = stack[index] let range = min(max(1, index + 1), stackCount) ..< stackCount let copy = stack[range] stack.removeSubrange(range) for controller in copy { controller.didRemovedFromStack() } show(controller, .none) } } } public func gotoEmpty(_ animated:Bool = true) -> Void { if controller != empty { for controller in stack { controller.viewWillDisappear(false) controller.didRemovedFromStack() controller.viewDidDisappear(false) } while stack.count != 1 { stack.removeLast() } show(empty, animated ? .pop : .none) } } public func close(animated:Bool = true) ->Void { if stackCount > 1 && !isLocked { let controller = stack[0] while stack.last != self.empty { stack.removeLast().didRemovedFromStack() } show(controller, animated ? .pop : .none) } } public func set(modalAction:NavigationModalAction, _ showView:Bool = true) { self.modalAction?.view?.removeFromSuperview() self.modalAction = modalAction modalAction.navigation = self if showView { let actionView = NavigationModalView(action: modalAction, viewController: self) modalAction.view = actionView actionView.frame = bounds view.addSubview(actionView) } } public func removeImmediately(_ controller: ViewController, upNext: Bool = true) { if let index = self.stack.firstIndex(where: { $0.internalId == controller.internalId }) { if index == self.stack.count - 1, upNext { if self.stack.count > index { show(self.stack[index - 1], .none) } else { show(self.empty, .none) } } else { controller.view.removeFromSuperview() } self.stack.remove(at: index) controller.didRemovedFromStack() } } public func index(of controller: ViewController) -> Int? { return self.stack.firstIndex(of: controller) } private let depencyReadyDisposable = MetaDisposable() public func removeImmediately(_ controller: ViewController, depencyReady: ViewController) { let ready = depencyReady.ready.get() |> take(1) |> deliverOnMainQueue depencyReadyDisposable.set(ready.start(completed: { [weak controller, weak self] in guard let controller = controller else { return } self?.removeImmediately(controller) })) } public func removeUntil(_ controllerType: ViewController.Type) { let index = stack.firstIndex(where: { current in return current.className == NSStringFromClass(controllerType) }) if let index = index { while stack.count - 1 > index { let controller = stack.removeLast() controller.didRemovedFromStack() } } else { while stack.count > 1 { let controller = stack.removeLast() controller.didRemovedFromStack() } } } public func removeAll() { stack.removeAll() } public func removeModalAction() { self.modalAction?.view?.removeFromSuperview() self.modalAction = nil } public func enumerateControllers(_ f:(ViewController, Int)->Bool) { for i in stride(from: stack.count - 1, to: -1, by: -1) { if f(stack[i], i) { break } } } open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if controller.redirectUserInterfaceCalls { controller.viewWillAppear(animated) } } open var previousController: ViewController? { if stackCount > 1 { return stack[stackCount - 2] } return nil } open var canSwipeBack: Bool { return self.stackCount > 1 // (self.genericView.state == .single || self.stackCount > 2) } open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.window?.add(swipe: { [weak self] direction, animated -> SwipeHandlerResult in guard let `self` = self, self.controller.view.layer?.animationKeys() == nil, let window = self.window else { return .failed } if let view = window.contentView!.hitTest(window.contentView!.convert(window.mouseLocationOutsideOfEventStream, from: nil))?.superview { if view is HorizontalRowView || view.superview is HorizontalRowView { return .failed } else if view.enclosingScrollView is HorizontalScrollView || view.enclosingScrollView is HorizontalTableView { return .failed } } if hasPopover(window) { return .failed } switch direction { case let .left(state): self.controller.updateSwipingState(state, controller: self.controller, isPrevious: false) self.previousController?.updateSwipingState(state, controller: self.controller, isPrevious: true) switch state { case .start: guard let previous = self.previousController, self.controller.supportSwipes, self.stackCount > 1 && !self.isLocked, self.canSwipeBack else {return .failed} previous.view.frame = NSMakeRect(0, previous.barHeight, self.frame.width, self.frame.height - previous.barHeight) self.containerView.addSubview(previous.view, positioned: .below, relativeTo: self.navigationBar) let prevBackgroundView = self.containerView.copy() as! NSView let nextBackgroundView = self.containerView.copy() as! NSView if !previous.isOpaque { previous.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: previous.view.subviews.first) prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -previous.view.frame.minY)) } if !self.controller.isOpaque { self.controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: self.controller.view.subviews.first) nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -self.controller.view.frame.minY)) } self.addShadowView(.left, controller: self.controller) if previous.bar.has { self.navigationBar.startMoveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction) } self.lock = true return .success(previous) case let .swiping(delta, previous): let settings = self.controller.stake let keepLeft: CGFloat = self.frame.width == settings.keepLeft ? 0 : settings.keepLeft let nPosition = min(max(keepLeft, delta + keepLeft), self.containerView.frame.width) self.controller.view._change(pos: NSMakePoint(nPosition, self.controller.view.frame.minY), animated: false) let previousStart = keepLeft > 0 ? 0 : -round(NSWidth(self.containerView.frame)/3.0) previous.view._change(pos: NSMakePoint(min(previousStart + max(delta, 0) / 3.0, 0), previous.view.frame.minY), animated: false) self.shadowView.setFrameOrigin(nPosition - self.shadowView.frame.width, self.shadowView.frame.minY) self.navigationLeftBorder.setFrameOrigin(nPosition - self.navigationLeftBorder.frame.width, self.navigationLeftBorder.frame.minY) self.shadowView.layer?.opacity = min(1.0 - Float(nPosition / self.containerView.frame.width) + 0.2, 1.0) if previous.bar.has { self.navigationBar.moveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction, percent: nPosition / self.containerView.frame.width) } else { self.navigationBar.setFrameOrigin(nPosition, self.navigationBar.frame.minY) } return .deltaUpdated(available: nPosition - keepLeft) case let .success(_, controller): self.lock = false controller.removeBackgroundCap() self.controller.removeBackgroundCap() self.back(forceAnimated: true) case let .failed(_, previous): // CATransaction.begin() let animationStyle = previous.animationStyle let settings = self.controller.stake let keepLeft: CGFloat = self.frame.width == settings.keepLeft ? 0 : settings.keepLeft self.controller.view._change(pos: NSMakePoint(keepLeft, self.controller.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) let previousStart = keepLeft > 0 ? 0 : -round(NSWidth(self.containerView.frame)/3.0) previous.view._change(pos: NSMakePoint(previousStart, previous.view.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self, weak previous] completed in if completed, keepLeft == 0 { previous?.view.removeFromSuperview() self?.controller.removeBackgroundCap() previous?.removeBackgroundCap() } }) self.shadowView.change(pos: NSMakePoint(-self.shadowView.frame.width + keepLeft, self.shadowView.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] completed in self?.shadowView.removeFromSuperview() }) self.navigationLeftBorder.change(pos: NSMakePoint(keepLeft - .borderSize, self.navigationLeftBorder.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) self.shadowView.change(opacity: 0, animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) if previous.bar.has { self.navigationBar.moveViews(left: previous.leftBarView, center: previous.centerBarView, right: previous.rightBarView, direction: direction, percent: 0, animationStyle: animationStyle) } else { self.navigationBar.change(pos: NSMakePoint(keepLeft, self.navigationBar.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) } self.lock = false // CATransaction.commit() } case let .right(state): self.controller.updateSwipingState(state, controller: self.controller, isPrevious: false) self.previousController?.updateSwipingState(state, controller: self.controller, isPrevious: true) switch state { case .start: guard let new = self.controller.rightSwipeController, !self.isLocked else {return .failed} new._frameRect = self.containerView.bounds new.view.setFrameOrigin(NSMakePoint(self.containerView.frame.width, self.controller.frame.minY)) let prevBackgroundView = self.containerView.copy() as! NSView let nextBackgroundView = self.containerView.copy() as! NSView if !new.isOpaque { new.view.addSubview(prevBackgroundView, positioned: .below, relativeTo: new.view.subviews.first) prevBackgroundView.setFrameOrigin(NSMakePoint(prevBackgroundView.frame.minX, -new.view.frame.minY)) } if !self.controller.isOpaque { self.controller.view.addSubview(nextBackgroundView, positioned: .below, relativeTo: self.controller.view.subviews.first) nextBackgroundView.setFrameOrigin(NSMakePoint(nextBackgroundView.frame.minX, -self.controller.view.frame.minY)) } self.containerView.addSubview(new.view, positioned: .above, relativeTo: self.controller.view) self.addShadowView(.right, controller: self.controller) self.navigationBar.startMoveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction) self.lock = true return .success(new) case let .swiping(delta, new): let delta = min(max(0, delta), self.containerView.frame.width) let nPosition = self.containerView.frame.width - delta // NSLog("\(nPosition)") new.view._change(pos: NSMakePoint(nPosition, new.frame.minY), animated: false) self.controller.view._change(pos: NSMakePoint(min(-delta / 3.0, 0), self.controller.view.frame.minY), animated: false) self.shadowView.setFrameOrigin(nPosition - self.shadowView.frame.width, self.shadowView.frame.minY) self.shadowView.layer?.opacity = min(1.0 - Float(nPosition / self.containerView.frame.width) + 0.2, 1.0) self.navigationBar.moveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction, percent: delta / self.containerView.frame.width) return .deltaUpdated(available: delta) case let .success(_, controller): self.lock = false controller.removeBackgroundCap() self.controller.removeBackgroundCap() self.push(controller, true, style: .push) case let .failed(_, new): // CATransaction.begin() let animationStyle = new.animationStyle var _new:ViewController? = new _new?.view._change(pos: NSMakePoint(self.containerView.frame.width, self.controller.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function) self.containerView.contentViews[1]._change(pos: NSMakePoint(0, self.containerView.contentViews[1].frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak new, weak self] completed in self?.controller.removeBackgroundCap() new?.view.removeFromSuperview() _new = nil }) self.shadowView.change(pos: NSMakePoint(self.containerView.frame.width, self.shadowView.frame.minY), animated: animated, duration: animationStyle.duration, timingFunction: animationStyle.function, completion: { [weak self] completed in self?.shadowView.removeFromSuperview() }) self.shadowView.change(opacity: 1, duration: animationStyle.duration, timingFunction: animationStyle.function) self.navigationBar.moveViews(left: new.leftBarView, center: new.centerBarView, right: new.rightBarView, direction: direction, percent: 0, animationStyle: animationStyle) self.lock = false // CATransaction.commit() } default: break } return .nothing }, with: self.containerView, identifier: "\(self.description)") if controller.redirectUserInterfaceCalls { controller.viewDidAppear(animated) } } open override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) self.window?.removeAllHandlers(for: self) if controller.redirectUserInterfaceCalls { controller.viewWillDisappear(animated) } } open override func scrollup(force: Bool = false) { super.scrollup() if controller.redirectUserInterfaceCalls { controller.scrollup() } } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) if controller.redirectUserInterfaceCalls { controller.viewDidDisappear(animated) } } open override func focusSearch(animated: Bool, text: String? = nil) { super.focusSearch(animated: animated) if controller.redirectUserInterfaceCalls { controller.focusSearch(animated: animated, text: text) } } }