// // TableRowView.swift // TGUIKit // // Created by keepcoder on 07/09/16. // Copyright © 2016 Telegram. All rights reserved. // import Cocoa import SwiftSignalKit import AVFoundation import Foundation open class TableRowView: NSTableRowView, CALayerDelegate { private var animatedView: RowAnimateView? private let longDisposable = MetaDisposable() public internal(set) var isResorting: Bool = false { didSet { updateIsResorting() } } var dynamicContentStateForRestore:Bool? = nil var interactionStateForRestore:Bool? = nil public var isDynamicContentLocked:Bool = false { didSet { if isDynamicContentLocked != oldValue { viewDidUpdatedDynamicContent() } } } public var userInteractionEnabled:Bool = true { didSet { if userInteractionEnabled != oldValue { viewDidUpdatedInteractivity() } } } open func viewDidUpdatedInteractivity() { } open func viewDidUpdatedDynamicContent() { self._updateAnimatableContent() } open private(set) var item:TableRowItem? private let menuDisposable = MetaDisposable() // var selected:Bool? open var border:BorderType? public var animates:Bool = true public private(set) var contextMenu:AppMenu? required public override init(frame frameRect: NSRect) { super.init(frame: frameRect) // self.layer = (self.layerClass() as! CALayer.Type).init() self.wantsLayer = true backgroundColor = .clear self.layerContentsRedrawPolicy = .never layer?.masksToBounds = true autoresizingMask = [] // self.layer?.delegate = self autoresizesSubviews = false pressureConfiguration = NSPressureConfiguration(pressureBehavior: .primaryDeepClick) appearance = presentation.appearance } // open override var translatesAutoresizingMaskIntoConstraints: Bool { // get { // return false // } // set { // // } // } open func updateColors() { self.layer?.backgroundColor = backdorColor.cgColor } open override func smartMagnify(with event: NSEvent) { super.smartMagnify(with: event) } open var backdorColor: NSColor { if let item = self.item { return item.backdorColor } return presentation.colors.background } open var borderColor: NSColor { if let item = self.item { return item.borderColor } return presentation.colors.border } open var isSelect: Bool { return item?.isSelected ?? false } open var isHighlighted: Bool { return item?.isHighlighted ?? false } open override func draw(_ dirtyRect: NSRect) { var bp:Int = 0 bp += 1 } open func updateIsResorting() { } open override func isAccessibilityElement() -> Bool { return false } open func draw(_ layer: CALayer, in ctx: CGContext) { // ctx.setFillColor(backdorColor.cgColor) // ctx.fill(layer.bounds) // layer.draw(in: ctx) if let border = border { ctx.setFillColor(borderColor.cgColor) if border.contains(.Top) { ctx.fill(NSMakeRect(0, frame.height - .borderSize, frame.width, .borderSize)) } if border.contains(.Bottom) { ctx.fill(NSMakeRect(0, 0, frame.width, .borderSize)) } if border.contains(.Left) { ctx.fill(NSMakeRect(0, 0, .borderSize, frame.height)) } if border.contains(.Right) { ctx.fill(NSMakeRect(frame.width - .borderSize, 0, .borderSize, frame.height)) } } } open func interactionContentView(for innerId: AnyHashable, animateIn: Bool ) -> NSView { return self } open func interactionControllerDidFinishAnimation(interactive: Bool, innerId: AnyHashable) { } open func addAccesoryOnCopiedView(innerId: AnyHashable, view: NSView) { } open func videoTimebase(for innerId: AnyHashable) -> CMTimebase? { return nil } open func applyTimebase(for innerId: AnyHashable, timebase: CMTimebase?) { } open func hasFirstResponder() -> Bool { return false } open func nextResponder() -> NSResponder? { return nil } open var firstResponder:NSResponder? { return self } open override func mouseMoved(with event: NSEvent) { super.mouseMoved(with: event) updateMouse(animated: true) } open override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) updateMouse(animated: true) } open override func mouseExited(with event: NSEvent) { super.mouseMoved(with: event) updateMouse(animated: true) } open override func mouseDown(with event: NSEvent) { if event.modifierFlags.contains(.control) && event.clickCount == 1 { showContextMenu(event) } else { if event.clickCount == 2 { doubleClick(in: convert(event.locationInWindow, from: nil)) return } super.mouseDown(with: event) } } private var lastPressureEventStage = 0 open override func pressureChange(with event: NSEvent) { super.pressureChange(with: event) if event.stage >= 1 && event.stage != lastPressureEventStage { longDisposable.set(delaySignal(0.5).start(completed: { [weak self] in if let strongSelf = self { if NSEvent.pressedMouseButtons & (1 << 0) != 0 { if strongSelf.window?.mouseLocationOutsideOfEventStream == event.locationInWindow { strongSelf.forceClick(in: strongSelf.convert(event.locationInWindow, from: nil)) } } } })) } if event.stage < 1 { longDisposable.set(nil) } if event.stage == 2 && lastPressureEventStage < 2 { longDisposable.set(nil) forceClick(in: convert(event.locationInWindow, from: nil)) } lastPressureEventStage = event.stage } open override func rightMouseDown(with event: NSEvent) { super.rightMouseDown(with: event) if mouseInside() { showContextMenu(event) } } open func doubleClick(in location:NSPoint) -> Void { } open func forceClick(in location: NSPoint) { } open func canMultiselectTextIn(_ location: NSPoint) -> Bool { return true } open func convertWindowPointToContent(_ point: NSPoint) -> NSPoint { return convert(point, from: nil) } open func canAnimateUpdate(_ item: TableRowItem) -> Bool { return true } open func showContextMenu(_ event:NSEvent) -> Void { menuDisposable.set(nil) contextMenu = nil if let item = item { let items = item.menuItems(in: convertWindowPointToContent(event.locationInWindow)) |> deliverOnMainQueue |> take(1) menuDisposable.set(combineLatest(queue: .mainQueue(), items, item.menuAdditionView).start(next: { [weak self, weak item] items, topWindow in if let strongSelf = self, let item = item { let menu = ContextMenu(presentation: item.menuPresentation, isLegacy: item.isLegacyMenu) menu.topWindow = topWindow menu.onShow = { [weak strongSelf] menu in strongSelf?.onShowContextMenu() } menu.onClose = { [weak strongSelf] in strongSelf?.contextMenu = nil strongSelf?.onCloseContextMenu() } for item in items { menu.addItem(item) } strongSelf.contextMenu = AppMenu(menu: menu) strongSelf.contextMenu?.show(event: event, view: strongSelf) } })) } } open override func menu(for event: NSEvent) -> NSMenu? { return NSMenu() } open func onShowContextMenu() ->Void { self.layer?.setNeedsDisplay() updateColors() } open func onCloseContextMenu() ->Void { self.layer?.setNeedsDisplay() updateColors() } required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } open func updateMouse(animated: Bool) { } public var isInsertionAnimated:Bool { if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { return true } return false } public var rect:NSRect { if let layer = layer?.presentation(), layer.animation(forKey: "position") != nil { let rect = NSMakeRect(layer.position.x, layer.position.y, frame.width, frame.height) return rect } return frame } open override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) } override public static var isCompatibleWithResponsiveScrolling: Bool { return true } open override func viewDidMoveToSuperview() { super.viewDidMoveToSuperview() _updateListeners() _updateAnimatableContent() } open override func layout() { super.layout() self.updateLayout(size: self.frame.size, transition: .immediate) } public func notifySubviewsToLayout(_ subview:NSView) -> Void { for sub in subview.subviews { sub.needsLayout = true } } open override var needsLayout: Bool { set { super.needsLayout = newValue if newValue { guard #available(OSX 10.12, *) else { layout() notifySubviewsToLayout(self) return } } } get { return super.needsLayout } } deinit { longDisposable.dispose() menuDisposable.dispose() removeNotificationListeners() } open var mouseInsideField: Bool { return false } open override func copy() -> Any { let view:View = View(frame:bounds) view.backgroundColor = self.backdorColor return view } open func onRemove(_ animation: NSTableView.AnimationOptions) { } open func onInsert(_ animation: NSTableView.AnimationOptions, appearAnimated: Bool) { } open func set(item:TableRowItem, animated:Bool = false) -> Void { self.item = item; updateColors() self._updateAnimatableContent() } open func focusAnimation(_ innerId: AnyHashable?, text: String?) { if animatedView == nil { self.animatedView = RowAnimateView(frame: bounds) self.animatedView?.isEventLess = true self.addSubview(animatedView!) animatedView?.backgroundColor = presentation.colors.focusAnimationColor animatedView?.layer?.opacity = 0 } animatedView?.stableId = item?.stableId let animation: CABasicAnimation = makeSpringAnimation("opacity") animation.fromValue = animatedView?.layer?.presentation()?.opacity ?? 0 animation.toValue = 0.5 animation.autoreverses = true animation.isRemovedOnCompletion = true animation.fillMode = CAMediaTimingFillMode.forwards animation.delegate = CALayerAnimationDelegate(completion: { [weak self] completed in if completed { self?.animatedView?.removeFromSuperview() self?.animatedView = nil } }) animation.isAdditive = false animatedView?.layer?.add(animation, forKey: "opacity") } open var interactableView: NSView { return self } open func shakeView() { } open func shakeViewWithData(_ data: Any) { } open func mouseInside() -> Bool { return super._mouseInside() } open var rowAppearance: NSAppearance? { return self.appearance } open func updateLayout(size: NSSize, transition: ContainedViewLayoutTransition) { } @objc private func _updateAnimatableContent() { DispatchQueue.main.async { [weak self] in self?.updateAnimatableContent() } } open func updateAnimatableContent() { } open var isEmojiLite: Bool { return false } open override func viewDidMoveToWindow() { super.viewDidMoveToWindow() _updateListeners() _updateAnimatableContent() } private func _updateListeners() { let center = NotificationCenter.default if let window = window { center.removeObserver(self) center.addObserver(self, selector: #selector(_updateAnimatableContent), name: NSWindow.didBecomeKeyNotification, object: window) center.addObserver(self, selector: #selector(_updateAnimatableContent), name: NSWindow.didResignKeyNotification, object: window) center.addObserver(self, selector: #selector(_updateAnimatableContent), name: NSView.boundsDidChangeNotification, object: self.enclosingScrollView?.contentView) center.addObserver(self, selector: #selector(_updateAnimatableContent), name: NSView.frameDidChangeNotification, object: self.enclosingScrollView?.documentView) center.addObserver(self, selector: #selector(_updateAnimatableContent), name: NSView.frameDidChangeNotification, object: self) } else { center.removeObserver(self) } } open override func cursorUpdate(with event: NSEvent) { super.cursorUpdate(with: event) NSCursor.arrow.set() } private func removeNotificationListeners() { NotificationCenter.default.removeObserver(self) } }