// // NSWindow+KMExtension.swift // PDF Reader Pro // // Created by tangchao on 2023/10/30. // import Foundation private var _KMWindowPopOverKey = "KMWindowPopOverKey" private var _KMWindowPopOverSourcesRectKey = "KMWindowPopOverSourcesRectKey" extension NSWindow { var popover: NSPopover? { get { return objc_getAssociatedObject(self, &_KMWindowPopOverKey) as? NSPopover } set { objc_setAssociatedObject(self, &_KMWindowPopOverKey, newValue, .OBJC_ASSOCIATION_RETAIN) } } var sourcesRect: NSRect { get { if let data = objc_getAssociatedObject(self, &_KMWindowPopOverSourcesRectKey) as? NSValue { return data.rectValue } return NSZeroRect } set { objc_setAssociatedObject(self, &_KMWindowPopOverSourcesRectKey, NSValue(rect: newValue), .OBJC_ASSOCIATION_RETAIN) } } open override func mouseMoved(with event: NSEvent) { super.mouseMoved(with: event) let point = event.locationInWindow var need = true if let data = event.window?.isKind(of: NSClassFromString("_NSPopoverWindow")!) { // need = !data if data { need = false } else { need = true } } else { need = true } if (need) { if (self.sourcesRect.contains(point) == false) { if self.popover != nil { NotificationCenter.default.post(name: Notification.Name("KMPopOverClosedByWindowNotification"), object: self.popover!) self.popover?.close() self.popover = nil self.sourcesRect = NSRect.null } } } } } extension NSWindow { static func currentWindow() -> NSWindow { var window = NSApp.mainWindow if NSApp.mainWindow?.sheets.first != nil { window = NSApp.mainWindow?.sheets.first } if window == nil { window = NSApplication.shared.windows.first } return window ?? NSWindow() } } @objc extension NSWindow { public static let willEnterInteractionModeNotification: NSNotification.Name = .init("KMWillEnterInteractionModeNotificationName") public static let didEnterInteractionModeNotification: NSNotification.Name = .init("KMDidEnterInteractionModeNotificationName") public static let willShowFullScreenNotification: NSNotification.Name = .init("KMWillShowFullScreenNotificationName") public static let didShowFullScreenNotification: NSNotification.Name = .init("KMDidShowFullScreenNotificationName") public static let didAddContentViewNotification: NSNotification.Name = .init("KMDidAddContentViewNotificationName") struct UserInfo { public static let interactionModeKey = "KMInteractionModeKey" public static let isSwitchingFullScreenKey = "KMIsSwitchingFullScreenKey" public static let wantsPresentationKey = "KMWantsPresentationKey" } private static var interactionModeKey_ = "KMInteractionModeKey" var interactionMode: KMInteractionMode { get { if let win = self.interactionParent { // 从 interactionParent 获取数据 return win.interactionMode } if let value = (objc_getAssociatedObject(self, &Self.interactionModeKey_) as? Int) { return KMInteractionMode(rawValue: value) ?? .normal } return .normal } set { objc_setAssociatedObject(self, &Self.interactionModeKey_, newValue.rawValue, .OBJC_ASSOCIATION_ASSIGN) // interactionParent 设置数据 self.interactionParent?.interactionMode = newValue } } private static var isSwitchingFullScreenKey_ = "KMIsSwitchingFullScreenKey" var isSwitchingFullScreen: Bool { get { if let win = self.interactionParent { // 从 interactionParent 获取数据 return win.isSwitchingFullScreen } return (objc_getAssociatedObject(self, &Self.isSwitchingFullScreenKey_) as? Bool) ?? false } set { objc_setAssociatedObject(self, &Self.isSwitchingFullScreenKey_, newValue, .OBJC_ASSOCIATION_ASSIGN) // interactionParent 设置数据 self.interactionParent?.isSwitchingFullScreen = newValue } } private static var wantsPresentationKey_ = "KMWantsPresentationKey" var wantsPresentation: Bool { get { if let win = self.interactionParent { // 从 interactionParent 获取数据 return win.wantsPresentation } return (objc_getAssociatedObject(self, &Self.wantsPresentationKey_) as? Bool) ?? false } set { objc_setAssociatedObject(self, &Self.wantsPresentationKey_, newValue, .OBJC_ASSOCIATION_ASSIGN) // interactionParent 设置数据 self.interactionParent?.wantsPresentation = newValue } } // sheetParent private static var interactionParentKey_ = "KMInteractionParentKey" var interactionParent: NSWindow? { get { return (objc_getAssociatedObject(self, &Self.interactionParentKey_) as? NSWindow) } set { objc_setAssociatedObject(self, &Self.interactionParentKey_, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) // 更新数据 self.interactionMode = self.interactionParent?.interactionMode ?? .normal self.isSwitchingFullScreen = self.interactionParent?.isSwitchingFullScreen ?? false self.wantsPresentation = self.interactionParent?.wantsPresentation ?? false } } func canEnterPresentation() -> Bool { if self.interactionMode == .presentation { // 幻灯片模式下 return false } if self.isSwitchingFullScreen { // 正在切换中 return false } let cnt = self.tabbedWindows?.count ?? 0 return cnt < 2 } func canExitPresentation() -> Bool { if self.isSwitchingFullScreen { // 正在切换中 return false } if self.interactionMode == .presentation { // 幻灯片模式下 return true } return false } func enterPresentation(provider: KMInteractionProviderProtocol? = nil) { if self.canEnterPresentation() == false { NSSound.beep() return } let wasInteractionMode = self.interactionMode if wasInteractionMode == .fullScreen { // 是全屏模式,直接退出全屏模式 self.wantsPresentation = true self.toggleFullScreen(nil) return } let backgroundColor = NSColor.black let level = UserDefaults.standard.bool(forKey: "SKUseNormalLevelForPresentationKey") ? NSWindow.Level.normal : NSWindow.Level.popUpMenu NotificationCenter.default.post(name: Self.willEnterInteractionModeNotification, object: self, userInfo: [Self.UserInfo.interactionModeKey : KMInteractionMode.presentation]) // 设置切换中标识 self.isSwitchingFullScreen = true // 设置模式标识 self.interactionMode = .presentation if wasInteractionMode == .legacyFullScreen { } else { NotificationCenter.default.post(name: Self.willShowFullScreenNotification, object: self, userInfo: ["fullScreenWindow" : ""]) let fsWindow = self.fadeInFullScreenWindow(with: backgroundColor, level: level.rawValue) NotificationCenter.default.post(name: Self.didShowFullScreenNotification, object: self, userInfo: ["fullScreenWindow" : ""]) let view = provider?.providerContentView?(fullScreenWindow: fsWindow, inset: 0) self.fadeInFullScreenView(fullScreenWindow: fsWindow, with: view, inset: 0) } // 恢复切换全屏标识 self.isSwitchingFullScreen = false NotificationCenter.default.post(name: Self.didEnterInteractionModeNotification, object: self, userInfo: ["fullScreenWindow" : ""]) } func exitFullscreen() { } // MARK: - fadeIn & fadeOut [淡入、淡出] func fadeInFullScreenWindow(with backgroundColor: NSColor, level: Int) -> KMFullScreenWindow { let fullScreenWindow = KMFullScreenWindow(screen: (self.screen ?? NSScreen.main)!, bgColor: backgroundColor, level: NSWindow.Level.popUpMenu.rawValue, isMain: true) fullScreenWindow.interactionParent = self self.delegate = nil fullScreenWindow.fadeInBlocking() self.windowController?.window = fullScreenWindow fullScreenWindow.makeKey() let sel = NSSelectorFromString("setAnimationBehavior:") if self.responds(to: sel){ self.animationBehavior = .none } self.orderOut(nil) if self.responds(to: sel) { self.animationBehavior = .default } fullScreenWindow.level = NSWindow.Level(rawValue: level) fullScreenWindow.orderFront(nil) return fullScreenWindow } func fadeInFullScreenView(fullScreenWindow: KMFullScreenWindow, with view: NSView?, inset: CGFloat) { let fadeWindow = KMFullScreenWindow(screen: fullScreenWindow.screen!, bgColor: fullScreenWindow.backgroundColor, level: fullScreenWindow.level.rawValue, isMain: false) fadeWindow.order(.above, relativeTo: fullScreenWindow.windowNumber) if let _ = view { NotificationCenter.default.post(name: Self.didAddContentViewNotification, object: self) } fullScreenWindow.makeFirstResponder(view) fullScreenWindow.recalculateKeyViewLoop() fullScreenWindow.delegate = (fullScreenWindow.windowController as? any NSWindowDelegate) fullScreenWindow.display() fadeWindow.fadeOut() } }