NSWindow+KMExtension.swift 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. //
  2. // NSWindow+KMExtension.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/10/30.
  6. //
  7. import Foundation
  8. private var _KMWindowPopOverKey = "KMWindowPopOverKey"
  9. private var _KMWindowPopOverSourcesRectKey = "KMWindowPopOverSourcesRectKey"
  10. extension NSWindow {
  11. var popover: NSPopover? {
  12. get {
  13. return objc_getAssociatedObject(self, &_KMWindowPopOverKey) as? NSPopover
  14. }
  15. set {
  16. objc_setAssociatedObject(self, &_KMWindowPopOverKey, newValue, .OBJC_ASSOCIATION_RETAIN)
  17. }
  18. }
  19. var sourcesRect: NSRect {
  20. get {
  21. if let data = objc_getAssociatedObject(self, &_KMWindowPopOverSourcesRectKey) as? NSValue {
  22. return data.rectValue
  23. }
  24. return NSZeroRect
  25. }
  26. set {
  27. objc_setAssociatedObject(self, &_KMWindowPopOverSourcesRectKey, NSValue(rect: newValue), .OBJC_ASSOCIATION_RETAIN)
  28. }
  29. }
  30. open override func mouseMoved(with event: NSEvent) {
  31. super.mouseMoved(with: event)
  32. let point = event.locationInWindow
  33. var need = true
  34. if let data = event.window?.isKind(of: NSClassFromString("_NSPopoverWindow")!) {
  35. // need = !data
  36. if data {
  37. need = false
  38. } else {
  39. need = true
  40. }
  41. } else {
  42. need = true
  43. }
  44. if (need) {
  45. if (self.sourcesRect.contains(point) == false) {
  46. if self.popover != nil {
  47. NotificationCenter.default.post(name: Notification.Name("KMPopOverClosedByWindowNotification"), object: self.popover!)
  48. self.popover?.close()
  49. self.popover = nil
  50. self.sourcesRect = NSRect.null
  51. }
  52. }
  53. }
  54. }
  55. }
  56. extension NSWindow {
  57. static func currentWindow() -> NSWindow {
  58. var window = NSApp.mainWindow
  59. if NSApp.mainWindow?.sheets.first != nil {
  60. window = NSApp.mainWindow?.sheets.first
  61. }
  62. if window == nil {
  63. window = NSApplication.shared.windows.first
  64. }
  65. return window ?? NSWindow()
  66. }
  67. }
  68. @objc extension NSWindow {
  69. public static let willEnterInteractionModeNotification: NSNotification.Name = .init("KMWillEnterInteractionModeNotificationName")
  70. public static let didEnterInteractionModeNotification: NSNotification.Name = .init("KMDidEnterInteractionModeNotificationName")
  71. public static let willShowFullScreenNotification: NSNotification.Name = .init("KMWillShowFullScreenNotificationName")
  72. public static let didShowFullScreenNotification: NSNotification.Name = .init("KMDidShowFullScreenNotificationName")
  73. public static let didAddContentViewNotification: NSNotification.Name = .init("KMDidAddContentViewNotificationName")
  74. struct UserInfo {
  75. public static let interactionModeKey = "KMInteractionModeKey"
  76. public static let isSwitchingFullScreenKey = "KMIsSwitchingFullScreenKey"
  77. public static let wantsPresentationKey = "KMWantsPresentationKey"
  78. }
  79. private static var interactionModeKey_ = "KMInteractionModeKey"
  80. var interactionMode: KMInteractionMode {
  81. get {
  82. if let win = self.interactionParent { // 从 interactionParent 获取数据
  83. return win.interactionMode
  84. }
  85. if let value = (objc_getAssociatedObject(self, &Self.interactionModeKey_) as? Int) {
  86. return KMInteractionMode(rawValue: value) ?? .normal
  87. }
  88. return .normal
  89. }
  90. set {
  91. objc_setAssociatedObject(self, &Self.interactionModeKey_, newValue.rawValue, .OBJC_ASSOCIATION_ASSIGN)
  92. // interactionParent 设置数据
  93. self.interactionParent?.interactionMode = newValue
  94. }
  95. }
  96. private static var isSwitchingFullScreenKey_ = "KMIsSwitchingFullScreenKey"
  97. var isSwitchingFullScreen: Bool {
  98. get {
  99. if let win = self.interactionParent { // 从 interactionParent 获取数据
  100. return win.isSwitchingFullScreen
  101. }
  102. return (objc_getAssociatedObject(self, &Self.isSwitchingFullScreenKey_) as? Bool) ?? false
  103. }
  104. set {
  105. objc_setAssociatedObject(self, &Self.isSwitchingFullScreenKey_, newValue, .OBJC_ASSOCIATION_ASSIGN)
  106. // interactionParent 设置数据
  107. self.interactionParent?.isSwitchingFullScreen = newValue
  108. }
  109. }
  110. private static var wantsPresentationKey_ = "KMWantsPresentationKey"
  111. var wantsPresentation: Bool {
  112. get {
  113. if let win = self.interactionParent { // 从 interactionParent 获取数据
  114. return win.wantsPresentation
  115. }
  116. return (objc_getAssociatedObject(self, &Self.wantsPresentationKey_) as? Bool) ?? false
  117. }
  118. set {
  119. objc_setAssociatedObject(self, &Self.wantsPresentationKey_, newValue, .OBJC_ASSOCIATION_ASSIGN)
  120. // interactionParent 设置数据
  121. self.interactionParent?.wantsPresentation = newValue
  122. }
  123. }
  124. // sheetParent
  125. private static var interactionParentKey_ = "KMInteractionParentKey"
  126. var interactionParent: NSWindow? {
  127. get {
  128. return (objc_getAssociatedObject(self, &Self.interactionParentKey_) as? NSWindow)
  129. }
  130. set {
  131. objc_setAssociatedObject(self, &Self.interactionParentKey_, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  132. // 更新数据
  133. self.interactionMode = self.interactionParent?.interactionMode ?? .normal
  134. self.isSwitchingFullScreen = self.interactionParent?.isSwitchingFullScreen ?? false
  135. self.wantsPresentation = self.interactionParent?.wantsPresentation ?? false
  136. }
  137. }
  138. func canEnterPresentation() -> Bool {
  139. if self.interactionMode == .presentation { // 幻灯片模式下
  140. return false
  141. }
  142. if self.isSwitchingFullScreen { // 正在切换中
  143. return false
  144. }
  145. let cnt = self.tabbedWindows?.count ?? 0
  146. return cnt < 2
  147. }
  148. func canExitPresentation() -> Bool {
  149. if self.isSwitchingFullScreen { // 正在切换中
  150. return false
  151. }
  152. if self.interactionMode == .presentation { // 幻灯片模式下
  153. return true
  154. }
  155. return false
  156. }
  157. func enterPresentation(provider: KMInteractionProviderProtocol? = nil) {
  158. if self.canEnterPresentation() == false {
  159. NSSound.beep()
  160. return
  161. }
  162. let wasInteractionMode = self.interactionMode
  163. if wasInteractionMode == .fullScreen { // 是全屏模式,直接退出全屏模式
  164. self.wantsPresentation = true
  165. self.toggleFullScreen(nil)
  166. return
  167. }
  168. let backgroundColor = NSColor.black
  169. let level = UserDefaults.standard.bool(forKey: "SKUseNormalLevelForPresentationKey") ? NSWindow.Level.normal : NSWindow.Level.popUpMenu
  170. NotificationCenter.default.post(name: Self.willEnterInteractionModeNotification, object: self, userInfo: [Self.UserInfo.interactionModeKey : KMInteractionMode.presentation])
  171. // 设置切换中标识
  172. self.isSwitchingFullScreen = true
  173. // 设置模式标识
  174. self.interactionMode = .presentation
  175. if wasInteractionMode == .legacyFullScreen {
  176. } else {
  177. NotificationCenter.default.post(name: Self.willShowFullScreenNotification, object: self, userInfo: ["fullScreenWindow" : ""])
  178. let fsWindow = self.fadeInFullScreenWindow(with: backgroundColor, level: level.rawValue)
  179. NotificationCenter.default.post(name: Self.didShowFullScreenNotification, object: self, userInfo: ["fullScreenWindow" : ""])
  180. let view = provider?.providerContentView?(fullScreenWindow: fsWindow, inset: 0)
  181. self.fadeInFullScreenView(fullScreenWindow: fsWindow, with: view, inset: 0)
  182. }
  183. // 恢复切换全屏标识
  184. self.isSwitchingFullScreen = false
  185. NotificationCenter.default.post(name: Self.didEnterInteractionModeNotification, object: self, userInfo: ["fullScreenWindow" : ""])
  186. }
  187. func exitFullscreen() {
  188. }
  189. // MARK: - fadeIn & fadeOut [淡入、淡出]
  190. func fadeInFullScreenWindow(with backgroundColor: NSColor, level: Int) -> KMFullScreenWindow {
  191. let fullScreenWindow = KMFullScreenWindow(screen: (self.screen ?? NSScreen.main)!, bgColor: backgroundColor, level: NSWindow.Level.popUpMenu.rawValue, isMain: true)
  192. fullScreenWindow.interactionParent = self
  193. self.delegate = nil
  194. fullScreenWindow.fadeInBlocking()
  195. self.windowController?.window = fullScreenWindow
  196. fullScreenWindow.makeKey()
  197. let sel = NSSelectorFromString("setAnimationBehavior:")
  198. if self.responds(to: sel){
  199. self.animationBehavior = .none
  200. }
  201. self.orderOut(nil)
  202. if self.responds(to: sel) {
  203. self.animationBehavior = .default
  204. }
  205. fullScreenWindow.level = NSWindow.Level(rawValue: level)
  206. fullScreenWindow.orderFront(nil)
  207. return fullScreenWindow
  208. }
  209. func fadeInFullScreenView(fullScreenWindow: KMFullScreenWindow, with view: NSView?, inset: CGFloat) {
  210. let fadeWindow = KMFullScreenWindow(screen: fullScreenWindow.screen!, bgColor: fullScreenWindow.backgroundColor, level: fullScreenWindow.level.rawValue, isMain: false)
  211. fadeWindow.order(.above, relativeTo: fullScreenWindow.windowNumber)
  212. if let _ = view {
  213. NotificationCenter.default.post(name: Self.didAddContentViewNotification, object: self)
  214. }
  215. fullScreenWindow.makeFirstResponder(view)
  216. fullScreenWindow.recalculateKeyViewLoop()
  217. fullScreenWindow.delegate = (fullScreenWindow.windowController as? any NSWindowDelegate)
  218. fullScreenWindow.display()
  219. fadeWindow.fadeOut()
  220. }
  221. }