feat: proper menubarextra
This commit is contained in:
@@ -12,8 +12,6 @@ import Combine
|
|||||||
@MainActor
|
@MainActor
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
var timerEngine: TimerEngine?
|
var timerEngine: TimerEngine?
|
||||||
private var statusItem: NSStatusItem?
|
|
||||||
private var popover: NSPopover?
|
|
||||||
private var settingsManager: SettingsManager?
|
private var settingsManager: SettingsManager?
|
||||||
private var reminderWindowController: NSWindowController?
|
private var reminderWindowController: NSWindowController?
|
||||||
private var settingsWindowController: NSWindowController?
|
private var settingsWindowController: NSWindowController?
|
||||||
@@ -28,7 +26,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
settingsManager = SettingsManager.shared
|
settingsManager = SettingsManager.shared
|
||||||
timerEngine = TimerEngine(settingsManager: settingsManager!)
|
timerEngine = TimerEngine(settingsManager: settingsManager!)
|
||||||
|
|
||||||
setupMenuBar()
|
|
||||||
setupLifecycleObservers()
|
setupLifecycleObservers()
|
||||||
observeSettingsChanges()
|
observeSettingsChanges()
|
||||||
|
|
||||||
@@ -42,48 +39,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
startTimers()
|
startTimers()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupMenuBar() {
|
|
||||||
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
||||||
|
|
||||||
if let button = statusItem?.button {
|
|
||||||
button.image = NSImage(systemSymbolName: "eye.fill", accessibilityDescription: "Gaze")
|
|
||||||
button.action = #selector(togglePopover)
|
|
||||||
button.target = self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func togglePopover() {
|
|
||||||
if let popover = popover, popover.isShown {
|
|
||||||
popover.close()
|
|
||||||
} else {
|
|
||||||
showPopover()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func showPopover() {
|
|
||||||
// Reuse existing popover or create new one
|
|
||||||
if popover == nil {
|
|
||||||
let newPopover = NSPopover()
|
|
||||||
newPopover.contentSize = NSSize(width: 300, height: 400)
|
|
||||||
newPopover.behavior = .transient
|
|
||||||
popover = newPopover
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always set fresh content
|
|
||||||
popover?.contentViewController = NSHostingController(
|
|
||||||
rootView: MenuBarContentView(
|
|
||||||
timerEngine: timerEngine!,
|
|
||||||
settingsManager: settingsManager!,
|
|
||||||
onQuit: { NSApplication.shared.terminate(nil) },
|
|
||||||
onOpenSettings: { [weak self] in self?.openSettings() }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if let button = statusItem?.button {
|
|
||||||
popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startTimers() {
|
private func startTimers() {
|
||||||
guard !hasStartedTimers else { return }
|
guard !hasStartedTimers else { return }
|
||||||
hasStartedTimers = true
|
hasStartedTimers = true
|
||||||
@@ -226,11 +181,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
reminderWindowController = nil
|
reminderWindowController = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public method to get menubar icon position for animations
|
|
||||||
func getMenuBarIconPosition() -> NSRect? {
|
|
||||||
return statusItem?.button?.window?.frame
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public method to open settings window
|
// Public method to open settings window
|
||||||
func openSettings() {
|
func openSettings() {
|
||||||
// If window already exists, just bring it to front
|
// If window already exists, just bring it to front
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ struct GazeApp: App {
|
|||||||
@StateObject private var settingsManager = SettingsManager.shared
|
@StateObject private var settingsManager = SettingsManager.shared
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
|
// Onboarding window (only shown when not completed)
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
if settingsManager.settings.hasCompletedOnboarding {
|
if settingsManager.settings.hasCompletedOnboarding {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
@@ -33,6 +34,19 @@ struct GazeApp: App {
|
|||||||
.commands {
|
.commands {
|
||||||
CommandGroup(replacing: .newItem) { }
|
CommandGroup(replacing: .newItem) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Menu bar extra (always present once onboarding is complete)
|
||||||
|
MenuBarExtra("Gaze", systemImage: "eye.fill") {
|
||||||
|
if let timerEngine = appDelegate.timerEngine {
|
||||||
|
MenuBarContentView(
|
||||||
|
timerEngine: timerEngine,
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
onQuit: { NSApplication.shared.terminate(nil) },
|
||||||
|
onOpenSettings: { appDelegate.openSettings() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menuBarExtraStyle(.window)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func closeAllWindows() {
|
private func closeAllWindows() {
|
||||||
|
|||||||
@@ -188,30 +188,14 @@ struct OnboardingContainerView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get menubar icon position from AppDelegate
|
// Calculate target position (top-center of screen where menu bar is)
|
||||||
let appDelegate = NSApplication.shared.delegate as? AppDelegate
|
let screen = NSScreen.main?.frame ?? .zero
|
||||||
let targetFrame = appDelegate?.getMenuBarIconPosition()
|
let targetRect = NSRect(
|
||||||
|
x: screen.midX,
|
||||||
// Calculate target position (menubar icon or top-center as fallback)
|
y: screen.maxY,
|
||||||
let targetRect: NSRect
|
width: 0,
|
||||||
if let menuBarFrame = targetFrame {
|
height: 0
|
||||||
// Use menubar icon position
|
)
|
||||||
targetRect = NSRect(
|
|
||||||
x: menuBarFrame.midX,
|
|
||||||
y: menuBarFrame.midY,
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Fallback to top-center of screen
|
|
||||||
let screen = NSScreen.main?.frame ?? .zero
|
|
||||||
targetRect = NSRect(
|
|
||||||
x: screen.midX,
|
|
||||||
y: screen.maxY,
|
|
||||||
width: 0,
|
|
||||||
height: 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start SwiftUI animation for visual effects
|
// Start SwiftUI animation for visual effects
|
||||||
withAnimation(.easeInOut(duration: 0.7)) {
|
withAnimation(.easeInOut(duration: 0.7)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user