Last active
October 31, 2024 01:20
-
-
Save MaximBazarov/30240366d60763efbc50324084ea8b47 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import AppKit | |
import SwiftUI | |
import Combine | |
import OSLog | |
/// Full screen window that hosts the SwiftUI view. | |
/// - Can not become active nor main so user stays in the app they were in. | |
/// - Ignores all mouse events allowing user to interact with content behind it. | |
/// - Enables mouse events, only when mouse is over its content view. | |
@MainActor | |
final class PassThroughWindow<Content: View>: NSPanel { | |
/// Provided ``CurrentValueSubject`` to listen to the hover over SwiftUI view events. | |
var isMouseTrackingEnabled: CurrentValueSubject<Bool, Never> | |
private let log = Logger(subsystem: "Window Management", category: "PassThroughWindow<Content: View>") | |
private var subscriptions: Set<AnyCancellable> = [] | |
private let content: Content | |
// Prevents window to be activated, we don't want that because then we remove focus | |
// from the active application and change it to our app, which creates a subpar user experience. | |
override var canBecomeKey: Bool { false } | |
override var canBecomeMain: Bool { false } | |
public init( | |
isMouseTrackingEnabled: CurrentValueSubject<Bool, Never>, | |
@ViewBuilder _ content: @escaping () -> Content | |
) { | |
self.isMouseTrackingEnabled = isMouseTrackingEnabled | |
let contentRect = NSRect.zero | |
self.content = content() | |
super.init( | |
contentRect: contentRect, | |
styleMask: [ | |
.nonactivatingPanel, | |
.fullSizeContentView, | |
], | |
backing: .buffered, | |
defer: false | |
) | |
collectionBehavior = [ | |
.canJoinAllSpaces, | |
.ignoresCycle, | |
.transient | |
] | |
// Allow the panel to appear in a fullscreen space | |
collectionBehavior.insert(.fullScreenAuxiliary) | |
// UI tweaks to be invisible. | |
isOpaque = false | |
hasShadow = false | |
backgroundColor = .clear | |
// Allow the panel to be on top of almost all other windows | |
isFloatingPanel = true | |
level = .floating | |
hidesOnDeactivate = false | |
becomesKeyOnlyIfNeeded = true | |
// While we may set a title for the window, don't show it | |
titleVisibility = .hidden | |
titlebarAppearsTransparent = true | |
isMovableByWindowBackground = false | |
acceptsMouseMovedEvents = true | |
orderFrontRegardless() | |
allowsToolTipsWhenApplicationIsInactive = true | |
let content = NSHostingView(rootView: self.content) | |
content.autoresizingMask = [.height, .width] | |
contentView?.addSubview(content) | |
// We activate/deactivate based on what the container tells us. | |
isMouseTrackingEnabled.sink { isEnabled in | |
isEnabled | |
? self.activateInteractions() | |
: self.deactivateInteractions() | |
}.store(in: &subscriptions) | |
} | |
/// Activates interaction with the window | |
func activateInteractions() { | |
ignoresMouseEvents = false | |
} | |
/// Deactivates interaction with the window | |
func deactivateInteractions() { | |
ignoresMouseEvents = true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment