SPB Git

spb/focale Public

Swift 100%
2.2 KB · 66 lines swift
Raw Blame History
1//2//  ControlDial.swift3//  Focale4//5//  Author: Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//8//  Horizontal scrub dial for one manual control, with configurable9//  haptic detents (e.g. a tick every third of a stop — CLAUDE.md §6).10//1112import SwiftUI1314struct ControlDial: View {15    let control: CaptureControl16    let haptics: HapticProfile17    @Bindable var controls: ManualControls18    var onChanged: () -> Void1920    @State private var dragStartValue: Double?21    @State private var lastDetent: Int = -12223    var body: some View {24        VStack(spacing: 4) {25            Text(controls.displayValue(for: control))26                .font(DesignTokens.controlValueFont)27                .foregroundStyle(.white)28                .contentTransition(.numericText())2930            Text(control.displayName)31                .font(DesignTokens.controlLabelFont)32                .foregroundStyle(DesignTokens.textSecondary)33        }34        .frame(minWidth: 74)35        .padding(.vertical, 8)36        .padding(.horizontal, 10)37        .background(DesignTokens.surfaceRaised, in: RoundedRectangle(38            cornerRadius: DesignTokens.cornerRadius39        ))40        .sensoryFeedback(.selection, trigger: lastDetent)41        .gesture(dial)42        .accessibilityLabel(control.displayName)43        .accessibilityValue(controls.displayValue(for: control))44    }4546    private var dial: some Gesture {47        DragGesture(minimumDistance: 2)48            .onChanged { gesture in49                let start = dragStartValue ?? controls.normalizedValue(for: control)50                if dragStartValue == nil { dragStartValue = start }5152                let sensitivity = 1.0 / 220.0   // full range over ~220 pt53                let value = (start + gesture.translation.width * sensitivity)54                    .clamped(to: 0...1)55                controls.setNormalizedValue(value, for: control)5657                if haptics.enabled, haptics.detentCount > 0 {58                    let detent = Int(value * Double(haptics.detentCount))59                    if detent != lastDetent { lastDetent = detent }60                }61                onChanged()62            }63            .onEnded { _ in dragStartValue = nil }64    }65}66