This commit is contained in:
Michael Freno
2026-01-29 17:37:05 -05:00
parent 817f391305
commit 8631fa7207
8 changed files with 158 additions and 248 deletions

View File

@@ -1,29 +0,0 @@
//
// CalibrationSampleCollector.swift
// Gaze
//
// Created by Mike Freno on 1/29/26.
//
import Foundation
struct CalibrationSampleCollector {
mutating func addSample(
to data: inout CalibrationData,
step: CalibrationStep,
leftRatio: Double?,
rightRatio: Double?,
leftVertical: Double?,
rightVertical: Double?,
faceWidthRatio: Double?
) {
let sample = GazeSample(
leftRatio: leftRatio,
rightRatio: rightRatio,
leftVerticalRatio: leftVertical,
rightVerticalRatio: rightVertical,
faceWidthRatio: faceWidthRatio
)
data.addSample(sample, for: step)
}
}

View File

@@ -1,54 +0,0 @@
//
// CalibrationWindowManager.swift
// Gaze
//
// Manages the fullscreen calibration overlay window.
//
import AppKit
import SwiftUI
@MainActor
final class CalibrationWindowManager {
static let shared = CalibrationWindowManager()
private var windowController: NSWindowController?
private init() {}
func showCalibrationOverlay() {
guard let screen = NSScreen.main else { return }
let window = KeyableWindow(
contentRect: screen.frame,
styleMask: [.borderless, .fullSizeContentView],
backing: .buffered,
defer: false
)
window.level = .screenSaver
window.isOpaque = true
window.backgroundColor = .black
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.acceptsMouseMovedEvents = true
window.ignoresMouseEvents = false
let overlayView = CalibrationOverlayView {
self.dismissCalibrationOverlay()
}
window.contentView = NSHostingView(rootView: overlayView)
windowController = NSWindowController(window: window)
windowController?.showWindow(nil)
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
print("🎯 Calibration overlay window opened")
}
func dismissCalibrationOverlay() {
windowController?.close()
windowController = nil
print("🎯 Calibration overlay window closed")
}
}

View File

