WindowGroup.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import Foundation
  2. /// A scene that presents a group of identically structured windows.
  3. public struct WindowGroup<Content: View>: WindowingScene {
  4. public typealias Node = WindowGroupNode<Content>
  5. /// The title of the window (shown in the title bar on most OSes).
  6. var title: String
  7. /// The window's content.
  8. var content: () -> Content
  9. /// The window's ID.
  10. ///
  11. /// This should never change after creation.
  12. let id: String?
  13. /// Creates a window group optionally specifying a title and an ID. Window title
  14. /// defaults to `ProcessInfo.processInfo.processName`.
  15. public init(
  16. _ title: String? = nil,
  17. id: String? = nil,
  18. @ViewBuilder _ content: @escaping () -> Content
  19. ) {
  20. #if os(WASI)
  21. let title = title ?? "Title"
  22. #else
  23. let title = title ?? ProcessInfo.processInfo.processName
  24. #endif
  25. self.id = id
  26. self.title = title
  27. self.content = content
  28. }
  29. }
  30. /// The ``SceneGraphNode`` corresponding to a ``WindowGroup`` scene.
  31. public final class WindowGroupNode<Content: View>: SceneGraphNode {
  32. public typealias NodeScene = WindowGroup<Content>
  33. /// The references to the underlying window objects, which also manage
  34. /// each window's view graph.
  35. ///
  36. /// Empty if there are currently no instances of the window.
  37. private var windowReferences: [UUID: WindowReference<WindowGroup<Content>>] = [:]
  38. /// The underlying scene.
  39. private var scene: WindowGroup<Content>
  40. public init<Backend: BaseAppBackend>(
  41. from scene: WindowGroup<Content>,
  42. backend: Backend,
  43. environment: EnvironmentValues
  44. ) {
  45. self.scene = scene
  46. let openOnAppLaunch =
  47. switch environment.defaultLaunchBehavior {
  48. case .automatic, .presented: true
  49. case .suppressed: false
  50. }
  51. if openOnAppLaunch {
  52. let windowID = UUID()
  53. self.windowReferences = [
  54. windowID: WindowReference(
  55. scene: scene,
  56. backend: backend,
  57. environment: environment,
  58. onClose: { self.windowReferences[windowID] = nil },
  59. id: nextId()
  60. )
  61. ]
  62. }
  63. }
  64. private func nextId() -> String {
  65. // Vaguely based off the research covered by https://github.com/pd95/SwiftUI-macos-HandleWindow
  66. let baseId = scene.id ?? String(describing: Content.self)
  67. return "\(baseId)-\(windowReferences.count)"
  68. }
  69. public func updateNode(
  70. _ newScene: NodeScene?,
  71. environment: EnvironmentValues
  72. ) -> SceneNodeUpdateResult {
  73. if let newScene {
  74. self.scene = newScene
  75. }
  76. return .leafScene()
  77. }
  78. public func update<Backend: BaseAppBackend>(
  79. backend: Backend,
  80. environment: EnvironmentValues
  81. ) {
  82. if let id = scene.id {
  83. environment.openWindowFunctionsByID.value[id] = { [weak self] in
  84. guard let self else { return }
  85. let windowID = UUID()
  86. let reference = WindowReference(
  87. scene: scene,
  88. backend: backend,
  89. environment: environment,
  90. onClose: { self.windowReferences[windowID] = nil },
  91. id: nextId()
  92. )
  93. windowReferences[windowID] = reference
  94. reference.update(
  95. nil,
  96. backend: backend,
  97. environment: environment
  98. )
  99. }
  100. }
  101. for windowReference in windowReferences.values {
  102. windowReference.update(
  103. scene,
  104. backend: backend,
  105. environment: environment
  106. )
  107. }
  108. }
  109. }