task 04
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,45 @@
|
||||
//
|
||||
// CalibrationManager.swift
|
||||
// CalibratorService.swift
|
||||
// Gaze
|
||||
//
|
||||
// Created by Mike Freno on 1/15/26.
|
||||
// Created by Mike Freno on 1/29/26.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
class CalibrationManager: ObservableObject {
|
||||
static let shared = CalibrationManager()
|
||||
|
||||
// MARK: - Published Properties
|
||||
|
||||
final class CalibratorService: ObservableObject {
|
||||
static let shared = CalibratorService()
|
||||
|
||||
@Published var isCalibrating = false
|
||||
@Published var isCollectingSamples = false // True when actively collecting (after countdown)
|
||||
@Published var isCollectingSamples = false
|
||||
@Published var currentStep: CalibrationStep?
|
||||
@Published var currentStepIndex = 0
|
||||
@Published var samplesCollected = 0
|
||||
@Published var calibrationData = CalibrationData()
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
private let samplesPerStep = 30 // Collect 30 samples per calibration point (~1 second at 30fps)
|
||||
private let samplesPerStep = 30
|
||||
private let userDefaultsKey = "eyeTrackingCalibration"
|
||||
|
||||
private let calibrationSteps: [CalibrationStep] = [
|
||||
.center,
|
||||
.left,
|
||||
.right,
|
||||
.farLeft,
|
||||
.farRight,
|
||||
.up,
|
||||
.down,
|
||||
.topLeft,
|
||||
.topRight
|
||||
]
|
||||
|
||||
private let flowController: CalibrationFlowController
|
||||
private var sampleCollector = CalibrationSampleCollector()
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
private var windowController: NSWindowController?
|
||||
|
||||
private init() {
|
||||
self.flowController = CalibrationFlowController(
|
||||
samplesPerStep: samplesPerStep,
|
||||
calibrationSteps: calibrationSteps
|
||||
calibrationSteps: [
|
||||
.center,
|
||||
.left,
|
||||
.right,
|
||||
.farLeft,
|
||||
.farRight,
|
||||
.up,
|
||||
.down,
|
||||
.topLeft,
|
||||
.topRight
|
||||
]
|
||||
)
|
||||
loadCalibration()
|
||||
bindFlowController()
|
||||
@@ -62,29 +55,26 @@ class CalibrationManager: ObservableObject {
|
||||
flowController.$samplesCollected
|
||||
.assign(to: &$samplesCollected)
|
||||
}
|
||||
|
||||
// MARK: - Calibration Flow
|
||||
|
||||
|
||||
func startCalibration() {
|
||||
print("🎯 Starting calibration...")
|
||||
isCalibrating = true
|
||||
flowController.start()
|
||||
calibrationData = CalibrationData()
|
||||
}
|
||||
|
||||
/// Reset state for a new calibration attempt (clears isComplete flag from previous calibration)
|
||||
|
||||
func resetForNewCalibration() {
|
||||
print("🔄 Resetting for new calibration...")
|
||||
calibrationData = CalibrationData()
|
||||
flowController.start()
|
||||
}
|
||||
|
||||
|
||||
func startCollectingSamples() {
|
||||
guard isCalibrating else { return }
|
||||
print("📊 Started collecting samples for step: \(currentStep?.displayName ?? "unknown")")
|
||||
flowController.startCollectingSamples()
|
||||
}
|
||||
|
||||
|
||||
func collectSample(
|
||||
leftRatio: Double?,
|
||||
rightRatio: Double?,
|
||||
@@ -94,21 +84,20 @@ class CalibrationManager: ObservableObject {
|
||||
) {
|
||||
guard isCalibrating, isCollectingSamples, let step = currentStep else { return }
|
||||
|
||||
sampleCollector.addSample(
|
||||
to: &calibrationData,
|
||||
step: step,
|
||||
let sample = GazeSample(
|
||||
leftRatio: leftRatio,
|
||||
rightRatio: rightRatio,
|
||||
leftVertical: leftVertical,
|
||||
rightVertical: rightVertical,
|
||||
leftVerticalRatio: leftVertical,
|
||||
rightVerticalRatio: rightVertical,
|
||||
faceWidthRatio: faceWidthRatio
|
||||
)
|
||||
calibrationData.addSample(sample, for: step)
|
||||
|
||||
if flowController.markSampleCollected() {
|
||||
advanceToNextStep()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func advanceToNextStep() {
|
||||
if flowController.advanceToNextStep() {
|
||||
print("📍 Calibration step: \(currentStep?.displayName ?? "unknown")")
|
||||
@@ -116,42 +105,75 @@ class CalibrationManager: ObservableObject {
|
||||
finishCalibration()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func skipStep() {
|
||||
// Allow skipping optional steps (up, down, diagonals)
|
||||
guard isCalibrating, let step = currentStep else { return }
|
||||
|
||||
|
||||
print("⏭️ Skipping calibration step: \(step.displayName)")
|
||||
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() {
|
||||
print("✓ Calibration complete, calculating thresholds...")
|
||||
|
||||
|
||||
calibrationData.calculateThresholds()
|
||||
calibrationData.isComplete = true
|
||||
calibrationData.calibrationDate = Date()
|
||||
|
||||
|
||||
saveCalibration()
|
||||
applyCalibration()
|
||||
|
||||
|
||||
isCalibrating = false
|
||||
flowController.stop()
|
||||
|
||||
|
||||
print("✓ Calibration saved and applied")
|
||||
}
|
||||
|
||||
|
||||
func cancelCalibration() {
|
||||
print("❌ Calibration cancelled")
|
||||
isCalibrating = false
|
||||
flowController.stop()
|
||||
calibrationData = CalibrationData()
|
||||
|
||||
|
||||
CalibrationState.shared.reset()
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
|
||||
|
||||
private func saveCalibration() {
|
||||
do {
|
||||
let encoder = JSONEncoder()
|
||||
@@ -163,18 +185,18 @@ class CalibrationManager: ObservableObject {
|
||||
print("❌ Failed to save calibration: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func loadCalibration() {
|
||||
guard let data = UserDefaults.standard.data(forKey: userDefaultsKey) else {
|
||||
print("ℹ️ No existing calibration found")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
calibrationData = try decoder.decode(CalibrationData.self, from: data)
|
||||
|
||||
|
||||
if isCalibrationValid() {
|
||||
print("✓ Loaded valid calibration from \(calibrationData.calibrationDate)")
|
||||
applyCalibration()
|
||||
@@ -185,18 +207,14 @@ class CalibrationManager: ObservableObject {
|
||||
print("❌ Failed to load calibration: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func clearCalibration() {
|
||||
UserDefaults.standard.removeObject(forKey: userDefaultsKey)
|
||||
calibrationData = CalibrationData()
|
||||
|
||||
CalibrationState.shared.reset()
|
||||
|
||||
print("🗑️ Calibration data cleared")
|
||||
}
|
||||
|
||||
// MARK: - Validation
|
||||
|
||||
|
||||
func isCalibrationValid() -> Bool {
|
||||
guard calibrationData.isComplete,
|
||||
let thresholds = calibrationData.computedThresholds,
|
||||
@@ -205,23 +223,20 @@ class CalibrationManager: ObservableObject {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func needsRecalibration() -> Bool {
|
||||
return !isCalibrationValid()
|
||||
}
|
||||
|
||||
// MARK: - Apply Calibration
|
||||
|
||||
|
||||
private func applyCalibration() {
|
||||
guard let thresholds = calibrationData.computedThresholds else {
|
||||
print("⚠️ No thresholds to apply")
|
||||
return
|
||||
}
|
||||
|
||||
// Push to thread-safe state for background processing
|
||||
|
||||
CalibrationState.shared.setThresholds(thresholds)
|
||||
CalibrationState.shared.setComplete(true)
|
||||
|
||||
|
||||
print("✓ Applied calibrated thresholds:")
|
||||
print(" Looking left: ≥\(String(format: "%.3f", thresholds.minLeftRatio))")
|
||||
print(" Looking right: ≤\(String(format: "%.3f", thresholds.maxRightRatio))")
|
||||
@@ -229,36 +244,50 @@ class CalibrationManager: ObservableObject {
|
||||
print(" Looking down: ≥\(String(format: "%.3f", thresholds.maxDownRatio))")
|
||||
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 {
|
||||
guard calibrationData.isComplete else {
|
||||
return "No calibration data"
|
||||
}
|
||||
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
|
||||
var summary = "Calibrated: \(dateFormatter.string(from: calibrationData.calibrationDate))\n"
|
||||
|
||||
|
||||
if let thresholds = calibrationData.computedThresholds {
|
||||
summary += "H-Range: \(String(format: "%.3f", thresholds.screenRightBound)) to \(String(format: "%.3f", thresholds.screenLeftBound))\n"
|
||||
summary += "V-Range: \(String(format: "%.3f", thresholds.screenTopBound)) to \(String(format: "%.3f", thresholds.screenBottomBound))\n"
|
||||
summary += "Ref Face Width: \(String(format: "%.3f", thresholds.referenceFaceWidth))"
|
||||
}
|
||||
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// MARK: - Progress
|
||||
|
||||
|
||||
var progress: Double {
|
||||
flowController.progress
|
||||
}
|
||||
|
||||
|
||||
var progressText: String {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@ class EyeTrackingService: NSObject, ObservableObject {
|
||||
private let cameraManager = CameraSessionManager()
|
||||
private let visionPipeline = VisionPipeline()
|
||||
private let debugAdapter = EyeDebugStateAdapter()
|
||||
private let calibrationBridge = CalibrationBridge()
|
||||
private let gazeDetector: GazeDetector
|
||||
|
||||
var previewLayer: AVCaptureVideoPreviewLayer? {
|
||||
@@ -135,10 +134,11 @@ class EyeTrackingService: NSObject, ObservableObject {
|
||||
debugImageSize = debugAdapter.imageSize
|
||||
}
|
||||
|
||||
nonisolated private func updateGazeConfiguration() {
|
||||
@MainActor
|
||||
private func updateGazeConfiguration() {
|
||||
let configuration = GazeDetector.Configuration(
|
||||
thresholds: calibrationBridge.thresholds,
|
||||
isCalibrationComplete: calibrationBridge.isComplete,
|
||||
thresholds: CalibrationState.shared.thresholds,
|
||||
isCalibrationComplete: CalibratorService.shared.isCalibrating || CalibrationState.shared.isComplete,
|
||||
eyeClosedEnabled: EyeTrackingConstants.eyeClosedEnabled,
|
||||
eyeClosedThreshold: EyeTrackingConstants.eyeClosedThreshold,
|
||||
yawEnabled: EyeTrackingConstants.yawEnabled,
|
||||
@@ -173,8 +173,8 @@ extension EyeTrackingService: CameraSessionDelegate {
|
||||
let rightRatio = result.rightPupilRatio,
|
||||
let faceWidth = result.faceWidthRatio {
|
||||
Task { @MainActor in
|
||||
guard CalibrationManager.shared.isCalibrating else { return }
|
||||
calibrationBridge.submitSample(
|
||||
guard CalibratorService.shared.isCalibrating else { return }
|
||||
CalibratorService.shared.submitSampleToBridge(
|
||||
leftRatio: leftRatio,
|
||||
rightRatio: rightRatio,
|
||||
leftVertical: result.leftVerticalRatio,
|
||||
|
||||
@@ -10,7 +10,7 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct CalibrationOverlayView: View {
|
||||
@StateObject private var calibrationManager = CalibrationManager.shared
|
||||
@StateObject private var calibratorService = CalibratorService.shared
|
||||
@StateObject private var eyeTrackingService = EyeTrackingService.shared
|
||||
@StateObject private var viewModel = CalibrationOverlayViewModel()
|
||||
|
||||
@@ -33,10 +33,10 @@ struct CalibrationOverlayView: View {
|
||||
errorView(error)
|
||||
} else if !viewModel.cameraStarted {
|
||||
startingCameraView
|
||||
} else if calibrationManager.isCalibrating {
|
||||
} else if calibratorService.isCalibrating {
|
||||
calibrationContentView(screenSize: geometry.size)
|
||||
} else if viewModel.calibrationStarted
|
||||
&& calibrationManager.calibrationData.isComplete
|
||||
&& calibratorService.calibrationData.isComplete
|
||||
{
|
||||
// Only show completion if we started calibration this session AND it completed
|
||||
completionView
|
||||
@@ -48,15 +48,15 @@ struct CalibrationOverlayView: View {
|
||||
}
|
||||
.task {
|
||||
await viewModel.startCamera(
|
||||
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
eyeTrackingService: eyeTrackingService, calibratorService: calibratorService)
|
||||
}
|
||||
.onDisappear {
|
||||
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 {
|
||||
viewModel.startStepCountdown(calibrationManager: calibrationManager)
|
||||
viewModel.startStepCountdown(calibratorService: calibratorService)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ struct CalibrationOverlayView: View {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if let step = calibrationManager.currentStep {
|
||||
if let step = calibratorService.currentStep {
|
||||
calibrationTarget(for: step, screenSize: screenSize)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ struct CalibrationOverlayView: View {
|
||||
HStack {
|
||||
cancelButton
|
||||
Spacer()
|
||||
if !calibrationManager.isCollectingSamples {
|
||||
if !calibratorService.isCollectingSamples {
|
||||
skipButton
|
||||
}
|
||||
}
|
||||
@@ -146,11 +146,11 @@ struct CalibrationOverlayView: View {
|
||||
Text("Calibrating...")
|
||||
.foregroundStyle(.white)
|
||||
Spacer()
|
||||
Text(calibrationManager.progressText)
|
||||
Text(calibratorService.progressText)
|
||||
.foregroundStyle(.white.opacity(0.7))
|
||||
}
|
||||
|
||||
ProgressView(value: calibrationManager.progress)
|
||||
ProgressView(value: calibratorService.progress)
|
||||
.progressViewStyle(.linear)
|
||||
.tint(.blue)
|
||||
}
|
||||
@@ -198,29 +198,29 @@ struct CalibrationOverlayView: View {
|
||||
value: viewModel.isCountingDown)
|
||||
|
||||
// Progress ring when collecting
|
||||
if calibrationManager.isCollectingSamples {
|
||||
if calibratorService.isCollectingSamples {
|
||||
Circle()
|
||||
.trim(from: 0, to: CGFloat(calibrationManager.samplesCollected) / 30.0)
|
||||
.trim(from: 0, to: CGFloat(calibratorService.samplesCollected) / 30.0)
|
||||
.stroke(Color.green, lineWidth: 4)
|
||||
.frame(width: 90, height: 90)
|
||||
.rotationEffect(.degrees(-90))
|
||||
.animation(
|
||||
.linear(duration: 0.1), value: calibrationManager.samplesCollected)
|
||||
.linear(duration: 0.1), value: calibratorService.samplesCollected)
|
||||
}
|
||||
|
||||
// Inner circle
|
||||
Circle()
|
||||
.fill(calibrationManager.isCollectingSamples ? Color.green : Color.blue)
|
||||
.fill(calibratorService.isCollectingSamples ? Color.green : Color.blue)
|
||||
.frame(width: 60, height: 60)
|
||||
.animation(
|
||||
.easeInOut(duration: 0.3), value: calibrationManager.isCollectingSamples)
|
||||
.easeInOut(duration: 0.3), value: calibratorService.isCollectingSamples)
|
||||
|
||||
// Countdown number or collecting indicator
|
||||
if viewModel.isCountingDown && viewModel.countdownValue > 0 {
|
||||
Text("\(viewModel.countdownValue)")
|
||||
.font(.system(size: 36, weight: .bold))
|
||||
.foregroundStyle(.white)
|
||||
} else if calibrationManager.isCollectingSamples {
|
||||
} else if calibratorService.isCollectingSamples {
|
||||
Image(systemName: "eye.fill")
|
||||
.font(.system(size: 24, weight: .bold))
|
||||
.foregroundStyle(.white)
|
||||
@@ -241,7 +241,7 @@ struct CalibrationOverlayView: View {
|
||||
private func instructionText(for step: CalibrationStep) -> String {
|
||||
if viewModel.isCountingDown && viewModel.countdownValue > 0 {
|
||||
return "Get ready..."
|
||||
} else if calibrationManager.isCollectingSamples {
|
||||
} else if calibratorService.isCollectingSamples {
|
||||
return "Look at the target"
|
||||
} else {
|
||||
return step.instructionText
|
||||
@@ -252,7 +252,7 @@ struct CalibrationOverlayView: View {
|
||||
|
||||
private var skipButton: some View {
|
||||
Button {
|
||||
viewModel.skipCurrentStep(calibrationManager: calibrationManager)
|
||||
viewModel.skipCurrentStep(calibratorService: calibratorService)
|
||||
} label: {
|
||||
Text("Skip")
|
||||
.foregroundStyle(.white)
|
||||
@@ -267,7 +267,7 @@ struct CalibrationOverlayView: View {
|
||||
private var cancelButton: some View {
|
||||
Button {
|
||||
viewModel.cleanup(
|
||||
eyeTrackingService: eyeTrackingService, calibrationManager: calibrationManager)
|
||||
eyeTrackingService: eyeTrackingService, calibratorService: calibratorService)
|
||||
onDismiss()
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
@@ -369,7 +369,7 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
private var lastFaceDetectedTime: Date = .distantPast
|
||||
private let faceDetectionDebounce: TimeInterval = 0.5 // 500ms debounce
|
||||
|
||||
func startCamera(eyeTrackingService: EyeTrackingService, calibrationManager: CalibrationManager)
|
||||
func startCamera(eyeTrackingService: EyeTrackingService, calibratorService: CalibratorService)
|
||||
async
|
||||
{
|
||||
do {
|
||||
@@ -382,10 +382,10 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
try? await Task.sleep(for: .seconds(0.5))
|
||||
|
||||
// Reset any previous calibration data before starting fresh
|
||||
calibrationManager.resetForNewCalibration()
|
||||
calibrationManager.startCalibration()
|
||||
calibratorService.resetForNewCalibration()
|
||||
calibratorService.startCalibration()
|
||||
calibrationStarted = true
|
||||
startStepCountdown(calibrationManager: calibrationManager)
|
||||
startStepCountdown(calibratorService: calibratorService)
|
||||
} catch {
|
||||
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 = nil
|
||||
faceDetectionCancellable?.cancel()
|
||||
faceDetectionCancellable = nil
|
||||
isCountingDown = false
|
||||
|
||||
if calibrationManager.isCalibrating {
|
||||
calibrationManager.cancelCalibration()
|
||||
if calibratorService.isCalibrating {
|
||||
calibratorService.cancelCalibration()
|
||||
}
|
||||
|
||||
eyeTrackingService.stopEyeTracking()
|
||||
}
|
||||
|
||||
func skipCurrentStep(calibrationManager: CalibrationManager) {
|
||||
func skipCurrentStep(calibratorService: CalibratorService) {
|
||||
countdownTask?.cancel()
|
||||
countdownTask = nil
|
||||
isCountingDown = false
|
||||
calibrationManager.skipStep()
|
||||
calibratorService.skipStep()
|
||||
}
|
||||
|
||||
func startStepCountdown(calibrationManager: CalibrationManager) {
|
||||
func startStepCountdown(calibratorService: CalibratorService) {
|
||||
countdownTask?.cancel()
|
||||
countdownTask = nil
|
||||
countdownValue = 1
|
||||
@@ -446,7 +446,7 @@ class CalibrationOverlayViewModel: ObservableObject {
|
||||
// Done counting, start collecting
|
||||
isCountingDown = false
|
||||
countdownValue = 0
|
||||
calibrationManager.startCollectingSamples()
|
||||
calibratorService.startCollectingSamples()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EyeTrackingCalibrationView: View {
|
||||
@StateObject private var calibrationManager = CalibrationManager.shared
|
||||
@StateObject private var calibratorService = CalibratorService.shared
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
@@ -46,12 +46,12 @@ struct EyeTrackingCalibrationView: View {
|
||||
}
|
||||
.padding(.vertical, 20)
|
||||
|
||||
if calibrationManager.calibrationData.isComplete {
|
||||
if calibratorService.calibrationData.isComplete {
|
||||
VStack(spacing: 10) {
|
||||
Text("Last calibration:")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.gray)
|
||||
Text(calibrationManager.getCalibrationSummary())
|
||||
Text(calibratorService.getCalibrationSummary())
|
||||
.font(.caption)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundStyle(.gray)
|
||||
@@ -83,10 +83,10 @@ struct EyeTrackingCalibrationView: View {
|
||||
|
||||
private func startFullscreenCalibration() {
|
||||
dismiss()
|
||||
|
||||
|
||||
// Small delay to allow sheet dismissal animation
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
CalibrationWindowManager.shared.showCalibrationOverlay()
|
||||
CalibratorService.shared.showCalibrationOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct EnforceModeSetupView: View {
|
||||
@State private var isViewActive = false
|
||||
@State private var showAdvancedSettings = false
|
||||
@State private var showCalibrationWindow = false
|
||||
@ObservedObject var calibrationManager = CalibrationManager.shared
|
||||
@ObservedObject var calibratorService = CalibratorService.shared
|
||||
|
||||
private var cameraHardwareAvailable: Bool {
|
||||
cameraService.hasCameraHardware
|
||||
@@ -155,13 +155,13 @@ struct EnforceModeSetupView: View {
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
if calibrationManager.calibrationData.isComplete {
|
||||
if calibratorService.calibrationData.isComplete {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(calibrationManager.getCalibrationSummary())
|
||||
Text(calibratorService.getCalibrationSummary())
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
if calibrationManager.needsRecalibration() {
|
||||
if calibratorService.needsRecalibration() {
|
||||
Label(
|
||||
"Calibration expired - recalibration recommended",
|
||||
systemImage: "exclamationmark.triangle.fill"
|
||||
@@ -186,7 +186,7 @@ struct EnforceModeSetupView: View {
|
||||
HStack {
|
||||
Image(systemName: "target")
|
||||
Text(
|
||||
calibrationManager.calibrationData.isComplete
|
||||
calibratorService.calibrationData.isComplete
|
||||
? "Recalibrate" : "Run Calibration")
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Reference in New Issue
Block a user