@@ -1,32 +1,35 @@
// //
// CalibrationManager.swift // CalibratorService.swift
// Gaze // Gaze
// //
// Created by Mike Freno on 1/15/26. // Created by Mike Freno on 1/29/26.
// //
import Combine import Combine
import Foundation import Foundation
import AppKit
import SwiftUI
@MainActor @MainActor
class CalibrationManager: ObservableObject { final class CalibratorService: ObservableObject {
static let shared = CalibrationManager() static let shared = CalibratorService()
// MARK: - Published Properties
@Published var isCalibrating = false @Published var isCalibrating = false
@Published var isCollectingSamples = false // True when actively collecting (after countdown) @Published var isCollectingSamples = false
@Published var currentStep: CalibrationStep? @Published var currentStep: CalibrationStep?
@Published var currentStepIndex = 0 @Published var currentStepIndex = 0
@Published var samplesCollected = 0 @Published var samplesCollected = 0
@Published var calibrationData = CalibrationData() @Published var calibrationData = CalibrationData()
// MARK: - Configuration private let samplesPerStep = 30
private let samplesPerStep = 30 // Collect 30 samples per calibration point (~1 second at 30fps)
private let userDefaultsKey = "eyeTrackingCalibration" private let userDefaultsKey = "eyeTrackingCalibration"
private let flowController: CalibrationFlowController
private var windowController: NSWindowController?
private let calibrationSteps: [CalibrationStep] = [ private init() {
self.flowController = CalibrationFlowController(
samplesPerStep: samplesPerStep,
calibrationSteps: [
.center, .center,
.left, .left,
.right, .right,
@@ -37,16 +40,6 @@ class CalibrationManager: ObservableObject {
.topLeft, .topLeft,
.topRight .topRight
] ]
private let flowController: CalibrationFlowController
private var sampleCollector = CalibrationSampleCollector()
// MARK: - Initialization
private init() {
self.flowController = CalibrationFlowController(
samplesPerStep: samplesPerStep,
calibrationSteps: calibrationSteps
) )
loadCalibration() loadCalibration()
bindFlowController() bindFlowController()
@@ -63,8 +56,6 @@ class CalibrationManager: ObservableObject {
.assign(to: &$samplesCollected) .assign(to: &$samplesCollected)
} }
// MARK: - Calibration Flow
func startCalibration() { func startCalibration() {
print("🎯 Starting calibration...") print("🎯 Starting calibration...")
isCalibrating = true isCalibrating = true
@@ -72,7 +63,6 @@ class CalibrationManager: ObservableObject {
calibrationData = CalibrationData() calibrationData = CalibrationData()
} }
/// Reset state for a new calibration attempt (clears isComplete flag from previous calibration)
func resetForNewCalibration() { func resetForNewCalibration() {
print("🔄 Resetting for new calibration...") print("🔄 Resetting for new calibration...")
calibrationData = CalibrationData() calibrationData = CalibrationData()
@@ -94,15 +84,14 @@ class CalibrationManager: ObservableObject {
) { ) {
guard isCalibrating, isCollectingSamples, let step = currentStep else { return } guard isCalibrating, isCollectingSamples, let step = currentStep else { return }
sampleCollector.addSample( let sample = GazeSample(
to: &calibrationData,
step: step,
leftRatio: leftRatio, leftRatio: leftRatio,
rightRatio: rightRatio, rightRatio: rightRatio,
leftVertical: leftVertical, leftVerticalRatio: leftVertical,
rightVertical: rightVertical, rightVerticalRatio: rightVertical,
faceWidthRatio: faceWidthRatio faceWidthRatio: faceWidthRatio
) )
calibrationData.addSample(sample, for: step)
if flowController.markSampleCollected() { if flowController.markSampleCollected() {
advanceToNextStep() advanceToNextStep()
@@ -118,13 +107,48 @@ class CalibrationManager: ObservableObject {
} }
func skipStep() { func skipStep() {
// Allow skipping optional steps (up, down, diagonals)
guard isCalibrating, let step = currentStep else { return } guard isCalibrating, let step = currentStep else { return }
print("⏭️ Skipping calibration step: \(step.displayName)") print("⏭️ Skipping calibration step: \(step.displayName)")
advanceToNextStep() advanceToNextStep()
} }
func showCalibrationOverlay() {
guard let screen = NSScreen.main else { return }
let window = KeyableWindow(
contentRect: screen.frame,
styleMask: [.borderless, .fullSizeContentView],
backing: .buffered,
defer: false
)
window.level = .screenSaver
window.isOpaque = true
window.backgroundColor = .black
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.acceptsMouseMovedEvents = true
window.ignoresMouseEvents = false
let overlayView = CalibrationOverlayView {
self.dismissCalibrationOverlay()
}
window.contentView = NSHostingView(rootView: overlayView)
windowController = NSWindowController(window: window)
windowController?.showWindow(nil)
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
print("🎯 Calibration overlay window opened")
}
func dismissCalibrationOverlay() {
windowController?.close()
windowController = nil
print("🎯 Calibration overlay window closed")
}
func finishCalibration() { func finishCalibration() {
print("✓ Calibration complete, calculating thresholds...") print("✓ Calibration complete, calculating thresholds...")
@@ -150,8 +174,6 @@ class CalibrationManager: ObservableObject {
CalibrationState.shared.reset() CalibrationState.shared.reset()
} }
// MARK: - Persistence
private func saveCalibration() { private func saveCalibration() {
do { do {
let encoder = JSONEncoder() let encoder = JSONEncoder()
@@ -189,14 +211,10 @@ class CalibrationManager: ObservableObject {
func clearCalibration() { func clearCalibration() {
UserDefaults.standard.removeObject(forKey: userDefaultsKey) UserDefaults.standard.removeObject(forKey: userDefaultsKey)
calibrationData = CalibrationData() calibrationData = CalibrationData()
CalibrationState.shared.reset() CalibrationState.shared.reset()
print("🗑️ Calibration data cleared") print("🗑️ Calibration data cleared")
} }
// MARK: - Validation
func isCalibrationValid() -> Bool { func isCalibrationValid() -> Bool {
guard calibrationData.isComplete, guard calibrationData.isComplete,
let thresholds = calibrationData.computedThresholds, let thresholds = calibrationData.computedThresholds,
@@ -210,15 +228,12 @@ class CalibrationManager: ObservableObject {
return !isCalibrationValid() return !isCalibrationValid()
} }
// MARK: - Apply Calibration
private func applyCalibration() { private func applyCalibration() {
guard let thresholds = calibrationData.computedThresholds else { guard let thresholds = calibrationData.computedThresholds else {
print("⚠️ No thresholds to apply") print("⚠️ No thresholds to apply")
return return
} }
// Push to thread-safe state for background processing
CalibrationState.shared.setThresholds(thresholds) CalibrationState.shared.setThresholds(thresholds)
CalibrationState.shared.setComplete(true) CalibrationState.shared.setComplete(true)
@@ -230,8 +245,6 @@ class CalibrationManager: ObservableObject {
print(" Screen Bounds: [\(String(format: "%.2f", thresholds.screenRightBound))..\(String(format: "%.2f", thresholds.screenLeftBound))] x [\(String(format: "%.2f", thresholds.screenTopBound))..\(String(format: "%.2f", thresholds.screenBottomBound))]") print(" Screen Bounds: [\(String(format: "%.2f", thresholds.screenRightBound))..\(String(format: "%.2f", thresholds.screenLeftBound))] x [\(String(format: "%.2f", thresholds.screenTopBound))..\(String(format: "%.2f", thresholds.screenBottomBound))]")
} }
// MARK: - Statistics
func getCalibrationSummary() -> String { func getCalibrationSummary() -> String {
guard calibrationData.isComplete else { guard calibrationData.isComplete else {
return "No calibration data" return "No calibration data"
@@ -252,8 +265,6 @@ class CalibrationManager: ObservableObject {
return summary return summary
} }
// MARK: - Progress
var progress: Double { var progress: Double {
flowController.progress flowController.progress
} }
@@ -261,4 +272,22 @@ class CalibrationManager: ObservableObject {
var progressText: String { var progressText: String {
flowController.progressText flowController.progressText
} }
func submitSampleToBridge(
leftRatio: Double,
rightRatio: Double,
leftVertical: Double? = nil,
rightVertical: Double? = nil,
faceWidthRatio: Double = 0
) {
Task { @MainActor in
collectSample(
leftRatio: leftRatio,
rightRatio: rightRatio,
leftVertical: leftVertical,
rightVertical: rightVertical,
faceWidthRatio: faceWidthRatio
)
}
}
} }

View File

@@ -1,36 +0,0 @@
//
// CalibrationBridge.swift
// Gaze
//
// Thread-safe calibration access for eye tracking.
//
import Foundation
final class CalibrationBridge: @unchecked Sendable {
nonisolated var thresholds: GazeThresholds? {
CalibrationState.shared.thresholds
}
nonisolated var isComplete: Bool {
CalibrationState.shared.isComplete
}
nonisolated func submitSample(
leftRatio: Double,
rightRatio: Double,
leftVertical: Double?,
rightVertical: Double?,
faceWidthRatio: Double
) {
Task { @MainActor in
CalibrationManager.shared.collectSample(
leftRatio: leftRatio,
rightRatio: rightRatio,
leftVertical: leftVertical,
rightVertical: rightVertical,
faceWidthRatio: faceWidthRatio
)
}
}
}

View File

@@ -44,7 +44,6 @@ class EyeTrackingService: NSObject, ObservableObject {
private let cameraManager = CameraSessionManager() private let cameraManager = CameraSessionManager()
private let visionPipeline = VisionPipeline() private let visionPipeline = VisionPipeline()
private let debugAdapter = EyeDebugStateAdapter() private let debugAdapter = EyeDebugStateAdapter()
private let calibrationBridge = CalibrationBridge()
private let gazeDetector: GazeDetector private let gazeDetector: GazeDetector
var previewLayer: AVCaptureVideoPreviewLayer? { var previewLayer: AVCaptureVideoPreviewLayer? {
@@ -135,10 +134,11 @@ class EyeTrackingService: NSObject, ObservableObject {
debugImageSize = debugAdapter.imageSize debugImageSize = debugAdapter.imageSize
} }
nonisolated private func updateGazeConfiguration() { @MainActor
private func updateGazeConfiguration() {
let configuration = GazeDetector.Configuration( let configuration = GazeDetector.Configuration(
thresholds: calibrationBridge.thresholds, thresholds: CalibrationState.shared.thresholds,
isCalibrationComplete: calibrationBridge.isComplete, isCalibrationComplete: CalibratorService.shared.isCalibrating || CalibrationState.shared.isComplete,
eyeClosedEnabled: EyeTrackingConstants.eyeClosedEnabled, eyeClosedEnabled: EyeTrackingConstants.eyeClosedEnabled,
eyeClosedThreshold: EyeTrackingConstants.eyeClosedThreshold, eyeClosedThreshold: EyeTrackingConstants.eyeClosedThreshold,
yawEnabled: EyeTrackingConstants.yawEnabled, yawEnabled: EyeTrackingConstants.yawEnabled,
@@ -173,8 +173,8 @@ extension EyeTrackingService: CameraSessionDelegate {
let rightRatio = result.rightPupilRatio, let rightRatio = result.rightPupilRatio,
let faceWidth = result.faceWidthRatio { let faceWidth = result.faceWidthRatio {
Task { @MainActor in Task { @MainActor in
guard CalibrationManager.shared.isCalibrating else { return } guard CalibratorService.shared.isCalibrating else { return }
calibrationBridge.submitSample( CalibratorService.shared.submitSampleToBridge(
leftRatio: leftRatio, leftRatio: leftRatio,
rightRatio: rightRatio, rightRatio: rightRatio,
leftVertical: result.leftVerticalRatio, leftVertical: result.leftVerticalRatio,

View File

@@ -10,7 +10,7 @@ import Combine
import SwiftUI import SwiftUI
struct CalibrationOverlayView: View { struct CalibrationOverlayView: View {
@StateObject private var calibrationManager = CalibrationManager.shared @StateObject private var calibratorService = CalibratorService.shared
@StateObject private var eyeTrackingService = EyeTrackingService.shared @StateObject private var eyeTrackingService = EyeTrackingService.shared
@StateObject private var viewModel = CalibrationOverlayViewModel() @StateObject private var viewModel = CalibrationOverlayViewModel()
@@ -33,10 +33,10 @@ struct CalibrationOverlayView: View {
errorView(error) errorView(error)
} else if !viewModel.cameraStarted { } else if !viewModel.cameraStarted {
startingCameraView startingCameraView
} else if calibrationManager.isCalibrating { } else if calibratorService.isCalibrating {
calibrationContentView(screenSize: geometry.size) calibrationContentView(screenSize: geometry.size)
} else if viewModel.calibrationStarted } else if viewModel.calibrationStarted
&& calibrationManager.calibrationData.isComplete && calibratorService.calibrationData.isComplete
{ {
// Only show completion if we started calibration this session AND it completed // Only show completion if we started calibration this session AND it completed
completionView completionView
@@ -48,15 +48,15 @@ struct CalibrationOverlayView: View {
} }
.task { .task {
await viewModel.startCamera( await viewModel.startCamera(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) eyeTrackingService: eyeTrackingService, calibratorService: calibratorService)
} }
.onDisappear { .onDisappear {
viewModel.cleanup( viewModel.cleanup(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) eyeTrackingService: eyeTrackingService, calibratorService: calibratorService)
} }
.onChange(of: calibrationManager.currentStep) { oldStep, newStep in .onChange(of: calibratorService.currentStep) { oldStep, newStep in
if newStep != nil && oldStep != newStep { if newStep != nil && oldStep != newStep {
viewModel.startStepCountdown(calibrationManager: calibrationManager) viewModel.startStepCountdown(calibratorService: calibratorService)
} }
} }
} }
@@ -110,7 +110,7 @@ struct CalibrationOverlayView: View {
Spacer() Spacer()
} }
if let step = calibrationManager.currentStep { if let step = calibratorService.currentStep {
calibrationTarget(for: step, screenSize: screenSize) calibrationTarget(for: step, screenSize: screenSize)
} }
@@ -119,7 +119,7 @@ struct CalibrationOverlayView: View {
HStack { HStack {
cancelButton cancelButton
Spacer() Spacer()
if !calibrationManager.isCollectingSamples { if !calibratorService.isCollectingSamples {
skipButton skipButton
} }
} }
@@ -146,11 +146,11 @@ struct CalibrationOverlayView: View {
Text("Calibrating...") Text("Calibrating...")
.foregroundStyle(.white) .foregroundStyle(.white)
Spacer() Spacer()
Text(calibrationManager.progressText) Text(calibratorService.progressText)
.foregroundStyle(.white.opacity(0.7)) .foregroundStyle(.white.opacity(0.7))
} }
ProgressView(value: calibrationManager.progress) ProgressView(value: calibratorService.progress)
.progressViewStyle(.linear) .progressViewStyle(.linear)
.tint(.blue) .tint(.blue)
} }
@@ -198,29 +198,29 @@ struct CalibrationOverlayView: View {
value: viewModel.isCountingDown) value: viewModel.isCountingDown)
// Progress ring when collecting // Progress ring when collecting
if calibrationManager.isCollectingSamples { if calibratorService.isCollectingSamples {
Circle() Circle()
.trim(from: 0, to: CGFloat(calibrationManager.samplesCollected) / 30.0) .trim(from: 0, to: CGFloat(calibratorService.samplesCollected) / 30.0)
.stroke(Color.green, lineWidth: 4) .stroke(Color.green, lineWidth: 4)
.frame(width: 90, height: 90) .frame(width: 90, height: 90)
.rotationEffect(.degrees(-90)) .rotationEffect(.degrees(-90))
.animation( .animation(
.linear(duration: 0.1), value: calibrationManager.samplesCollected) .linear(duration: 0.1), value: calibratorService.samplesCollected)
} }
// Inner circle // Inner circle
Circle() Circle()
.fill(calibrationManager.isCollectingSamples ? Color.green : Color.blue) .fill(calibratorService.isCollectingSamples ? Color.green : Color.blue)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.animation( .animation(
.easeInOut(duration: 0.3), value: calibrationManager.isCollectingSamples) .easeInOut(duration: 0.3), value: calibratorService.isCollectingSamples)
// Countdown number or collecting indicator // Countdown number or collecting indicator
if viewModel.isCountingDown && viewModel.countdownValue > 0 { if viewModel.isCountingDown && viewModel.countdownValue > 0 {
Text("\(viewModel.countdownValue)") Text("\(viewModel.countdownValue)")
.font(.system(size: 36, weight: .bold)) .font(.system(size: 36, weight: .bold))
.foregroundStyle(.white) .foregroundStyle(.white)
} else if calibrationManager.isCollectingSamples { } else if calibratorService.isCollectingSamples {
Image(systemName: "eye.fill") Image(systemName: "eye.fill")
.font(.system(size: 24, weight: .bold)) .font(.system(size: 24, weight: .bold))
.foregroundStyle(.white) .foregroundStyle(.white)
@@ -241,7 +241,7 @@ struct CalibrationOverlayView: View {
private func instructionText(for step: CalibrationStep) -> String { private func instructionText(for step: CalibrationStep) -> String {
if viewModel.isCountingDown && viewModel.countdownValue > 0 { if viewModel.isCountingDown && viewModel.countdownValue > 0 {
return "Get ready..." return "Get ready..."
} else if calibrationManager.isCollectingSamples { } else if calibratorService.isCollectingSamples {
return "Look at the target" return "Look at the target"
} else { } else {
return step.instructionText return step.instructionText
@@ -252,7 +252,7 @@ struct CalibrationOverlayView: View {
private var skipButton: some View { private var skipButton: some View {
Button { Button {
viewModel.skipCurrentStep(calibrationManager: calibrationManager) viewModel.skipCurrentStep(calibratorService: calibratorService)
} label: { } label: {
Text("Skip") Text("Skip")
.foregroundStyle(.white) .foregroundStyle(.white)
@@ -267,7 +267,7 @@ struct CalibrationOverlayView: View {
private var cancelButton: some View { private var cancelButton: some View {
Button { Button {
viewModel.cleanup( viewModel.cleanup(
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager) eyeTrackingService: eyeTrackingService, calibratorService: calibratorService)
onDismiss() onDismiss()
} label: { } label: {
HStack(spacing: 6) { HStack(spacing: 6) {
@@ -369,7 +369,7 @@ class CalibrationOverlayViewModel: ObservableObject {
private var lastFaceDetectedTime: Date = .distantPast private var lastFaceDetectedTime: Date = .distantPast
private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce
func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager) func startCamera(eyeTrackingService: EyeTrackingService, calibratorService: CalibratorService)
async async
{ {
do { do {
@@ -382,10 +382,10 @@ class CalibrationOverlayViewModel: ObservableObject {
try? await Task.sleep(for: .seconds(0.5)) try? await Task.sleep(for: .seconds(0.5))
// Reset any previous calibration data before starting fresh // Reset any previous calibration data before starting fresh
calibrationManager.resetForNewCalibration() calibratorService.resetForNewCalibration()
calibrationManager.startCalibration() calibratorService.startCalibration()
calibrationStarted = true calibrationStarted = true
startStepCountdown(calibrationManager: calibrationManager) startStepCountdown(calibratorService: calibratorService)
} catch { } catch {
showError = "Failed to start camera: \(error.localizedDescription)" showError = "Failed to start camera: \(error.localizedDescription)"
} }
@@ -411,28 +411,28 @@ class CalibrationOverlayViewModel: ObservableObject {
} }
} }
func cleanup(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager) { func cleanup(eyeTrackingService: EyeTrackingService, calibratorService: CalibratorService) {
countdownTask?.cancel() countdownTask?.cancel()
countdownTask = nil countdownTask = nil
faceDetectionCancellable?.cancel() faceDetectionCancellable?.cancel()
faceDetectionCancellable = nil faceDetectionCancellable = nil
isCountingDown = false isCountingDown = false
if calibrationManager.isCalibrating { if calibratorService.isCalibrating {
calibrationManager.cancelCalibration() calibratorService.cancelCalibration()
} }
eyeTrackingService.stopEyeTracking() eyeTrackingService.stopEyeTracking()
} }
func skipCurrentStep(calibrationManager: CalibrationManager) { func skipCurrentStep(calibratorService: CalibratorService) {
countdownTask?.cancel() countdownTask?.cancel()
countdownTask = nil countdownTask = nil
isCountingDown = false isCountingDown = false
calibrationManager.skipStep() calibratorService.skipStep()
} }
func startStepCountdown(calibrationManager: CalibrationManager) { func startStepCountdown(calibratorService: CalibratorService) {
countdownTask?.cancel() countdownTask?.cancel()
countdownTask = nil countdownTask = nil
countdownValue = 1 countdownValue = 1
@@ -446,7 +446,7 @@ class CalibrationOverlayViewModel: ObservableObject {
// Done counting, start collecting // Done counting, start collecting
isCountingDown = false isCountingDown = false
countdownValue = 0 countdownValue = 0
calibrationManager.startCollectingSamples() calibratorService.startCollectingSamples()
} }
} }
} }

View File

@@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
struct EyeTrackingCalibrationView: View { struct EyeTrackingCalibrationView: View {
@StateObject private var calibrationManager = CalibrationManager.shared @StateObject private var calibratorService = CalibratorService.shared
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
var body: some View { var body: some View {
@@ -46,12 +46,12 @@ struct EyeTrackingCalibrationView: View {
} }
.padding(.vertical, 20) .padding(.vertical, 20)
if calibrationManager.calibrationData.isComplete { if calibratorService.calibrationData.isComplete {
VStack(spacing: 10) { VStack(spacing: 10) {
Text("Last calibration:") Text("Last calibration:")
.font(.caption) .font(.caption)
.foregroundStyle(.gray) .foregroundStyle(.gray)
Text(calibrationManager.getCalibrationSummary()) Text(calibratorService.getCalibrationSummary())
.font(.caption) .font(.caption)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundStyle(.gray) .foregroundStyle(.gray)
@@ -86,7 +86,7 @@ struct EyeTrackingCalibrationView: View {
// Small delay to allow sheet dismissal animation // Small delay to allow sheet dismissal animation
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
CalibrationWindowManager.shared.showCalibrationOverlay() CalibratorService.shared.showCalibrationOverlay()
} }
} }
} }

View File

@@ -23,7 +23,7 @@ struct EnforceModeSetupView: View {
@State private var isViewActive = false @State private var isViewActive = false
@State private var showAdvancedSettings = false @State private var showAdvancedSettings = false
@State private var showCalibrationWindow = false @State private var showCalibrationWindow = false
@ObservedObject var calibrationManager = CalibrationManager.shared @ObservedObject var calibratorService = CalibratorService.shared
private var cameraHardwareAvailable: Bool { private var cameraHardwareAvailable: Bool {
cameraService.hasCameraHardware cameraService.hasCameraHardware
@@ -155,13 +155,13 @@ struct EnforceModeSetupView: View {
.font(.headline) .font(.headline)
} }
if calibrationManager.calibrationData.isComplete { if calibratorService.calibrationData.isComplete {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text(calibrationManager.getCalibrationSummary()) Text(calibratorService.getCalibrationSummary())
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
if calibrationManager.needsRecalibration() { if calibratorService.needsRecalibration() {
Label( Label(
"Calibration expired - recalibration recommended", "Calibration expired - recalibration recommended",
systemImage: "exclamationmark.triangle.fill" systemImage: "exclamationmark.triangle.fill"
@@ -186,7 +186,7 @@ struct EnforceModeSetupView: View {
HStack { HStack {
Image(systemName: "target") Image(systemName: "target")
Text( Text(
calibrationManager.calibrationData.isComplete calibratorService.calibrationData.isComplete
? "Recalibrate" : "Run Calibration") ? "Recalibrate" : "Run Calibration")
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)