ComponentGroup.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. //
  2. // ComponentGroup.swift
  3. // KMComponentLibrary
  4. //
  5. // Created by Niehaoyu on 2024/9/3.
  6. //
  7. import Cocoa
  8. import AppKit
  9. @objc public protocol ComponentGroupDelegate: AnyObject {
  10. @objc optional func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?)
  11. @objc optional func componentGroupDidDismiss(group: ComponentGroup?)
  12. @objc optional func componentGroupMouseEvent(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty, _ mouseState: ComponentMouseState, _ event: NSEvent)
  13. }
  14. public class ComponentGroup: NSView, NibLoadable {
  15. @IBOutlet var contendBox: NSBox!
  16. @IBOutlet var scrollView: NSScrollView!
  17. @IBOutlet var collectionView: NSCollectionView!
  18. // MARK: Private Property
  19. private var localMonitor: Any?
  20. private var globalMonitor: Any?
  21. private var _shadowInfo : ComponentShadowInfo?
  22. public var menuItemArr: [ComponentMenuitemProperty] = []
  23. public var clickedAutoHide = true //点击Item后,是否自动隐藏
  24. var subGroupView: ComponentGroup!
  25. var choosedIndex: Int = -1
  26. weak open var groupDelegate: ComponentGroupDelegate?
  27. var windowVC = ComponentGroupWindowController.init(windowNibName: "ComponentGroupWindowController")
  28. var subWindowVC = ComponentSubGroupWindowController.init(windowNibName: "ComponentSubGroupWindowController")
  29. deinit {
  30. NotificationCenter.default.removeObserver(self)
  31. }
  32. public override func draw(_ dirtyRect: NSRect) {
  33. super.draw(dirtyRect)
  34. // Drawing code here.
  35. if shadowInfo != nil {
  36. let shadow = NSShadow()
  37. shadow.shadowColor = shadowInfo?.shadowColor
  38. shadow.shadowOffset = shadowInfo?.shadowOffset ?? CGSizeZero
  39. shadow.shadowBlurRadius = shadowInfo?.shadowBlurRadius ?? 0
  40. shadow.set()
  41. let cornerRadius: CGFloat = shadowInfo?.cornerRadius ?? 0
  42. let roundedRect = NSBezierPath(roundedRect: bounds, xRadius: cornerRadius, yRadius: cornerRadius)
  43. if let color = shadowInfo?.color {
  44. color.setFill()
  45. roundedRect.fill()
  46. }
  47. }
  48. }
  49. public override func removeFromSuperview() {
  50. super.removeFromSuperview()
  51. clearMonitor()
  52. }
  53. public required init?(coder decoder: NSCoder) {
  54. super.init(coder: decoder)
  55. }
  56. override init(frame frameRect: NSRect) {
  57. super.init(frame: frameRect)
  58. }
  59. public override func awakeFromNib() {
  60. super.awakeFromNib()
  61. shadowInfo = ComponentLibrary.shared.configShadowInfoWithKey("")
  62. if let value = ComponentLibrary.shared.getComponentValueFromKey("colorBg/popup") {
  63. let currentValue = value as! [String : Any]
  64. contendBox.fillColor = ComponentLibrary.shared.getColor(rgbaDict: currentValue)
  65. } else {
  66. contendBox.fillColor = NSColor.clear
  67. }
  68. contendBox.borderColor = NSColor.clear
  69. contendBox.borderWidth = 0
  70. scrollView.scrollerStyle = .overlay
  71. collectionView.delegate = self
  72. collectionView.dataSource = self
  73. collectionView.backgroundColors = [NSColor.clear]
  74. collectionView.wantsLayer = true
  75. collectionView.layer?.backgroundColor = NSColor.clear.cgColor
  76. collectionView.register(ComponentGroupItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "componentGroupItemID"))
  77. }
  78. //MARK: - Setter and Getter
  79. public var shadowInfo : ComponentShadowInfo? {
  80. get {
  81. return _shadowInfo
  82. }
  83. set {
  84. _shadowInfo = newValue
  85. display()
  86. }
  87. }
  88. //MARK: - Public Method
  89. public func updateGroupInfo(_ arr: [ComponentMenuitemProperty]) {
  90. menuItemArr.removeAll()
  91. for item in arr {
  92. menuItemArr.append(item)
  93. }
  94. reloadData()
  95. }
  96. public func reloadData() {
  97. collectionView.reloadData()
  98. if choosedIndex > 0 {
  99. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15) {
  100. let indexpath = IndexPath(item: self.choosedIndex, section: 0)
  101. var set = Set<IndexPath>()
  102. set.insert(indexpath)
  103. self.collectionView.scrollToItems(at: set, scrollPosition: .centeredVertically)
  104. }
  105. }
  106. }
  107. //MARK: - Show
  108. //Point是相对于当前WIndow的point,
  109. public func showWithPoint(_ point: CGPoint, relativeTo positioningView: NSView?) {
  110. var screenRect = NSScreen.main!.frame
  111. if let keyWindow = NSApplication.shared.keyWindow, let screen = keyWindow.screen {
  112. screenRect = screen.visibleFrame
  113. }
  114. if self.windowVC.window?.isVisible == true {
  115. self.windowVC.close()
  116. }
  117. let windowVC = ComponentGroupWindowController.init(windowNibName: "ComponentGroupWindowController")
  118. windowVC.window?.setFrame(screenRect, display: true)
  119. if let window = positioningView?.window {
  120. windowVC.window?.level = window.level + 1
  121. }
  122. windowVC.window?.backgroundColor = NSColor.clear
  123. windowVC.showWindow(nil)
  124. self.windowVC = windowVC
  125. var rect = self.frame
  126. if let subView = positioningView {
  127. let subviewRect = subView.bounds // NSView的坐标
  128. let subviewWindow = subView.window
  129. let subviewWindowRect = subView.convert(subviewRect, to: nil) // 转换到窗口坐标
  130. let subviewscreenRect = subviewWindow?.convertToScreen(subviewWindowRect) ?? CGRectZero // 转换到屏幕坐标 ?
  131. rect.origin.x = subviewscreenRect.origin.x - screenRect.origin.x
  132. rect.origin.y = subviewscreenRect.origin.y - screenRect.origin.y
  133. rect.origin.y -= rect.size.height
  134. } else {
  135. var editPoint = point
  136. if let keyWindow = NSApplication.shared.keyWindow {
  137. editPoint.x += keyWindow.frame.origin.x - screenRect.origin.x
  138. editPoint.y += keyWindow.frame.origin.y - screenRect.origin.y
  139. }
  140. rect.origin.x = editPoint.x
  141. rect.origin.y = editPoint.y
  142. }
  143. var viewWidth: CGFloat = CGRectGetWidth(self.frame)
  144. for item in menuItemArr {
  145. ComponentLibrary.shared.configMenuItemComponent(properties: item)
  146. viewWidth = max(viewWidth, item.propertyInfo.viewWidth)
  147. }
  148. rect.size.width = viewWidth
  149. if(rect.origin.y < 0) {
  150. rect.origin.y = 0
  151. }
  152. self.frame = rect
  153. if let window = windowVC.window {
  154. window.contentView?.addSubview(self)
  155. }
  156. clearMonitor()
  157. addMonitor()
  158. }
  159. public func removeGroupView() {
  160. clearMonitor()
  161. if let tempWindow = self.window {
  162. tempWindow.orderOut(nil)
  163. tempWindow.close()
  164. }
  165. if let window = windowVC.window {
  166. window.close()
  167. }
  168. removeFromSuperview()
  169. }
  170. //MARK: - SubGroupView
  171. func removeSubGroupView() {
  172. if subGroupView != nil {
  173. subGroupView.removeGroupView()
  174. subGroupView = nil
  175. }
  176. if let window = subGroupView?.window {
  177. window.close()
  178. }
  179. }
  180. func showSubGroupWithPoint(_ point: CGPoint) {
  181. var screenRect = NSScreen.main!.frame
  182. if let keyWindow = NSApplication.shared.keyWindow, let screen = keyWindow.screen {
  183. screenRect = screen.visibleFrame
  184. }
  185. if self.subWindowVC.window?.isVisible == true {
  186. self.subWindowVC.close()
  187. }
  188. let windowVC = ComponentSubGroupWindowController.init(windowNibName: "ComponentSubGroupWindowController")
  189. windowVC.window?.setFrame(screenRect, display: true)
  190. windowVC.window?.level = .popUpMenu
  191. windowVC.window?.backgroundColor = NSColor.clear
  192. windowVC.showWindow(nil)
  193. self.subWindowVC = windowVC
  194. var rect = self.frame
  195. rect.origin.x = point.x
  196. rect.origin.y = point.y
  197. var viewWidth: CGFloat = CGRectGetWidth(self.frame)
  198. for item in menuItemArr {
  199. ComponentLibrary.shared.configMenuItemComponent(properties: item)
  200. viewWidth = max(viewWidth, item.propertyInfo.viewWidth)
  201. }
  202. rect.size.width = viewWidth
  203. if(rect.origin.y < 0) {
  204. rect.origin.y = 0
  205. }
  206. self.frame = rect
  207. if let window = windowVC.window {
  208. window.contentView?.addSubview(self)
  209. }
  210. clearMonitor()
  211. addMonitor()
  212. }
  213. func showSubGroupView(itemView: ComponentGroupItem) {
  214. if subGroupView == nil {
  215. subGroupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  216. }
  217. var viewHeight: CGFloat = 8
  218. for property in itemView.properties.subPropertys {
  219. if property.type == .divider {
  220. viewHeight += 8
  221. } else if property.type == .normal {
  222. viewHeight += 36
  223. } else if property.type == .header {
  224. viewHeight += 36
  225. }
  226. }
  227. subGroupView.groupDelegate = self.groupDelegate
  228. subGroupView?.frame = CGRectMake(0, 0, 160, viewHeight)
  229. subGroupView.updateGroupInfo(itemView.properties.subPropertys)
  230. var point = itemView.contendBox.viewFrameInScreen().origin
  231. point.x += CGRectGetWidth(itemView.contendBox.frame)
  232. point.y -= viewHeight
  233. point.y += CGRectGetHeight(itemView.contendBox.frame)
  234. guard let _ = itemView.view.window?.contentView else {
  235. return
  236. }
  237. subGroupView.showSubGroupWithPoint(point)
  238. }
  239. //MARK: - Private Method
  240. func addMonitor() {
  241. if globalMonitor == nil {
  242. globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown], handler: { (event) in
  243. if event.type == .leftMouseDown {
  244. self.leftMouseDownAction()
  245. }
  246. })
  247. }
  248. if localMonitor == nil {
  249. localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .scrollWheel, .rightMouseDown, .otherMouseDown], handler: { (event) in
  250. if event.type == .leftMouseDown {
  251. let point = self.convert(event.locationInWindow, from: nil)
  252. if CGRectContainsPoint(self.bounds, point) {
  253. } else {
  254. self.leftMouseDownAction()
  255. }
  256. } else if event.type == .scrollWheel {
  257. let point = self.convert(event.locationInWindow, from: nil)
  258. var subPoint = CGPointZero
  259. if let subview = self.subGroupView {
  260. subPoint = subview.convert(event.locationInWindow, from: nil)
  261. }
  262. if CGRectContainsPoint(self.bounds, point) {
  263. } else if let subView = self.subGroupView, CGRectContainsPoint(subView.bounds, subPoint) {
  264. } else {
  265. }
  266. } else if event.type == .rightMouseDown {
  267. let point = self.convert(event.locationInWindow, from: nil)
  268. if CGRectContainsPoint(self.bounds, point) {
  269. } else {
  270. self.leftMouseDownAction()
  271. }
  272. } else if event.type == .otherMouseDown {
  273. self.leftMouseDownAction()
  274. }
  275. return event
  276. })
  277. }
  278. }
  279. func clearMonitor() {
  280. if localMonitor != nil {
  281. NSEvent.removeMonitor(self.localMonitor as Any)
  282. localMonitor = nil
  283. }
  284. if globalMonitor != nil {
  285. NSEvent.removeMonitor(self.globalMonitor as Any)
  286. globalMonitor = nil
  287. }
  288. }
  289. @objc func leftMouseDownAction() {
  290. groupDelegate?.componentGroupDidDismiss?(group: self)
  291. removeGroupView()
  292. removeSubGroupView()
  293. }
  294. }
  295. //MARK: - NSCollectionViewDelegate
  296. extension ComponentGroup: NSCollectionViewDelegate {
  297. }
  298. extension ComponentGroup: NSCollectionViewDataSource {
  299. public func numberOfSections(in collectionView: NSCollectionView) -> Int {
  300. return 1
  301. }
  302. public func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
  303. return menuItemArr.count
  304. }
  305. public func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
  306. let itemView = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "componentGroupItemID"), for: indexPath) as! ComponentGroupItem
  307. if indexPath.item < menuItemArr.count {
  308. let properties = menuItemArr[indexPath.item]
  309. itemView.setUpWithProperties(properties)
  310. }
  311. itemView.clickHandle = {[weak self] view in
  312. DispatchQueue.main.async {
  313. let property = view.properties
  314. if self?.clickedAutoHide == true {
  315. self?.groupDelegate?.componentGroupDidDismiss?(group: self)
  316. self?.removeGroupView()
  317. }
  318. self?.groupDelegate?.componentGroupDidSelect?(group: self, menuItemProperty: property)
  319. }
  320. }
  321. itemView.mouseHandle = {[weak self] view, mouseState, event in
  322. guard let weakSelf = self else { return }
  323. if view.properties.subPropertys.isEmpty == false {
  324. if mouseState == .enter {
  325. self?.showSubGroupView(itemView: view)
  326. } else if mouseState == .move {
  327. } else if mouseState == .exit {
  328. }
  329. } else {
  330. self?.removeSubGroupView()
  331. }
  332. self?.groupDelegate?.componentGroupMouseEvent?(group: self, menuItemProperty: view.properties, mouseState, event)
  333. }
  334. return itemView
  335. }
  336. }
  337. extension ComponentGroup: NSCollectionViewDelegateFlowLayout {
  338. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
  339. var viewHeight: CGFloat = 36
  340. if indexPath.item < menuItemArr.count {
  341. let properties = menuItemArr[indexPath.item]
  342. if properties.type == .divider {
  343. viewHeight = 8
  344. }
  345. }
  346. return NSSize(width: NSWidth(self.collectionView.frame), height: viewHeight)
  347. }
  348. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
  349. return NSSize(width: 0, height: 0)
  350. }
  351. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForFooterInSection section: Int) -> NSSize {
  352. return NSSize(width: 0, height: 0)
  353. }
  354. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
  355. return 0
  356. }
  357. public func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, insetForSectionAt section: Int) -> NSEdgeInsets {
  358. return NSEdgeInsetsMake(0, 0, 0, 0)
  359. }
  360. public func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
  361. }
  362. }