general: attempts at better window management
This commit is contained in:
@@ -17,8 +17,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
private var updateManager: UpdateManager?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var hasStartedTimers = false
|
||||
private var isSettingsWindowOpen = false
|
||||
private var isOnboardingWindowOpen = false
|
||||
|
||||
// Convenience accessor for settings
|
||||
private var settingsManager: any SettingsProviding {
|
||||
@@ -51,11 +49,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
|
||||
serviceContainer.setupSmartModeServices()
|
||||
|
||||
// Check if onboarding needs to be shown automatically
|
||||
if !settingsManager.settings.hasCompletedOnboarding {
|
||||
// Set the flag to indicate we expect an onboarding window
|
||||
isOnboardingWindowOpen = true
|
||||
}
|
||||
|
||||
// Initialize update manager after onboarding is complete
|
||||
if settingsManager.settings.hasCompletedOnboarding {
|
||||
@@ -228,45 +221,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private let menuDismissalDelay: TimeInterval = 0.1
|
||||
|
||||
func openSettings(tab: Int = 0) {
|
||||
// If settings window is already open, focus it instead of opening new one
|
||||
if isSettingsWindowOpen {
|
||||
// Try to focus existing window
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name("SwitchToSettingsTab"),
|
||||
object: tab
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handleMenuDismissal()
|
||||
isSettingsWindowOpen = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
performAfterMenuDismissal { [weak self] in
|
||||
guard let self else { return }
|
||||
windowManager.showSettings(settingsManager: self.settingsManager, initialTab: tab)
|
||||
self.windowManager.showSettings(settingsManager: self.settingsManager, initialTab: tab)
|
||||
}
|
||||
}
|
||||
|
||||
func openOnboarding() {
|
||||
// If onboarding window is already open, focus it instead of opening new one
|
||||
if isOnboardingWindowOpen {
|
||||
// Try to activate existing window
|
||||
DispatchQueue.main.async {
|
||||
OnboardingWindowPresenter.shared.activateIfPresent()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
handleMenuDismissal()
|
||||
// Explicitly set the flag to true when we're about to show the onboarding window
|
||||
isOnboardingWindowOpen = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
performAfterMenuDismissal { [weak self] in
|
||||
guard let self else { return }
|
||||
windowManager.showOnboarding(settingsManager: self.settingsManager)
|
||||
self.windowManager.showOnboarding(settingsManager: self.settingsManager)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,8 +242,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
windowManager.dismissOverlayReminder()
|
||||
}
|
||||
|
||||
private func performAfterMenuDismissal(_ action: @escaping () -> Void) {
|
||||
handleMenuDismissal()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + menuDismissalDelay) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupWindowCloseObservers() {
|
||||
// Observe settings window closing
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(settingsWindowDidClose),
|
||||
@@ -284,7 +257,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
object: nil
|
||||
)
|
||||
|
||||
// Observe onboarding window closing
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(onboardingWindowDidClose),
|
||||
@@ -293,13 +265,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
||||
)
|
||||
}
|
||||
|
||||
@objc private func settingsWindowDidClose() {
|
||||
isSettingsWindowOpen = false
|
||||
}
|
||||
@objc private func settingsWindowDidClose() {}
|
||||
|
||||
@objc private func onboardingWindowDidClose() {
|
||||
// Reset the flag when we receive the close notification
|
||||
isOnboardingWindowOpen = false
|
||||
}
|
||||
@objc private func onboardingWindowDidClose() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -76,6 +76,11 @@ enum EyeTrackingConstants: Sendable {
|
||||
/// > 1.0 = More aggressive scaling
|
||||
static let distanceSensitivity: Double = 1.0
|
||||
|
||||
/// Default reference face width for distance scaling when uncalibrated.
|
||||
/// Measured from test videos at typical laptop distance (~60cm).
|
||||
/// Face bounding box width as ratio of image width.
|
||||
static let defaultReferenceFaceWidth: Double = 0.4566
|
||||
|
||||
/// Minimum confidence required for a valid pupil detection before updating the gaze average.
|
||||
/// Helps filter out blinks or noisy frames.
|
||||
static let minimumGazeConfidence: Int = 3 // consecutive valid frames
|
||||
|
||||
@@ -144,7 +144,7 @@ struct GazeThresholds: Codable {
|
||||
screenRightBound: 0.20, // Right edge of screen
|
||||
screenTopBound: 0.35, // Top edge of screen
|
||||
screenBottomBound: 0.55, // Bottom edge of screen
|
||||
referenceFaceWidth: 0.0 // 0.0 means unused/uncalibrated
|
||||
referenceFaceWidth: 0.4566 // Measured from test videos (avg of inner/outer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,8 +535,23 @@ class EyeTrackingService: NSObject, ObservableObject {
|
||||
|
||||
} else {
|
||||
// Fallback to default constants (no calibration)
|
||||
let lookingRight = avgH <= EyeTrackingConstants.pixelGazeMinRatio
|
||||
let lookingLeft = avgH >= EyeTrackingConstants.pixelGazeMaxRatio
|
||||
// Still apply distance scaling using default reference
|
||||
let currentFaceWidth = face.boundingBox.width
|
||||
let refFaceWidth = EyeTrackingConstants.defaultReferenceFaceWidth
|
||||
|
||||
var distanceScale = 1.0
|
||||
if refFaceWidth > 0 && currentFaceWidth > 0 {
|
||||
let rawScale = refFaceWidth / currentFaceWidth
|
||||
distanceScale = 1.0 + (rawScale - 1.0) * EyeTrackingConstants.distanceSensitivity
|
||||
distanceScale = max(0.5, min(2.0, distanceScale))
|
||||
}
|
||||
|
||||
// Center is assumed at midpoint of the thresholds
|
||||
let centerH = (EyeTrackingConstants.pixelGazeMinRatio + EyeTrackingConstants.pixelGazeMaxRatio) / 2.0
|
||||
let normalizedH = centerH + (avgH - centerH) * distanceScale
|
||||
|
||||
let lookingRight = normalizedH <= EyeTrackingConstants.pixelGazeMinRatio
|
||||
let lookingLeft = normalizedH >= EyeTrackingConstants.pixelGazeMaxRatio
|
||||
eyesLookingAway = lookingRight || lookingLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,12 @@ final class OnboardingWindowPresenter {
|
||||
|
||||
private weak var windowController: NSWindowController?
|
||||
private var closeObserver: NSObjectProtocol?
|
||||
private var isShowingWindow = false
|
||||
|
||||
func show(settingsManager: SettingsManager) {
|
||||
if activateIfPresent() { return }
|
||||
guard !isShowingWindow else { return }
|
||||
isShowingWindow = true
|
||||
createWindow(settingsManager: settingsManager)
|
||||
}
|
||||
|
||||
@@ -44,15 +47,26 @@ final class OnboardingWindowPresenter {
|
||||
windowController = nil
|
||||
return false
|
||||
}
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeMain()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NSApp.unhide(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
if window.isMiniaturized {
|
||||
window.deminiaturize(nil)
|
||||
}
|
||||
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
window.makeMain()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func close() {
|
||||
windowController?.close()
|
||||
windowController = nil
|
||||
isShowingWindow = false
|
||||
removeCloseObserver()
|
||||
}
|
||||
|
||||
@@ -69,14 +83,18 @@ final class OnboardingWindowPresenter {
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.center()
|
||||
window.isReleasedWhenClosed = true
|
||||
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary]
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: OnboardingContainerView(settingsManager: settingsManager)
|
||||
)
|
||||
|
||||
let controller = NSWindowController(window: window)
|
||||
controller.showWindow(nil)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.unhide(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
|
||||
windowController = controller
|
||||
|
||||
@@ -88,6 +106,7 @@ final class OnboardingWindowPresenter {
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.windowController = nil
|
||||
self?.isShowingWindow = false
|
||||
self?.removeCloseObserver()
|
||||
}
|
||||
NotificationCenter.default.post(
|
||||
|
||||
@@ -13,9 +13,12 @@ final class SettingsWindowPresenter {
|
||||
|
||||
private weak var windowController: NSWindowController?
|
||||
private var closeObserver: NSObjectProtocol?
|
||||
private var isShowingWindow = false
|
||||
|
||||
func show(settingsManager: SettingsManager, initialTab: Int = 0) {
|
||||
if focusExistingWindow(tab: initialTab) { return }
|
||||
guard !isShowingWindow else { return }
|
||||
isShowingWindow = true
|
||||
createWindow(settingsManager: settingsManager, initialTab: initialTab)
|
||||
}
|
||||
|
||||
@@ -26,6 +29,7 @@ final class SettingsWindowPresenter {
|
||||
func close() {
|
||||
windowController?.close()
|
||||
windowController = nil
|
||||
isShowingWindow = false
|
||||
removeCloseObserver()
|
||||
}
|
||||
|
||||
@@ -36,15 +40,24 @@ final class SettingsWindowPresenter {
|
||||
return false
|
||||
}
|
||||
|
||||
if let tab {
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name("SwitchToSettingsTab"),
|
||||
object: tab
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if let tab {
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name("SwitchToSettingsTab"),
|
||||
object: tab
|
||||
)
|
||||
}
|
||||
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
NSApp.unhide(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
if window.isMiniaturized {
|
||||
window.deminiaturize(nil)
|
||||
}
|
||||
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -65,14 +78,18 @@ final class SettingsWindowPresenter {
|
||||
window.setFrameAutosaveName("SettingsWindow")
|
||||
window.isReleasedWhenClosed = false
|
||||
|
||||
window.collectionBehavior = [.managed, .participatesInCycle, .moveToActiveSpace, .fullScreenAuxiliary]
|
||||
|
||||
window.contentView = NSHostingView(
|
||||
rootView: SettingsWindowView(settingsManager: settingsManager, initialTab: initialTab)
|
||||
)
|
||||
|
||||
let controller = NSWindowController(window: window)
|
||||
controller.showWindow(nil)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.unhide(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
window.orderFrontRegardless()
|
||||
|
||||
windowController = controller
|
||||
|
||||
@@ -84,10 +101,12 @@ final class SettingsWindowPresenter {
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor [weak self] in
|
||||
self?.windowController = nil
|
||||
self?.isShowingWindow = false
|
||||
self?.removeCloseObserver()
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name("SettingsWindowDidClose"), object: nil)
|
||||
}
|
||||
self?.isShowingWindow = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user