Skip to content

Commit 4edb610

Browse files
committed
Adjust implementation
1 parent 7fd0ffe commit 4edb610

2 files changed

Lines changed: 92 additions & 67 deletions

File tree

OverlayWindow/Sources/OverlayWindow/IDEWorkspaceWindowOverlayWindowController.swift

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AppKit
22
import AXExtension
33
import AXNotificationStream
44
import Foundation
5+
import Perception
56
import SwiftUI
67
import XcodeInspector
78

@@ -28,7 +29,7 @@ final class IDEWorkspaceWindowOverlayWindowController {
2829
let inspector: WorkspaceXcodeWindowInspector
2930
let contentProviders: [any IDEWorkspaceWindowOverlayWindowControllerContentProvider]
3031
private var isDestroyed: Bool = false
31-
private let maskPanel: NSPanel
32+
private let maskPanel: OverlayPanel
3233
private var axNotificationTask: Task<Void, Never>?
3334

3435
init(
@@ -46,11 +47,9 @@ final class IDEWorkspaceWindowOverlayWindowController {
4647
let panel = OverlayPanel(
4748
contentRect: .init(x: 0, y: 0, width: 200, height: 200)
4849
) {
49-
ContentWrapper {
50-
ZStack {
51-
ForEach(0..<contentProviders.count, id: \.self) { index in
52-
contentProviders[index].contentBody
53-
}
50+
ZStack {
51+
ForEach(0..<contentProviders.count, id: \.self) { index in
52+
contentProviders[index].contentBody
5453
}
5554
}
5655
}
@@ -69,31 +68,22 @@ final class IDEWorkspaceWindowOverlayWindowController {
6968
notificationNames: kAXMovedNotification, kAXResizedNotification
7069
)
7170

72-
axNotificationTask = Task { [weak panel] in
71+
axNotificationTask = Task { [weak self] in
7372
for await notification in stream {
74-
guard let panel else { return }
73+
guard let panel = self?.maskPanel else { continue }
7574
if Task.isCancelled { return }
7675
switch notification.name {
7776
case kAXMovedNotification, kAXResizedNotification:
7877
if let rect = windowElement.rect {
79-
let screen = NSScreen.screens
80-
.first(where: { $0.frame.intersects(rect) }) ?? NSScreen.main
81-
let panelFrame = Self.convertAXRectToNSPanelFrame(
82-
axRect: rect,
83-
forScreen: screen
84-
)
85-
panel.setFrame(panelFrame, display: false)
78+
panel.setTopLeftCoordinateFrame(rect, display: true)
8679
}
8780
default: continue
8881
}
8982
}
9083
}
9184

9285
if let rect = windowElement.rect {
93-
let screen = NSScreen.screens.first(where: { $0.frame.intersects(rect) }) ?? NSScreen
94-
.main
95-
let panelFrame = Self.convertAXRectToNSPanelFrame(axRect: rect, forScreen: screen)
96-
panel.setFrame(panelFrame, display: false)
86+
panel.setTopLeftCoordinateFrame(rect, display: false)
9787
}
9888
}
9989

@@ -135,47 +125,3 @@ final class IDEWorkspaceWindowOverlayWindowController {
135125
}
136126
}
137127

138-
extension IDEWorkspaceWindowOverlayWindowController {
139-
struct ContentWrapper<Content: View>: View {
140-
@ViewBuilder let content: () -> Content
141-
@State var showOverlayArea: Bool = false
142-
143-
var body: some View {
144-
content()
145-
.background {
146-
if showOverlayArea {
147-
Rectangle().fill(.green.opacity(0.2))
148-
}
149-
}
150-
.overlay(alignment: .topTrailing) {
151-
#if DEBUG
152-
HStack {
153-
Button(action: {
154-
showOverlayArea.toggle()
155-
}) {
156-
Image(systemName: "eye")
157-
.foregroundColor(showOverlayArea ? .green : .red)
158-
}
159-
.padding()
160-
}
161-
#else
162-
EmptyView()
163-
#endif
164-
}
165-
}
166-
}
167-
168-
static func convertAXRectToNSPanelFrame(axRect: CGRect, forScreen screen: NSScreen?) -> CGRect {
169-
guard let screen = screen else { return .zero }
170-
let screenFrame = screen.frame
171-
let flippedY = screenFrame.origin.y + screenFrame.size
172-
.height - (axRect.origin.y + axRect.size.height)
173-
return CGRect(
174-
x: axRect.origin.x,
175-
y: flippedY,
176-
width: axRect.size.width,
177-
height: axRect.size.height
178-
)
179-
}
180-
}
181-

OverlayWindow/Sources/OverlayWindow/OverlayPanel.swift

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
import AppKit
2+
import Perception
23
import SwiftUI
34

5+
struct OverlayFrameEnvironmentKey: EnvironmentKey {
6+
static let defaultValue: CGRect = .zero
7+
}
8+
9+
public extension EnvironmentValues {
10+
var overlayFrame: CGRect {
11+
get { self[OverlayFrameEnvironmentKey.self] }
12+
set { self[OverlayFrameEnvironmentKey.self] = newValue }
13+
}
14+
}
15+
416
@MainActor
5-
final class OverlayPanel<Content: View>: NSPanel {
6-
init(
17+
final class OverlayPanel: NSPanel {
18+
@MainActor
19+
@Perceptible
20+
final class PanelState {
21+
var windowFrame: CGRect = .zero
22+
var windowFrameNSCoordinate: CGRect = .zero
23+
}
24+
25+
let panelState: PanelState = .init()
26+
27+
init<Content: View>(
728
contentRect: NSRect,
8-
@ViewBuilder content: () -> Content
29+
@ViewBuilder content: @escaping () -> Content
930
) {
1031
super.init(
1132
contentRect: contentRect,
@@ -34,7 +55,7 @@ final class OverlayPanel<Content: View>: NSPanel {
3455
standardWindowButton(.zoomButton)?.isHidden = true
3556

3657
contentView = NSHostingView(
37-
rootView: content()
58+
rootView: ContentWrapper(panelState: panelState) { content() }
3859
)
3960
}
4061

@@ -45,6 +66,63 @@ final class OverlayPanel<Content: View>: NSPanel {
4566
override var canBecomeMain: Bool {
4667
return false
4768
}
69+
70+
func setTopLeftCoordinateFrame(_ frame: CGRect, display: Bool) {
71+
let screen = NSScreen.screens
72+
.first(where: { $0.frame.intersects(frame) }) ?? NSScreen.main
73+
let panelFrame = Self.convertAXRectToNSPanelFrame(
74+
axRect: frame,
75+
forScreen: screen
76+
)
77+
panelState.windowFrame = frame
78+
panelState.windowFrameNSCoordinate = panelFrame
79+
setFrame(panelFrame, display: display)
80+
}
81+
82+
static func convertAXRectToNSPanelFrame(axRect: CGRect, forScreen screen: NSScreen?) -> CGRect {
83+
guard let screen = screen else { return .zero }
84+
let screenFrame = screen.frame
85+
let flippedY = screenFrame.origin.y + screenFrame.size
86+
.height - (axRect.origin.y + axRect.size.height)
87+
return CGRect(
88+
x: axRect.origin.x,
89+
y: flippedY,
90+
width: axRect.size.width,
91+
height: axRect.size.height
92+
)
93+
}
94+
95+
struct ContentWrapper<Content: View>: View {
96+
let panelState: PanelState
97+
@ViewBuilder let content: () -> Content
98+
@State var showOverlayArea: Bool = false
99+
100+
var body: some View {
101+
WithPerceptionTracking {
102+
content()
103+
.environment(\.overlayFrame, panelState.windowFrame)
104+
#if DEBUG
105+
.background {
106+
if showOverlayArea {
107+
Rectangle().fill(.green.opacity(0.2))
108+
}
109+
}
110+
.overlay(alignment: .topTrailing) {
111+
HStack {
112+
Button(action: {
113+
showOverlayArea.toggle()
114+
}) {
115+
Image(systemName: "eye")
116+
.foregroundColor(showOverlayArea ? .green : .red)
117+
.padding()
118+
}
119+
.buttonStyle(.plain)
120+
}
121+
}
122+
#endif
123+
}
124+
}
125+
}
48126
}
49127

50128
func overlayLevel(_ addition: Int) -> NSWindow.Level {
@@ -56,3 +134,4 @@ func overlayLevel(_ addition: Int) -> NSWindow.Level {
56134
#endif
57135
return .init(minimumWidgetLevel + addition)
58136
}
137+

0 commit comments

Comments
 (0)