WidgetsWindowManager.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import AppKit
  2. import SwiftUI
  3. final class WidgetsWindowManager {
  4. static let shared = WidgetsWindowManager()
  5. private var windowController: NSWindowController?
  6. private let defaultWindowSize = CGSize(width: 1120, height: 760)
  7. private init() {}
  8. func open(apps: [LauncherApp], selectedAppID: UUID?) {
  9. if let controller = windowController, controller.window != nil {
  10. if let hosting = controller.contentViewController as? NSHostingController<WidgetsWindowRootView> {
  11. hosting.rootView = WidgetsWindowRootView(apps: apps, selectedAppID: selectedAppID)
  12. }
  13. if let window = controller.window {
  14. resizeAndCenter(window: window)
  15. }
  16. controller.showWindow(nil)
  17. controller.window?.makeKeyAndOrderFront(nil)
  18. NSApp.activate(ignoringOtherApps: true)
  19. return
  20. }
  21. let root = WidgetsWindowRootView(apps: apps, selectedAppID: selectedAppID)
  22. let hosting = NSHostingController(rootView: root)
  23. let window = NSWindow(
  24. contentRect: NSRect(x: 0, y: 0, width: 980, height: 680),
  25. styleMask: [.titled, .closable, .miniaturizable],
  26. backing: .buffered,
  27. defer: false
  28. )
  29. window.title = "Widgets"
  30. window.contentViewController = hosting
  31. window.isReleasedWhenClosed = false
  32. window.minSize = NSSize(width: 900, height: 620)
  33. window.standardWindowButton(.zoomButton)?.isHidden = true
  34. resizeAndCenter(window: window)
  35. let controller = NSWindowController(window: window)
  36. self.windowController = controller
  37. controller.showWindow(nil)
  38. window.makeKeyAndOrderFront(nil)
  39. NSApp.activate(ignoringOtherApps: true)
  40. }
  41. private func resizeAndCenter(window: NSWindow) {
  42. let targetSize = NSSize(width: defaultWindowSize.width, height: defaultWindowSize.height)
  43. let screenFrame = (window.screen ?? NSScreen.main)?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
  44. let clampedWidth = min(max(targetSize.width, window.minSize.width), screenFrame.width)
  45. let clampedHeight = min(max(targetSize.height, window.minSize.height), screenFrame.height)
  46. let x = screenFrame.origin.x + (screenFrame.width - clampedWidth) / 2
  47. let y = screenFrame.origin.y + (screenFrame.height - clampedHeight) / 2
  48. let frame = NSRect(x: x, y: y, width: clampedWidth, height: clampedHeight)
  49. window.setFrame(frame, display: true)
  50. }
  51. }
  52. private struct WidgetsWindowRootView: View {
  53. let apps: [LauncherApp]
  54. let selectedAppID: UUID?
  55. @AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""
  56. /// Survives `NSHostingController.rootView` replacement so the sidebar stays selected like macOS widget settings.
  57. @AppStorage("widgetsLibrarySidebarSelectedAppID") private var persistedSidebarAppID: String = ""
  58. @State private var selection: UUID?
  59. @Environment(\.colorScheme) private var colorScheme
  60. var body: some View {
  61. ZStack {
  62. VisualEffectBlur(
  63. material: colorScheme == .dark ? .hudWindow : .underWindowBackground,
  64. blendingMode: .behindWindow
  65. )
  66. .ignoresSafeArea()
  67. (colorScheme == .dark ? Color.black.opacity(0.22) : Color.white.opacity(0.28))
  68. .ignoresSafeArea()
  69. WidgetsRootView(
  70. apps: apps,
  71. selectedAppID: $selection,
  72. widgetLibraryIDsData: $widgetAppIDsData
  73. )
  74. .padding(.top, 12)
  75. }
  76. .onAppear {
  77. restoreSelectionFromStorage()
  78. }
  79. .onChange(of: selectedAppID) { newValue in
  80. if let newValue {
  81. selection = newValue
  82. persistedSidebarAppID = newValue.uuidString
  83. }
  84. }
  85. .onChange(of: selection) { newValue in
  86. if let newValue {
  87. persistedSidebarAppID = newValue.uuidString
  88. }
  89. }
  90. }
  91. private func restoreSelectionFromStorage() {
  92. if let selectedAppID {
  93. selection = selectedAppID
  94. persistedSidebarAppID = selectedAppID.uuidString
  95. return
  96. }
  97. if let stored = UUID(uuidString: persistedSidebarAppID),
  98. apps.contains(where: { $0.id == stored }) {
  99. selection = stored
  100. return
  101. }
  102. if let first = apps.first {
  103. selection = first.id
  104. persistedSidebarAppID = first.id.uuidString
  105. }
  106. }
  107. }