From 2136563428bd8d471de7367868271f62c5ab7cb0 Mon Sep 17 00:00:00 2001 From: Owen Gretzinger Date: Fri, 25 Jul 2025 17:31:20 -0400 Subject: [PATCH] feat: recording capsule visible in fullscreen and clicking doesnt focus main window (#46) --- meetingnotes/MeetingnotesApp.swift | 27 -------- meetingnotes/Views/AudioLevelWindow.swift | 80 +++++++++++++++-------- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/meetingnotes/MeetingnotesApp.swift b/meetingnotes/MeetingnotesApp.swift index 2e9c935..48d3737 100644 --- a/meetingnotes/MeetingnotesApp.swift +++ b/meetingnotes/MeetingnotesApp.swift @@ -44,19 +44,7 @@ struct MeetingnotesApp: App { CommandGroup(after: .appInfo) { 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) } } - -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() - } - } - } -} diff --git a/meetingnotes/Views/AudioLevelWindow.swift b/meetingnotes/Views/AudioLevelWindow.swift index 13ea098..f5a8210 100644 --- a/meetingnotes/Views/AudioLevelWindow.swift +++ b/meetingnotes/Views/AudioLevelWindow.swift @@ -76,7 +76,7 @@ struct AudioLevelWindowView: View { @StateObject private var audioLevelManager = AudioLevelManager.shared 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 return DancingAudioBars( micLevel: micLevel, @@ -124,42 +124,68 @@ class AudioLevelManager: ObservableObject { } } -// MARK: - Audio Level Window Manager +// MARK: - Audio Level Window Manager using NSPanel @MainActor class AudioLevelWindowManager: ObservableObject { 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() { - // Find the audio levels window and bring it to front - for window in NSApplication.shared.windows { - if window.identifier?.rawValue == "audio-levels" { - window.orderFront(nil) - window.level = .floating - window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle] - window.isMovableByWindowBackground = true - window.hasShadow = true - window.styleMask = [.borderless] - return - } + guard let panel = audioLevelPanel else { + setupPanel() + showWindow() + return } - // If window not found, post a notification to trigger opening - NotificationCenter.default.post(name: .openAudioLevelWindow, object: nil) + panel.orderFrontRegardless() } func hideWindow() { - // Find the audio levels window and hide it - for window in NSApplication.shared.windows { - if window.identifier?.rawValue == "audio-levels" { - window.orderOut(nil) - return - } - } + audioLevelPanel?.orderOut(nil) } -} - -extension Notification.Name { - static let openAudioLevelWindow = Notification.Name("openAudioLevelWindow") } \ No newline at end of file