519 lines
15 KiB
Swift
519 lines
15 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
|
|
}
|