checkpoint
This commit is contained in:
@@ -43,6 +43,9 @@ struct AppSettings: Codable, Equatable, Hashable, Sendable {
|
|||||||
|
|
||||||
var smartMode: SmartModeSettings
|
var smartMode: SmartModeSettings
|
||||||
var enforceModeStrictness: Double
|
var enforceModeStrictness: Double
|
||||||
|
var enforceModeEyeBoxWidthFactor: Double
|
||||||
|
var enforceModeEyeBoxHeightFactor: Double
|
||||||
|
var enforceModeCalibration: EnforceModeCalibration?
|
||||||
|
|
||||||
var hasCompletedOnboarding: Bool
|
var hasCompletedOnboarding: Bool
|
||||||
var launchAtLogin: Bool
|
var launchAtLogin: Bool
|
||||||
@@ -59,6 +62,9 @@ struct AppSettings: Codable, Equatable, Hashable, Sendable {
|
|||||||
subtleReminderSize: ReminderSize = DefaultSettingsBuilder.subtleReminderSize,
|
subtleReminderSize: ReminderSize = DefaultSettingsBuilder.subtleReminderSize,
|
||||||
smartMode: SmartModeSettings = DefaultSettingsBuilder.smartMode,
|
smartMode: SmartModeSettings = DefaultSettingsBuilder.smartMode,
|
||||||
enforceModeStrictness: Double = DefaultSettingsBuilder.enforceModeStrictness,
|
enforceModeStrictness: Double = DefaultSettingsBuilder.enforceModeStrictness,
|
||||||
|
enforceModeEyeBoxWidthFactor: Double = DefaultSettingsBuilder.enforceModeEyeBoxWidthFactor,
|
||||||
|
enforceModeEyeBoxHeightFactor: Double = DefaultSettingsBuilder.enforceModeEyeBoxHeightFactor,
|
||||||
|
enforceModeCalibration: EnforceModeCalibration? = DefaultSettingsBuilder.enforceModeCalibration,
|
||||||
hasCompletedOnboarding: Bool = DefaultSettingsBuilder.hasCompletedOnboarding,
|
hasCompletedOnboarding: Bool = DefaultSettingsBuilder.hasCompletedOnboarding,
|
||||||
launchAtLogin: Bool = DefaultSettingsBuilder.launchAtLogin,
|
launchAtLogin: Bool = DefaultSettingsBuilder.launchAtLogin,
|
||||||
playSounds: Bool = DefaultSettingsBuilder.playSounds
|
playSounds: Bool = DefaultSettingsBuilder.playSounds
|
||||||
@@ -73,6 +79,9 @@ struct AppSettings: Codable, Equatable, Hashable, Sendable {
|
|||||||
self.subtleReminderSize = subtleReminderSize
|
self.subtleReminderSize = subtleReminderSize
|
||||||
self.smartMode = smartMode
|
self.smartMode = smartMode
|
||||||
self.enforceModeStrictness = enforceModeStrictness
|
self.enforceModeStrictness = enforceModeStrictness
|
||||||
|
self.enforceModeEyeBoxWidthFactor = enforceModeEyeBoxWidthFactor
|
||||||
|
self.enforceModeEyeBoxHeightFactor = enforceModeEyeBoxHeightFactor
|
||||||
|
self.enforceModeCalibration = enforceModeCalibration
|
||||||
self.hasCompletedOnboarding = hasCompletedOnboarding
|
self.hasCompletedOnboarding = hasCompletedOnboarding
|
||||||
self.launchAtLogin = launchAtLogin
|
self.launchAtLogin = launchAtLogin
|
||||||
self.playSounds = playSounds
|
self.playSounds = playSounds
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ struct DefaultSettingsBuilder {
|
|||||||
static let subtleReminderSize: ReminderSize = .medium
|
static let subtleReminderSize: ReminderSize = .medium
|
||||||
static let smartMode: SmartModeSettings = .defaults
|
static let smartMode: SmartModeSettings = .defaults
|
||||||
static let enforceModeStrictness = 0.4
|
static let enforceModeStrictness = 0.4
|
||||||
|
static let enforceModeEyeBoxWidthFactor = 0.18
|
||||||
|
static let enforceModeEyeBoxHeightFactor = 0.10
|
||||||
|
static let enforceModeCalibration: EnforceModeCalibration? = nil
|
||||||
static let hasCompletedOnboarding = false
|
static let hasCompletedOnboarding = false
|
||||||
static let launchAtLogin = false
|
static let launchAtLogin = false
|
||||||
static let playSounds = true
|
static let playSounds = true
|
||||||
@@ -33,6 +36,9 @@ struct DefaultSettingsBuilder {
|
|||||||
subtleReminderSize: subtleReminderSize,
|
subtleReminderSize: subtleReminderSize,
|
||||||
smartMode: smartMode,
|
smartMode: smartMode,
|
||||||
enforceModeStrictness: enforceModeStrictness,
|
enforceModeStrictness: enforceModeStrictness,
|
||||||
|
enforceModeEyeBoxWidthFactor: enforceModeEyeBoxWidthFactor,
|
||||||
|
enforceModeEyeBoxHeightFactor: enforceModeEyeBoxHeightFactor,
|
||||||
|
enforceModeCalibration: enforceModeCalibration,
|
||||||
hasCompletedOnboarding: hasCompletedOnboarding,
|
hasCompletedOnboarding: hasCompletedOnboarding,
|
||||||
launchAtLogin: launchAtLogin,
|
launchAtLogin: launchAtLogin,
|
||||||
playSounds: playSounds
|
playSounds: playSounds
|
||||||
|
|||||||
19
Gaze/Models/EnforceModeCalibration.swift
Normal file
19
Gaze/Models/EnforceModeCalibration.swift
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// EnforceModeCalibration.swift
|
||||||
|
// Gaze
|
||||||
|
//
|
||||||
|
// Created by Mike Freno on 2/1/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct EnforceModeCalibration: Codable, Equatable, Hashable, Sendable {
|
||||||
|
let createdAt: Date
|
||||||
|
let eyeBoxWidthFactor: Double
|
||||||
|
let eyeBoxHeightFactor: Double
|
||||||
|
let faceWidthRatio: Double
|
||||||
|
let horizontalMin: Double
|
||||||
|
let horizontalMax: Double
|
||||||
|
let verticalMin: Double
|
||||||
|
let verticalMax: Double
|
||||||
|
}
|
||||||
@@ -49,24 +49,56 @@ class EyeTrackingService: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func applyStrictness(_ strictness: Double) {
|
private func applyStrictness(_ strictness: Double) {
|
||||||
|
let settings = SettingsManager.shared.settings
|
||||||
|
let widthFactor = settings.enforceModeEyeBoxWidthFactor
|
||||||
|
let heightFactor = settings.enforceModeEyeBoxHeightFactor
|
||||||
|
let calibration = settings.enforceModeCalibration
|
||||||
|
|
||||||
|
let clamped = min(1, max(0, strictness))
|
||||||
|
let scale = 1.6 - (0.8 * clamped)
|
||||||
|
|
||||||
|
let horizontalThreshold: Double
|
||||||
|
let verticalThreshold: Double
|
||||||
|
let baselineEnabled: Bool
|
||||||
|
let centerHorizontal: Double
|
||||||
|
let centerVertical: Double
|
||||||
|
|
||||||
|
if let calibration {
|
||||||
|
let halfWidth = max(0.01, (calibration.horizontalMax - calibration.horizontalMin) / 2)
|
||||||
|
let halfHeight = max(0.01, (calibration.verticalMax - calibration.verticalMin) / 2)
|
||||||
|
let marginScale = 0.15
|
||||||
|
horizontalThreshold = halfWidth * (1.0 + marginScale) * scale
|
||||||
|
verticalThreshold = halfHeight * (1.0 + marginScale) * scale
|
||||||
|
baselineEnabled = false
|
||||||
|
centerHorizontal = (calibration.horizontalMin + calibration.horizontalMax) / 2
|
||||||
|
centerVertical = (calibration.verticalMin + calibration.verticalMax) / 2
|
||||||
|
} else {
|
||||||
|
horizontalThreshold = TrackingConfig.default.horizontalAwayThreshold * scale
|
||||||
|
verticalThreshold = TrackingConfig.default.verticalAwayThreshold * scale
|
||||||
|
baselineEnabled = TrackingConfig.default.baselineEnabled
|
||||||
|
centerHorizontal = TrackingConfig.default.defaultCenterHorizontal
|
||||||
|
centerVertical = TrackingConfig.default.defaultCenterVertical
|
||||||
|
}
|
||||||
|
|
||||||
let config = TrackingConfig(
|
let config = TrackingConfig(
|
||||||
horizontalAwayThreshold: 0.08,
|
horizontalAwayThreshold: horizontalThreshold,
|
||||||
verticalAwayThreshold: 0.12,
|
verticalAwayThreshold: verticalThreshold,
|
||||||
minBaselineSamples: TrackingConfig.default.minBaselineSamples,
|
minBaselineSamples: TrackingConfig.default.minBaselineSamples,
|
||||||
baselineSmoothing: TrackingConfig.default.baselineSmoothing,
|
baselineSmoothing: TrackingConfig.default.baselineSmoothing,
|
||||||
baselineUpdateThreshold: TrackingConfig.default.baselineUpdateThreshold,
|
baselineUpdateThreshold: TrackingConfig.default.baselineUpdateThreshold,
|
||||||
minConfidence: TrackingConfig.default.minConfidence,
|
minConfidence: TrackingConfig.default.minConfidence,
|
||||||
eyeClosedThreshold: TrackingConfig.default.eyeClosedThreshold,
|
eyeClosedThreshold: TrackingConfig.default.eyeClosedThreshold,
|
||||||
baselineEnabled: TrackingConfig.default.baselineEnabled,
|
baselineEnabled: baselineEnabled,
|
||||||
defaultCenterHorizontal: TrackingConfig.default.defaultCenterHorizontal,
|
defaultCenterHorizontal: centerHorizontal,
|
||||||
defaultCenterVertical: TrackingConfig.default.defaultCenterVertical,
|
defaultCenterVertical: centerVertical,
|
||||||
faceWidthSmoothing: TrackingConfig.default.faceWidthSmoothing,
|
faceWidthSmoothing: TrackingConfig.default.faceWidthSmoothing,
|
||||||
faceWidthScaleMin: TrackingConfig.default.faceWidthScaleMin,
|
faceWidthScaleMin: TrackingConfig.default.faceWidthScaleMin,
|
||||||
faceWidthScaleMax: 1.4,
|
faceWidthScaleMax: 1.4,
|
||||||
eyeBoundsHorizontalPadding: TrackingConfig.default.eyeBoundsHorizontalPadding,
|
eyeBoundsHorizontalPadding: TrackingConfig.default.eyeBoundsHorizontalPadding,
|
||||||
eyeBoundsVerticalPaddingUp: TrackingConfig.default.eyeBoundsVerticalPaddingUp,
|
eyeBoundsVerticalPaddingUp: TrackingConfig.default.eyeBoundsVerticalPaddingUp,
|
||||||
eyeBoundsVerticalPaddingDown: TrackingConfig.default.eyeBoundsVerticalPaddingDown,
|
eyeBoundsVerticalPaddingDown: TrackingConfig.default.eyeBoundsVerticalPaddingDown,
|
||||||
eyeBoundsSmoothing: TrackingConfig.default.eyeBoundsSmoothing
|
eyeBoxWidthFactor: widthFactor,
|
||||||
|
eyeBoxHeightFactor: heightFactor
|
||||||
)
|
)
|
||||||
|
|
||||||
processor.updateConfig(config)
|
processor.updateConfig(config)
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ public struct TrackingConfig: Sendable {
|
|||||||
eyeBoundsHorizontalPadding: Double,
|
eyeBoundsHorizontalPadding: Double,
|
||||||
eyeBoundsVerticalPaddingUp: Double,
|
eyeBoundsVerticalPaddingUp: Double,
|
||||||
eyeBoundsVerticalPaddingDown: Double,
|
eyeBoundsVerticalPaddingDown: Double,
|
||||||
eyeBoundsSmoothing: Double
|
eyeBoxWidthFactor: Double,
|
||||||
|
eyeBoxHeightFactor: Double
|
||||||
) {
|
) {
|
||||||
self.horizontalAwayThreshold = horizontalAwayThreshold
|
self.horizontalAwayThreshold = horizontalAwayThreshold
|
||||||
self.verticalAwayThreshold = verticalAwayThreshold
|
self.verticalAwayThreshold = verticalAwayThreshold
|
||||||
@@ -87,7 +88,8 @@ public struct TrackingConfig: Sendable {
|
|||||||
self.eyeBoundsHorizontalPadding = eyeBoundsHorizontalPadding
|
self.eyeBoundsHorizontalPadding = eyeBoundsHorizontalPadding
|
||||||
self.eyeBoundsVerticalPaddingUp = eyeBoundsVerticalPaddingUp
|
self.eyeBoundsVerticalPaddingUp = eyeBoundsVerticalPaddingUp
|
||||||
self.eyeBoundsVerticalPaddingDown = eyeBoundsVerticalPaddingDown
|
self.eyeBoundsVerticalPaddingDown = eyeBoundsVerticalPaddingDown
|
||||||
self.eyeBoundsSmoothing = eyeBoundsSmoothing
|
self.eyeBoxWidthFactor = eyeBoxWidthFactor
|
||||||
|
self.eyeBoxHeightFactor = eyeBoxHeightFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
public let horizontalAwayThreshold: Double
|
public let horizontalAwayThreshold: Double
|
||||||
@@ -106,7 +108,8 @@ public struct TrackingConfig: Sendable {
|
|||||||
public let eyeBoundsHorizontalPadding: Double
|
public let eyeBoundsHorizontalPadding: Double
|
||||||
public let eyeBoundsVerticalPaddingUp: Double
|
public let eyeBoundsVerticalPaddingUp: Double
|
||||||
public let eyeBoundsVerticalPaddingDown: Double
|
public let eyeBoundsVerticalPaddingDown: Double
|
||||||
public let eyeBoundsSmoothing: Double
|
public let eyeBoxWidthFactor: Double
|
||||||
|
public let eyeBoxHeightFactor: Double
|
||||||
|
|
||||||
public static let `default` = TrackingConfig(
|
public static let `default` = TrackingConfig(
|
||||||
horizontalAwayThreshold: 0.08,
|
horizontalAwayThreshold: 0.08,
|
||||||
@@ -125,6 +128,7 @@ public struct TrackingConfig: Sendable {
|
|||||||
eyeBoundsHorizontalPadding: 0.1,
|
eyeBoundsHorizontalPadding: 0.1,
|
||||||
eyeBoundsVerticalPaddingUp: 0.9,
|
eyeBoundsVerticalPaddingUp: 0.9,
|
||||||
eyeBoundsVerticalPaddingDown: 0.4,
|
eyeBoundsVerticalPaddingDown: 0.4,
|
||||||
eyeBoundsSmoothing: 0.2
|
eyeBoxWidthFactor: 0.18,
|
||||||
|
eyeBoxHeightFactor: 0.10
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
private let baselineModel = GazeBaselineModel()
|
private let baselineModel = GazeBaselineModel()
|
||||||
private var faceWidthBaseline: Double?
|
private var faceWidthBaseline: Double?
|
||||||
private var faceWidthSmoothed: Double?
|
private var faceWidthSmoothed: Double?
|
||||||
private var leftEyeFrameSmoothed: CGRect?
|
|
||||||
private var rightEyeFrameSmoothed: CGRect?
|
|
||||||
private var config: TrackingConfig
|
private var config: TrackingConfig
|
||||||
|
|
||||||
init(config: TrackingConfig) {
|
init(config: TrackingConfig) {
|
||||||
@@ -48,8 +46,6 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
baselineModel.reset()
|
baselineModel.reset()
|
||||||
faceWidthBaseline = nil
|
faceWidthBaseline = nil
|
||||||
faceWidthSmoothed = nil
|
faceWidthSmoothed = nil
|
||||||
leftEyeFrameSmoothed = nil
|
|
||||||
rightEyeFrameSmoothed = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func process(analysis: VisionPipeline.FaceAnalysis) -> ObservationResult {
|
func process(analysis: VisionPipeline.FaceAnalysis) -> ObservationResult {
|
||||||
@@ -81,15 +77,13 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
eye: landmarks.leftEye,
|
eye: landmarks.leftEye,
|
||||||
pupil: landmarks.leftPupil,
|
pupil: landmarks.leftPupil,
|
||||||
face: face,
|
face: face,
|
||||||
imageSize: analysis.imageSize,
|
imageSize: analysis.imageSize
|
||||||
smoothingRect: &leftEyeFrameSmoothed
|
|
||||||
)
|
)
|
||||||
let rightEye = makeEyeObservation(
|
let rightEye = makeEyeObservation(
|
||||||
eye: landmarks.rightEye,
|
eye: landmarks.rightEye,
|
||||||
pupil: landmarks.rightPupil,
|
pupil: landmarks.rightPupil,
|
||||||
face: face,
|
face: face,
|
||||||
imageSize: analysis.imageSize,
|
imageSize: analysis.imageSize
|
||||||
smoothingRect: &rightEyeFrameSmoothed
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let eyesClosed = detectEyesClosed(left: leftEye, right: rightEye)
|
let eyesClosed = detectEyesClosed(left: leftEye, right: rightEye)
|
||||||
@@ -132,8 +126,7 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
eye: VNFaceLandmarkRegion2D?,
|
eye: VNFaceLandmarkRegion2D?,
|
||||||
pupil: VNFaceLandmarkRegion2D?,
|
pupil: VNFaceLandmarkRegion2D?,
|
||||||
face: VNFaceObservation,
|
face: VNFaceObservation,
|
||||||
imageSize: CGSize,
|
imageSize: CGSize
|
||||||
smoothingRect: inout CGRect?
|
|
||||||
) -> EyeObservation? {
|
) -> EyeObservation? {
|
||||||
guard let eye else { return nil }
|
guard let eye else { return nil }
|
||||||
|
|
||||||
@@ -149,10 +142,12 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
pupilPoint = bounds.center
|
pupilPoint = bounds.center
|
||||||
}
|
}
|
||||||
|
|
||||||
let rawFrame = CGRect(x: bounds.minX, y: bounds.minY, width: bounds.size.width, height: bounds.size.height)
|
let eyeBox = makeFaceRelativeEyeBox(
|
||||||
let smoothedFrame = smoothRect(rawFrame, existing: &smoothingRect, smoothing: config.eyeBoundsSmoothing)
|
center: bounds.center,
|
||||||
|
faceWidth: face.boundingBox.size.width * imageSize.width
|
||||||
|
)
|
||||||
let paddedFrame = expandRect(
|
let paddedFrame = expandRect(
|
||||||
smoothedFrame,
|
eyeBox,
|
||||||
horizontalPadding: config.eyeBoundsHorizontalPadding,
|
horizontalPadding: config.eyeBoundsHorizontalPadding,
|
||||||
verticalPaddingUp: config.eyeBoundsVerticalPaddingUp,
|
verticalPaddingUp: config.eyeBoundsVerticalPaddingUp,
|
||||||
verticalPaddingDown: config.eyeBoundsVerticalPaddingDown
|
verticalPaddingDown: config.eyeBoundsVerticalPaddingDown
|
||||||
@@ -247,24 +242,15 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func smoothRect(_ rect: CGRect, existing: inout CGRect?, smoothing: Double) -> CGRect {
|
private func makeFaceRelativeEyeBox(center: CGPoint, faceWidth: CGFloat) -> CGRect {
|
||||||
guard smoothing > 0, smoothing < 1 else {
|
let width = faceWidth * CGFloat(config.eyeBoxWidthFactor)
|
||||||
existing = rect
|
let height = faceWidth * CGFloat(config.eyeBoxHeightFactor)
|
||||||
return rect
|
return CGRect(
|
||||||
}
|
x: center.x - width / 2,
|
||||||
|
y: center.y - height / 2,
|
||||||
if let current = existing {
|
width: width,
|
||||||
let newOriginX = current.origin.x + (rect.origin.x - current.origin.x) * smoothing
|
height: height
|
||||||
let newOriginY = current.origin.y + (rect.origin.y - current.origin.y) * smoothing
|
)
|
||||||
let newWidth = current.size.width + (rect.size.width - current.size.width) * smoothing
|
|
||||||
let newHeight = current.size.height + (rect.size.height - current.size.height) * smoothing
|
|
||||||
let updated = CGRect(x: newOriginX, y: newOriginY, width: newWidth, height: newHeight)
|
|
||||||
existing = updated
|
|
||||||
return updated
|
|
||||||
}
|
|
||||||
|
|
||||||
existing = rect
|
|
||||||
return rect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func averageCoordinate(left: CGFloat?, right: CGFloat?, fallback: Double?) -> Double? {
|
private func averageCoordinate(left: CGFloat?, right: CGFloat?, fallback: Double?) -> Double? {
|
||||||
@@ -342,9 +328,9 @@ final class VisionGazeProcessor: @unchecked Sendable {
|
|||||||
let lookingUp = vertical < baseline.vertical
|
let lookingUp = vertical < baseline.vertical
|
||||||
let verticalMultiplier: Double
|
let verticalMultiplier: Double
|
||||||
if lookingDown {
|
if lookingDown {
|
||||||
verticalMultiplier = 1.2
|
verticalMultiplier = 1.1
|
||||||
} else if lookingUp {
|
} else if lookingUp {
|
||||||
verticalMultiplier = 1.8
|
verticalMultiplier = 1.4
|
||||||
} else {
|
} else {
|
||||||
verticalMultiplier = 1.0
|
verticalMultiplier = 1.0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
// Created by Mike Freno on 1/30/26.
|
// Created by Mike Freno on 1/30/26.
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppKit
|
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import AppKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct EnforceModeSetupContent: View {
|
struct EnforceModeSetupContent: View {
|
||||||
@@ -79,6 +79,9 @@ struct EnforceModeSetupContent: View {
|
|||||||
if enforceModeService.isEnforceModeEnabled {
|
if enforceModeService.isEnforceModeEnabled {
|
||||||
strictnessControlView
|
strictnessControlView
|
||||||
}
|
}
|
||||||
|
if isTestModeActive && enforceModeService.isCameraActive {
|
||||||
|
eyeBoxControlView
|
||||||
|
}
|
||||||
if enforceModeService.isCameraActive {
|
if enforceModeService.isCameraActive {
|
||||||
trackingLapButton
|
trackingLapButton
|
||||||
}
|
}
|
||||||
@@ -120,7 +123,6 @@ struct EnforceModeSetupContent: View {
|
|||||||
.controlSize(presentation.isCard ? .regular : .large)
|
.controlSize(presentation.isCard ? .regular : .large)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private var testModePreviewView: some View {
|
private var testModePreviewView: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
let lookingAway = eyeTrackingService.trackingResult.gazeState == .lookingAway
|
let lookingAway = eyeTrackingService.trackingResult.gazeState == .lookingAway
|
||||||
@@ -140,7 +142,9 @@ struct EnforceModeSetupContent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: presentation.isCard ? 180 : (isCompact ? 200 : 300))
|
.frame(height: presentation.isCard ? 180 : (isCompact ? 200 : 300))
|
||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: sectionCornerRadius))
|
.glassEffectIfAvailable(
|
||||||
|
GlassStyle.regular, in: .rect(cornerRadius: sectionCornerRadius)
|
||||||
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if cachedPreviewLayer == nil {
|
if cachedPreviewLayer == nil {
|
||||||
cachedPreviewLayer = eyeTrackingService.previewLayer
|
cachedPreviewLayer = eyeTrackingService.previewLayer
|
||||||
@@ -249,7 +253,8 @@ struct EnforceModeSetupContent: View {
|
|||||||
}
|
}
|
||||||
.padding(sectionPadding)
|
.padding(sectionPadding)
|
||||||
.glassEffectIfAvailable(
|
.glassEffectIfAvailable(
|
||||||
GlassStyle.regular.tint(.blue.opacity(0.1)), in: .rect(cornerRadius: sectionCornerRadius)
|
GlassStyle.regular.tint(.blue.opacity(0.1)),
|
||||||
|
in: .rect(cornerRadius: sectionCornerRadius)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +354,8 @@ struct EnforceModeSetupContent: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let horizontal = eyeTrackingService.debugState.normalizedHorizontal,
|
if let horizontal = eyeTrackingService.debugState.normalizedHorizontal,
|
||||||
let vertical = eyeTrackingService.debugState.normalizedVertical {
|
let vertical = eyeTrackingService.debugState.normalizedVertical
|
||||||
|
{
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Text("Ratios:")
|
Text("Ratios:")
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
@@ -393,6 +399,37 @@ struct EnforceModeSetupContent: View {
|
|||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: sectionCornerRadius))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: sectionCornerRadius))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var eyeBoxControlView: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("Eye Box Size")
|
||||||
|
.font(headerFont)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("Width")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value: $settingsManager.settings.enforceModeEyeBoxWidthFactor,
|
||||||
|
in: 0.12...0.25
|
||||||
|
)
|
||||||
|
.controlSize(.small)
|
||||||
|
|
||||||
|
Text("Height")
|
||||||
|
.font(.caption2)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value: $settingsManager.settings.enforceModeEyeBoxHeightFactor,
|
||||||
|
in: 0.02...0.05
|
||||||
|
)
|
||||||
|
.controlSize(.small)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(sectionPadding)
|
||||||
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: sectionCornerRadius))
|
||||||
|
}
|
||||||
|
|
||||||
private var trackingLapButton: some View {
|
private var trackingLapButton: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
enforceModeService.logTrackingLap()
|
enforceModeService.logTrackingLap()
|
||||||
|
|||||||
Reference in New Issue
Block a user