feat: recording capsule visible in fullscreen and clicking doesnt focus main window (#46)
This commit is contained in:
@@ -44,19 +44,7 @@ struct MeetingnotesApp: App {
|
|||||||
CommandGroup(after: .appInfo) {
|
CommandGroup(after: .appInfo) {
|
||||||
CheckForUpdatesView(updater: updaterController.updater)
|
CheckForUpdatesView(updater: updaterController.updater)
|
||||||
}
|
}
|
||||||
// Hidden command group that handles audio level window notifications
|
|
||||||
CommandGroup(after: .windowArrangement) {
|
|
||||||
ShowAudioLevelsCommand()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Floating Audio Level Window
|
|
||||||
Window("", id: "audio-levels") {
|
|
||||||
AudioLevelWindowView()
|
|
||||||
}
|
|
||||||
.windowStyle(.plain)
|
|
||||||
.windowResizability(.contentSize)
|
|
||||||
.defaultPosition(.trailing)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,18 +58,3 @@ struct CheckForUpdatesView: View {
|
|||||||
.keyboardShortcut("u", modifiers: .command)
|
.keyboardShortcut("u", modifiers: .command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShowAudioLevelsCommand: View {
|
|
||||||
@Environment(\.openWindow) private var openWindow
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
// Empty view - we only need this for the notification observer
|
|
||||||
EmptyView()
|
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .openAudioLevelWindow)) { _ in
|
|
||||||
openWindow(id: "audio-levels")
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
||||||
AudioLevelWindowManager.shared.showWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ struct AudioLevelWindowView: View {
|
|||||||
@StateObject private var audioLevelManager = AudioLevelManager.shared
|
@StateObject private var audioLevelManager = AudioLevelManager.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let micLevel = audioLevelManager.isRecording ? audioLevelManager.micAudioLevel * 40 : 0
|
let micLevel = audioLevelManager.isRecording ? audioLevelManager.micAudioLevel * 30 : 0
|
||||||
let systemLevel = audioLevelManager.isRecording ? audioLevelManager.systemAudioLevel * 5 : 0
|
let systemLevel = audioLevelManager.isRecording ? audioLevelManager.systemAudioLevel * 5 : 0
|
||||||
return DancingAudioBars(
|
return DancingAudioBars(
|
||||||
micLevel: micLevel,
|
micLevel: micLevel,
|
||||||
@@ -124,42 +124,68 @@ class AudioLevelManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Audio Level Window Manager
|
// MARK: - Audio Level Window Manager using NSPanel
|
||||||
@MainActor
|
@MainActor
|
||||||
class AudioLevelWindowManager: ObservableObject {
|
class AudioLevelWindowManager: ObservableObject {
|
||||||
static let shared = AudioLevelWindowManager()
|
static let shared = AudioLevelWindowManager()
|
||||||
|
|
||||||
private init() {}
|
private var audioLevelPanel: NSPanel?
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
setupPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupPanel() {
|
||||||
|
let panelRect = NSRect(x: 0, y: 0, width: 60, height: 80)
|
||||||
|
|
||||||
|
audioLevelPanel = NSPanel(
|
||||||
|
contentRect: panelRect,
|
||||||
|
styleMask: [.nonactivatingPanel, .borderless],
|
||||||
|
backing: .buffered,
|
||||||
|
defer: true
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let panel = audioLevelPanel else { return }
|
||||||
|
|
||||||
|
// Configure panel properties inspired by the provided code
|
||||||
|
panel.level = .mainMenu
|
||||||
|
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
|
||||||
|
panel.isFloatingPanel = true
|
||||||
|
panel.isMovableByWindowBackground = true
|
||||||
|
panel.hasShadow = true
|
||||||
|
panel.backgroundColor = .clear
|
||||||
|
panel.isOpaque = false
|
||||||
|
panel.identifier = NSUserInterfaceItemIdentifier("audio-levels")
|
||||||
|
|
||||||
|
// Set up the SwiftUI hosting view
|
||||||
|
let hostingView = NSHostingView(rootView: AudioLevelWindowView())
|
||||||
|
hostingView.frame = panelRect
|
||||||
|
panel.contentView = hostingView
|
||||||
|
|
||||||
|
// Position the panel in the top-right corner of the screen
|
||||||
|
if let screen = NSScreen.main {
|
||||||
|
let screenFrame = screen.visibleFrame
|
||||||
|
let panelFrame = NSRect(
|
||||||
|
x: screenFrame.maxX - panelRect.width - 20,
|
||||||
|
y: screenFrame.maxY - panelRect.height - 20,
|
||||||
|
width: panelRect.width,
|
||||||
|
height: panelRect.height
|
||||||
|
)
|
||||||
|
panel.setFrame(panelFrame, display: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func showWindow() {
|
func showWindow() {
|
||||||
// Find the audio levels window and bring it to front
|
guard let panel = audioLevelPanel else {
|
||||||
for window in NSApplication.shared.windows {
|
setupPanel()
|
||||||
if window.identifier?.rawValue == "audio-levels" {
|
showWindow()
|
||||||
window.orderFront(nil)
|
return
|
||||||
window.level = .floating
|
|
||||||
window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]
|
|
||||||
window.isMovableByWindowBackground = true
|
|
||||||
window.hasShadow = true
|
|
||||||
window.styleMask = [.borderless]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If window not found, post a notification to trigger opening
|
panel.orderFrontRegardless()
|
||||||
NotificationCenter.default.post(name: .openAudioLevelWindow, object: nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideWindow() {
|
func hideWindow() {
|
||||||
// Find the audio levels window and hide it
|
audioLevelPanel?.orderOut(nil)
|
||||||
for window in NSApplication.shared.windows {
|
|
||||||
if window.identifier?.rawValue == "audio-levels" {
|
|
||||||
window.orderOut(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
|
||||||
static let openAudioLevelWindow = Notification.Name("openAudioLevelWindow")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user