KMThumbnailView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. //
  2. // KMThumbnailView.swift
  3. // PDF Master
  4. //
  5. // Created by tangchao on 2023/5/4.
  6. //
  7. import Cocoa
  8. @objc protocol KMThumbnailViewDelegate : NSObjectProtocol {
  9. // layout
  10. @objc optional func thumbnailView(thumbanView: KMThumbnailView, minimumLineSpacingForSectionAt section: Int) -> CGFloat
  11. @objc optional func thumbnailView(thumbanView: KMThumbnailView, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
  12. @objc optional func thumbnailView(thumbanView: KMThumbnailView, insetForSectionAt section: Int) -> NSEdgeInsets
  13. @objc optional func thumbnailView(thumbanView: KMThumbnailView, sizeForItemAt indexpath: IndexPath) -> NSSize
  14. @objc optional func thumbnailView(thumbanView: KMThumbnailView, numberOfItemsInSection section: Int) -> Int
  15. @objc optional func thumbnailView(thumbanView: KMThumbnailView, itemForRepresentedObjectAt indexpath: IndexPath) -> NSCollectionViewItem
  16. // Drag & Drop
  17. // 本地拖拽
  18. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didDrag dragedIndexPaths: [IndexPath], indexpath: IndexPath)
  19. // 外部拖拽
  20. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didDragAddFiles files: [URL], indexpath: IndexPath)
  21. @objc optional func thumbnailView(thumbanView: KMThumbnailView, didSelectItemAt indexpath: IndexPath, object: AnyObject?)
  22. @objc optional func thumbnailView(thumbanView: KMThumbnailView, rightMouseDidClick indexpath: IndexPath, item: NSCollectionViewItem?, object: AnyObject?)
  23. }
  24. @objc class KMThumbnailView: NSView {
  25. open weak var delegate: KMThumbnailViewDelegate?
  26. internal let localForDraggedTypes = kKMLocalForDraggedTypes
  27. internal var dragedIndexPaths: [IndexPath] = []
  28. override init(frame frameRect: NSRect) {
  29. super.init(frame: frameRect)
  30. self.initDefaultValue()
  31. self.initSubViews()
  32. }
  33. required public init?(coder: NSCoder) {
  34. super.init(coder: coder)
  35. self.initDefaultValue()
  36. self.initSubViews()
  37. }
  38. open var minimumLineSpacing: CGFloat = 0.0 {
  39. didSet {
  40. self.collectionView.reloadData()
  41. }
  42. }
  43. open var minimumInteritemSpacing: CGFloat = 0.0 {
  44. didSet {
  45. self.collectionView.reloadData()
  46. }
  47. }
  48. open var itemSize: NSSize = NSMakeSize(60, 80) {
  49. didSet {
  50. self.collectionView.reloadData()
  51. }
  52. }
  53. open var sectionInset: NSEdgeInsets = NSEdgeInsetsZero {
  54. didSet {
  55. self.collectionView.reloadData()
  56. }
  57. }
  58. open var numberOfSections: Int = 0 {
  59. didSet {
  60. self.collectionView.reloadData()
  61. }
  62. }
  63. // MARK: - Publick Methods
  64. public func initDefaultValue() {
  65. self.collectionView.registerForDraggedTypes([self.localForDraggedTypes, .fileURL,.string,.pdf])
  66. }
  67. public func initSubViews() {
  68. self.addSubview(self.scrollView)
  69. self.scrollView.frame = self.bounds
  70. self.scrollView.autoresizingMask = [.width, .height]
  71. self.scrollView.documentView = self.collectionView
  72. }
  73. // MARK: - register ItemClass
  74. open func register(_ itemClass: AnyClass?) {
  75. guard let itemClass_ = itemClass else {
  76. return
  77. }
  78. self.register(itemClass_, forItemWithIdentifier: NSStringFromClass(itemClass_))
  79. }
  80. open func register(_ itemClass: AnyClass?, forItemWithIdentifier identifier: String) {
  81. self.collectionView.register(itemClass, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier))
  82. }
  83. // MARK: - 刷新UI
  84. public func reloadData(indexs: Set<IndexPath> = NSSet() as! Set<IndexPath>) {
  85. if (indexs.count == 0) {
  86. self.collectionView.reloadData()
  87. } else {
  88. var indexpaths: Set<IndexPath> = []
  89. for index in indexs {
  90. if (index.section >= self.collectionView.numberOfSections) {
  91. continue
  92. }
  93. if (index.item >= self.collectionView.numberOfItems(inSection: index.section)) {
  94. continue
  95. }
  96. indexpaths.insert(index)
  97. }
  98. if (indexpaths.count == 0) {
  99. return
  100. }
  101. if (Thread.isMainThread) {
  102. self.collectionView.reloadItems(at: indexpaths)
  103. } else {
  104. DispatchQueue.main.async {
  105. self.collectionView.reloadItems(at: indexpaths)
  106. }
  107. }
  108. }
  109. }
  110. // MARK: - 属性 【懒加载】
  111. internal lazy var scrollView_: NSScrollView = {
  112. let view = NSScrollView()
  113. view.hasHorizontalScroller = true
  114. view.hasVerticalScroller = true
  115. view.autohidesScrollers = true
  116. view.minMagnification = 1.0
  117. view.scrollerStyle = .overlay
  118. view.wantsLayer = true
  119. view.layer?.backgroundColor = NSColor.clear.cgColor
  120. view.wantsLayer = true
  121. view.contentView.layer?.backgroundColor = .white
  122. return view
  123. }()
  124. var scrollView: NSScrollView {
  125. get {
  126. return self.scrollView_
  127. }
  128. }
  129. internal lazy var collectionView_: NSCollectionView = {
  130. let view = NSCollectionView()
  131. view.autoresizingMask = [.width, .height]
  132. let layout = NSCollectionViewFlowLayout()
  133. layout.sectionInset = NSEdgeInsetsMake(8, 15, 8, 15)
  134. layout.minimumLineSpacing = 0
  135. layout.minimumInteritemSpacing = 0
  136. view.collectionViewLayout = layout
  137. view.delegate = self
  138. view.dataSource = self
  139. view.register(NSCollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "KMPDFThumbnailItem"))
  140. view.isSelectable = true
  141. view.wantsLayer = true
  142. view.layer?.backgroundColor = NSColor(hex: "#F7F8FA").cgColor
  143. return view
  144. }()
  145. var collectionView: NSCollectionView {
  146. get {
  147. return self.collectionView_
  148. }
  149. }
  150. }
  151. // MARK: - NSCollectionViewDataSource, NSCollectionViewDelegate
  152. extension KMThumbnailView: NSCollectionViewDataSource {
  153. public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  154. if let items = self.delegate?.thumbnailView?(thumbanView: self, numberOfItemsInSection: section) {
  155. return items
  156. }
  157. return self.numberOfSections
  158. }
  159. func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  160. if let item = self.delegate?.thumbnailView?(thumbanView: self, itemForRepresentedObjectAt: indexPath) {
  161. return item
  162. }
  163. return NSCollectionViewItem()
  164. }
  165. }
  166. // MARK: - NSCollectionViewDelegate
  167. extension KMThumbnailView: NSCollectionViewDelegate {
  168. func collectionView(_ collectionView: NSCollectionView, shouldSelectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  169. return indexPaths
  170. }
  171. func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {}
  172. func collectionView(_ collectionView: NSCollectionView, shouldDeselectItemsAt indexPaths: Set<IndexPath>) -> Set<IndexPath> {
  173. return indexPaths
  174. }
  175. func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set<IndexPath>) {}
  176. func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
  177. return true
  178. }
  179. func collectionView(_ collectionView: NSCollectionView, writeItemsAt indexPaths: Set<IndexPath>, to pasteboard: NSPasteboard) -> Bool {
  180. let data: Data = try! NSKeyedArchiver.archivedData(withRootObject: indexPaths, requiringSecureCoding: true)
  181. pasteboard.declareTypes([self.localForDraggedTypes], owner: self)
  182. pasteboard.setData(data, forType: self.localForDraggedTypes)
  183. self.dragedIndexPaths.removeAll()
  184. for indexPath in indexPaths {
  185. self.dragedIndexPaths.append(indexPath)
  186. }
  187. return true
  188. }
  189. func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexes: IndexSet) {}
  190. func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
  191. let pboard = draggingInfo.draggingPasteboard
  192. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  193. return .move
  194. }
  195. return NSDragOperation(rawValue: 0)
  196. }
  197. func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
  198. let pboard = draggingInfo.draggingPasteboard
  199. if (pboard.availableType(from: [self.localForDraggedTypes]) != nil) {
  200. self.delegate?.thumbnailView?(thumbanView: self, didDrag: self.dragedIndexPaths, indexpath: indexPath)
  201. self.dragedIndexPaths.removeAll()
  202. return true
  203. } else if ((pboard.availableType(from: [.fileURL])) != nil) {
  204. var array: [URL] = []
  205. for item: NSPasteboardItem in pboard.pasteboardItems! {
  206. let string: String = item.string(forType: NSPasteboard.PasteboardType.fileURL)!
  207. let url = NSURL(string: string)
  208. array.append(url! as URL)
  209. }
  210. self.delegate?.thumbnailView?(thumbanView: self, didDragAddFiles: array, indexpath: indexPath)
  211. return true
  212. }
  213. return false
  214. }
  215. }
  216. // MARK: - NSCollectionViewDelegateFlowLayout
  217. extension KMThumbnailView: NSCollectionViewDelegateFlowLayout {
  218. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  219. if let size_ = self.delegate?.thumbnailView?(thumbanView: self, sizeForItemAt: indexPath) {
  220. return size_
  221. }
  222. return self.itemSize
  223. }
  224. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  225. if let minimumLineSpacing_ = self.delegate?.thumbnailView?(thumbanView: self, minimumLineSpacingForSectionAt: section) {
  226. return minimumLineSpacing_
  227. }
  228. return self.minimumLineSpacing
  229. }
  230. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
  231. if let minimumInteritemSpacing_ = self.delegate?.thumbnailView?(thumbanView: self, minimumInteritemSpacingForSectionAt: section) {
  232. return minimumInteritemSpacing_
  233. }
  234. return self.minimumInteritemSpacing
  235. }
  236. func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets {
  237. if let inset = self.delegate?.thumbnailView?(thumbanView: self, insetForSectionAt: section) {
  238. return inset
  239. }
  240. return self.sectionInset
  241. }
  242. }