Skip to content

Commit 962649e

Browse files
committed
Fix that the overlay panel is not visible
1 parent 2b7cb17 commit 962649e

File tree

4 files changed

+103
-56
lines changed

4 files changed

+103
-56
lines changed

OverlayWindow/Sources/OverlayWindow/IDEWorkspaceWindowOverlayWindowController.swift

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import Foundation
55
import SwiftUI
66
import XcodeInspector
77

8+
@MainActor
89
public protocol IDEWorkspaceWindowOverlayWindowControllerContentProvider {
910
associatedtype Content: View
1011
func createWindow() -> NSWindow?
1112
func createContent() -> Content
13+
func destroy()
1214

1315
init(windowInspector: WorkspaceXcodeWindowInspector, application: NSRunningApplication)
1416
}
@@ -38,64 +40,60 @@ final class IDEWorkspaceWindowOverlayWindowController {
3840
) {
3941
self.inspector = inspector
4042
self.application = application
41-
contentProviders = contentProviderFactory(inspector, application)
42-
43-
// Create the invisible panel
44-
let panel = NSPanel(
45-
contentRect: .zero,
46-
styleMask: [.borderless],
47-
backing: .buffered,
48-
defer: false
49-
)
50-
panel.isReleasedWhenClosed = false
51-
panel.isOpaque = false
52-
panel.backgroundColor = .clear
53-
panel.hasShadow = false
54-
panel.ignoresMouseEvents = true
55-
panel.alphaValue = 0
56-
panel.collectionBehavior = [.canJoinAllSpaces, .transient]
57-
panel.level = widgetLevel(0)
58-
panel.setIsVisible(true)
59-
maskPanel = panel
60-
61-
panel.contentView = NSHostingView(
62-
rootView: ZStack {
43+
let contentProviders = contentProviderFactory(inspector, application)
44+
self.contentProviders = contentProviders
45+
46+
let panel = OverlayPanel(
47+
contentRect: .init(x: 0, y: 0, width: 200, height: 200)
48+
) {
49+
ZStack {
50+
Text("Hello World")
51+
.padding()
52+
.background(
53+
Rectangle()
54+
.fill(.black)
55+
.opacity(0.3)
56+
)
6357
ForEach(0..<contentProviders.count, id: \.self) { (index: Int) in
64-
self.contentProviders[index].contentBody
58+
contentProviders[index].contentBody
6559
}
6660
}
61+
.background {
62+
Rectangle().fill(.green.opacity(0.2))
63+
}
6764
.allowsHitTesting(false)
68-
)
69-
65+
}
66+
maskPanel = panel
67+
7068
for contentProvider in contentProviders {
7169
if let window = contentProvider.createWindow() {
7270
panel.addChildWindow(window, ordered: .above)
7371
}
7472
}
7573

76-
// Listen to AX notifications for window move/resize
7774
let windowElement = inspector.uiElement
7875
let stream = AXNotificationStream(
7976
app: application,
8077
element: windowElement,
8178
notificationNames: kAXMovedNotification, kAXResizedNotification
8279
)
8380

84-
axNotificationTask = Task { [weak self] in
81+
axNotificationTask = Task { [weak panel] in
8582
for await notification in stream {
86-
guard let self else { return }
83+
guard let panel else { return }
84+
if Task.isCancelled { return }
8785
switch notification.name {
8886
case kAXMovedNotification, kAXResizedNotification:
8987
if let rect = windowElement.rect {
90-
self.maskPanel.setFrame(rect, display: false)
88+
panel.setFrame(rect, display: false)
9189
}
9290
default: continue
9391
}
9492
}
9593
}
9694

9795
if let rect = windowElement.rect {
98-
maskPanel.setFrame(rect, display: false)
96+
panel.setFrame(rect, display: false)
9997
}
10098
}
10199

@@ -111,40 +109,29 @@ final class IDEWorkspaceWindowOverlayWindowController {
111109
}
112110
}
113111

114-
/// Make the window the top most window and visible.
115112
func access() {
116113
lastAccessDate = Date()
117-
maskPanel.level = widgetLevel(0)
114+
maskPanel.level = overlayLevel(0)
118115
maskPanel.setIsVisible(true)
119116
maskPanel.orderFrontRegardless()
120117
}
121118

122-
/// Stop keeping the window the top most window, do not change visibility.
123119
func dim() {
124120
maskPanel.level = .normal
125121
}
126122

127-
/// Hide the window.
128123
func hide() {
129124
maskPanel.setIsVisible(false)
130125
maskPanel.level = .normal
131126
}
132127

133-
/// Destroy the controller and clean up resources.
134128
func destroy() {
135129
axNotificationTask?.cancel()
136130
maskPanel.close()
131+
for contentProvider in contentProviders {
132+
contentProvider.destroy()
133+
}
137134
isDestroyed = true
138135
}
139136
}
140137

