Files
Gaze/Gaze/Views/Components/EnforceModeCalibrationOverlayView.swift
Michael Freno d4adb530e0 meh
2026-02-01 02:13:32 -05:00

198 lines
6.1 KiB
Swift

//
// EnforceModeCalibrationOverlayView.swift
// Gaze
//
// Created by Mike Freno on 2/1/26.
//
import SwiftUI
struct EnforceModeCalibrationOverlayView: View {
@ObservedObject private var calibrationService = EnforceModeCalibrationService.shared
@ObservedObject private var eyeTrackingService = EyeTrackingService.shared
@Bindable private var settingsManager = SettingsManager.shared
@ObservedObject private var enforceModeService = EnforceModeService.shared
var body: some View {
ZStack {
cameraBackground
switch calibrationService.currentStep {
case .eyeBox:
eyeBoxStep
case .targets:
targetStep
case .complete:
completionStep
}
}
}
private var eyeBoxStep: some View {
ZStack {
VStack(spacing: 16) {
Text("Adjust Eye Box")
.font(.title2)
.foregroundStyle(.white)
Text(
"Use the sliders to fit the boxes around your eyes. When it looks right, continue."
)
.font(.callout)
.multilineTextAlignment(.center)
.foregroundStyle(.white.opacity(0.8))
}
.padding(.horizontal, 40)
.padding(.top, 40)
.frame(maxWidth: 520)
.frame(maxWidth: .infinity, alignment: .top)
VStack(alignment: .leading, spacing: 12) {
Text("Width")
.font(.caption)
.foregroundStyle(.white.opacity(0.8))
Slider(
value: $settingsManager.settings.enforceModeEyeBoxWidthFactor,
in: 0.12...0.25
)
Text("Height")
.font(.caption)
.foregroundStyle(.white.opacity(0.8))
Slider(
value: $settingsManager.settings.enforceModeEyeBoxHeightFactor,
in: 0.01...0.10
)
}
.padding(16)
.background(.black.opacity(0.6))
.clipShape(RoundedRectangle(cornerRadius: 12))
.frame(maxWidth: 420)
.frame(maxHeight: .infinity, alignment: .center)
VStack {
Spacer()
HStack(spacing: 12) {
Button("Cancel") {
calibrationService.dismissOverlay()
enforceModeService.stopTestMode()
}
.buttonStyle(.bordered)
Button("Continue") {
calibrationService.advance()
}
.buttonStyle(.borderedProminent)
}
}
.padding(.bottom, 40)
}
}
private var targetStep: some View {
ZStack {
VStack(spacing: 10) {
HStack {
Text("Calibrating...")
.foregroundStyle(.white)
Spacer()
Text(calibrationService.progressText)
.foregroundStyle(.white.opacity(0.7))
}
ProgressView(value: calibrationService.progress)
.progressViewStyle(.linear)
.tint(.blue)
}
.padding()
.background(Color.black.opacity(0.7))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
targetDot
VStack {
Spacer()
HStack(spacing: 12) {
Button("Cancel") {
calibrationService.dismissOverlay()
enforceModeService.stopTestMode()
}
.buttonStyle(.bordered)
}
}
.padding(.bottom, 40)
}
}
private var completionStep: some View {
VStack(spacing: 20) {
Text("Calibration Complete")
.font(.title2)
.foregroundStyle(.white)
Text("You can close this window and start testing.")
.font(.callout)
.foregroundStyle(.white.opacity(0.8))
Button("Done") {
calibrationService.dismissOverlay()
enforceModeService.stopTestMode()
}
.buttonStyle(.borderedProminent)
}
}
private var targetDot: some View {
GeometryReader { geometry in
let target = calibrationService.currentTarget()
let center = CGPoint(
x: geometry.size.width * target.x,
y: geometry.size.height * target.y
)
ZStack {
Circle()
.fill(Color.blue)
.frame(width: 120, height: 120)
Circle()
.trim(from: 0, to: CGFloat(calibrationService.countdownProgress))
.stroke(Color.blue.opacity(0.8), lineWidth: 8)
.frame(width: 160, height: 160)
.rotationEffect(.degrees(-90))
.animation(.linear(duration: 0.02), value: calibrationService.countdownProgress)
}
.position(center)
}
.ignoresSafeArea()
}
private var cameraBackground: some View {
ZStack {
if let layer = eyeTrackingService.previewLayer {
CameraPreviewView(
previewLayer: layer,
borderColor: .clear,
showsBorder: false,
cornerRadius: 0
)
.opacity(0.5)
}
if calibrationService.currentStep == .eyeBox {
GeometryReader { geometry in
EyeTrackingDebugOverlayView(
debugState: eyeTrackingService.debugState,
viewSize: geometry.size
)
.opacity(0.8)
}
}
Color.black.opacity(0.35)
.ignoresSafeArea()
}
.ignoresSafeArea()
}
}