AppDelegate.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import AppKit
  2. @MainActor
  3. final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
  4. private var windowObservers: [NSObjectProtocol] = []
  5. private var shouldCenterMainWindowOnShow = true
  6. private var centeringDeadline: Date?
  7. private weak var trackedMainWindow: NSWindow?
  8. func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
  9. false
  10. }
  11. func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
  12. if !flag {
  13. showMainWindow()
  14. }
  15. return true
  16. }
  17. func applicationDidFinishLaunching(_ notification: Notification) {
  18. installWindowObservers()
  19. configureMainWindows()
  20. beginCenteringMainWindow()
  21. }
  22. func applicationDidBecomeActive(_ notification: Notification) {
  23. configureMainWindows()
  24. showMainWindowIfNeeded()
  25. RedditWebViewStore.shared.handleApplicationDidBecomeActive()
  26. }
  27. func windowShouldClose(_ sender: NSWindow) -> Bool {
  28. if isMainAppWindow(sender) {
  29. trackedMainWindow = sender
  30. shouldCenterMainWindowOnShow = true
  31. centeringDeadline = nil
  32. }
  33. sender.orderOut(nil)
  34. return false
  35. }
  36. func windowWillMiniaturize(_ sender: NSWindow) {
  37. guard isMainAppWindow(sender) else { return }
  38. RedditWebViewStore.shared.suspendMediaPlaybackForWindowMinimize()
  39. }
  40. func windowDidDeminiaturize(_ sender: NSWindow) {
  41. guard isMainAppWindow(sender) else { return }
  42. RedditWebViewStore.shared.resumeMediaPlaybackAfterWindowDeminiaturize()
  43. }
  44. func windowDidMove(_ sender: NSWindow) {
  45. guard isMainAppWindow(sender), isCenteringMainWindow else { return }
  46. AppWindow.centerOnScreen(sender)
  47. }
  48. func configureMainWindows() {
  49. for window in NSApp.windows where isMainAppWindow(window) {
  50. trackedMainWindow = window
  51. window.isReleasedWhenClosed = false
  52. window.isRestorable = false
  53. window.setFrameAutosaveName("")
  54. if window.delegate !== self {
  55. window.delegate = self
  56. }
  57. }
  58. }
  59. func centerMainWindow() {
  60. beginCenteringMainWindow()
  61. }
  62. private var isCenteringMainWindow: Bool {
  63. guard shouldCenterMainWindowOnShow else { return false }
  64. if let centeringDeadline {
  65. return Date() < centeringDeadline
  66. }
  67. return true
  68. }
  69. private func beginCenteringMainWindow() {
  70. shouldCenterMainWindowOnShow = true
  71. centeringDeadline = Date().addingTimeInterval(0.6)
  72. centerMainWindowIfNeeded()
  73. }
  74. private func finishCenteringMainWindow() {
  75. shouldCenterMainWindowOnShow = false
  76. centeringDeadline = nil
  77. }
  78. private func centerMainWindowIfNeeded() {
  79. guard isCenteringMainWindow else { return }
  80. guard let window = NSApp.windows.first(where: isMainAppWindow) else { return }
  81. let centerAction = { [weak self] in
  82. guard let self, self.isCenteringMainWindow else { return }
  83. AppWindow.centerOnScreen(window)
  84. }
  85. centerAction()
  86. DispatchQueue.main.async(execute: centerAction)
  87. DispatchQueue.main.asyncAfter(deadline: .now() + 0.15, execute: centerAction)
  88. DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
  89. centerAction()
  90. self?.finishCenteringMainWindow()
  91. }
  92. }
  93. private func installWindowObservers() {
  94. let center = NotificationCenter.default
  95. windowObservers = [
  96. center.addObserver(
  97. forName: NSWindow.didBecomeKeyNotification,
  98. object: nil,
  99. queue: .main
  100. ) { [weak self] _ in
  101. Task { @MainActor in
  102. self?.configureMainWindows()
  103. }
  104. },
  105. center.addObserver(
  106. forName: NSApplication.didUpdateNotification,
  107. object: nil,
  108. queue: .main
  109. ) { [weak self] _ in
  110. Task { @MainActor in
  111. self?.configureMainWindows()
  112. }
  113. },
  114. ]
  115. }
  116. func showMainWindow() {
  117. configureMainWindows()
  118. guard let window = resolvedMainWindow() else { return }
  119. trackedMainWindow = window
  120. beginCenteringMainWindow()
  121. if window.isMiniaturized {
  122. window.deminiaturize(nil)
  123. }
  124. window.collectionBehavior.insert(.moveToActiveSpace)
  125. window.orderFrontRegardless()
  126. window.makeKeyAndOrderFront(nil)
  127. NSApp.activate(ignoringOtherApps: true)
  128. }
  129. private func showMainWindowIfNeeded() {
  130. guard !NSApp.windows.contains(where: { isMainAppWindow($0) && $0.isVisible && !$0.isMiniaturized }) else {
  131. return
  132. }
  133. showMainWindow()
  134. }
  135. private func resolvedMainWindow() -> NSWindow? {
  136. if let trackedMainWindow, NSApp.windows.contains(trackedMainWindow) {
  137. return trackedMainWindow
  138. }
  139. if let visible = NSApp.windows.first(where: { isMainAppWindow($0) && $0.isVisible }) {
  140. return visible
  141. }
  142. return NSApp.windows.first(where: isMainAppWindow)
  143. }
  144. private func isMainAppWindow(_ window: NSWindow) -> Bool {
  145. if window === trackedMainWindow { return true }
  146. guard !(window is NSPanel), window.level == .normal else { return false }
  147. if window.title == "Sign in to Reddit" { return false }
  148. return window.canBecomeKey || window.canBecomeMain
  149. }
  150. }