141-
func widgetLevel(_ addition: Int) -> NSWindow.Level {
142-
let minimumWidgetLevel: Int
143-
#if DEBUG
144-
minimumWidgetLevel = NSWindow.Level.floating.rawValue + 1
145-
#else
146-
minimumWidgetLevel = NSWindow.Level.floating.rawValue
147-
#endif
148-
return .init(minimumWidgetLevel + addition)
149-
}
150-
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import AppKit
2+
import SwiftUI
3+
4+
@MainActor
5+
final class OverlayPanel<Content: View>: NSPanel {
6+
init(
7+
contentRect: NSRect,
8+
@ViewBuilder content: () -> Content
9+
) {
10+
super.init(
11+
contentRect: contentRect,
12+
styleMask: [
13+
.borderless,
14+
.nonactivatingPanel,
15+
.fullSizeContentView,
16+
],
17+
backing: .buffered,
18+
defer: false
19+
)
20+
21+
isReleasedWhenClosed = false
22+
isOpaque = false
23+
backgroundColor = .clear
24+
hasShadow = true
25+
alphaValue = 1.0
26+
collectionBehavior = [.fullScreenAuxiliary]
27+
isFloatingPanel = true
28+
titleVisibility = .hidden
29+
titlebarAppearsTransparent = true
30+
animationBehavior = .utilityWindow
31+
32+
standardWindowButton(.closeButton)?.isHidden = true
33+
standardWindowButton(.miniaturizeButton)?.isHidden = true
34+
standardWindowButton(.zoomButton)?.isHidden = true
35+
36+
contentView = NSHostingView(
37+
rootView: content()
38+
)
39+
}
40+
41+
override var canBecomeKey: Bool {
42+
return true
43+
}
44+
45+
override var canBecomeMain: Bool {
46+
return false
47+
}
48+
}
49+
50+
func overlayLevel(_ addition: Int) -> NSWindow.Level {
51+
let minimumWidgetLevel: Int
52+
#if DEBUG
53+
minimumWidgetLevel = NSWindow.Level.floating.rawValue + 1
54+
#else
55+
minimumWidgetLevel = NSWindow.Level.floating.rawValue
56+
#endif
57+
return .init(minimumWidgetLevel + addition)
58+
}

OverlayWindow/Sources/OverlayWindow/OverlayWindowController.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import XcodeInspector
55

66
@MainActor
77
public final class OverlayWindowController {
8-
public typealias IDEWorkspaceWindowOverlayWindowControllerContentProviderFactory = @Sendable (
9-
_ windowInspector: WorkspaceXcodeWindowInspector,
10-
_ application: NSRunningApplication
11-
) -> any IDEWorkspaceWindowOverlayWindowControllerContentProvider
8+
public typealias IDEWorkspaceWindowOverlayWindowControllerContentProviderFactory =
9+
@MainActor @Sendable (
10+
_ windowInspector: WorkspaceXcodeWindowInspector,
11+
_ application: NSRunningApplication
12+
) -> any IDEWorkspaceWindowOverlayWindowControllerContentProvider
1213

1314
static var ideWindowOverlayWindowControllerContentProviderFactories:
1415
[IDEWorkspaceWindowOverlayWindowControllerContentProviderFactory] = []
@@ -21,10 +22,12 @@ public final class OverlayWindowController {
2122
observeEvents()
2223
}
2324

24-
public static func registerIDEWorkspaceWindowOverlayWindowControllerContentProviderFactory(
25+
public nonisolated static func registerIDEWorkspaceWindowOverlayWindowControllerContentProviderFactory(
2526
_ factory: @escaping IDEWorkspaceWindowOverlayWindowControllerContentProviderFactory
2627
) {
27-
ideWindowOverlayWindowControllerContentProviderFactories.append(factory)
28+
Task { @MainActor in
29+
ideWindowOverlayWindowControllerContentProviderFactories.append(factory)
30+
}
2831
}
2932
}
3033

@@ -102,11 +105,10 @@ private extension OverlayWindowController {
102105
let newController = IDEWorkspaceWindowOverlayWindowController(
103106
inspector: inspector,
104107
application: application,
105-
contentProviderFactory: { [ideWindowOverlayWindowControllerContentProviderFactories]
108+
contentProviderFactory: {
106109
windowInspector, application in
107-
ideWindowOverlayWindowControllerContentProviderFactories.map {
108-
$0(windowInspector, application)
109-
}
110+
OverlayWindowController.ideWindowOverlayWindowControllerContentProviderFactories
111+
.map { $0(windowInspector, application) }
110112
}
111113
)
112114
newController.access()

Tool/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ let package = Package(
3434
.library(
3535
name: "SuggestionProvider",
3636
targets: ["SuggestionProvider", "GitHubCopilotService", "CodeiumService"]
37-
),
37+
),
3838
.library(
3939
name: "AppMonitoring",
4040
targets: [

0 commit comments

Comments
 (0)