spb/focale Public
Swift 100%
1//2// ControlLayout.swift3// Focale4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7//8// User-placeable capture interface (CLAUDE.md §6): which controls are9// visible, in what order, and how haptics behave. Three provided10// profiles — Simple / Photographe / Expert — all editable.11//1213import Foundation1415/// A manual control the user can place in the capture interface.16enum CaptureControl: String, Codable, Sendable, CaseIterable, Identifiable {17 case iso18 case shutterSpeed19 case exposureBias20 case focus21 case whiteBalance22 case zoom2324 var id: String { rawValue }2526 /// User-facing label, in French.27 var displayName: String {28 switch self {29 case .iso: "ISO"30 case .shutterSpeed: "Vitesse"31 case .exposureBias: "Expo"32 case .focus: "Mise au point"33 case .whiteBalance: "Balance"34 case .zoom: "Zoom"35 }36 }3738 var symbolName: String {39 switch self {40 case .iso: "camera.aperture"41 case .shutterSpeed: "timer"42 case .exposureBias: "plusminus.circle"43 case .focus: "scope"44 case .whiteBalance: "thermometer.sun"45 case .zoom: "plus.magnifyingglass"46 }47 }48}4950/// Per-control haptic configuration (e.g. a detent every third of a stop).51struct HapticProfile: Codable, Sendable {52 var enabled: Bool53 /// Number of detents across the control's full range. 0 = continuous.54 var detentCount: Int5556 static let subtle = HapticProfile(enabled: true, detentCount: 0)57 static let thirdStops = HapticProfile(enabled: true, detentCount: 18)58 static let off = HapticProfile(enabled: false, detentCount: 0)59}6061struct ControlLayout: Codable, Sendable, Identifiable {62 var id: UUID63 var name: String64 /// Controls shown in the dial strip, in order.65 var visibleControls: [CaptureControl]66 var haptics: [CaptureControl.RawValue: HapticProfile]67 var gestureMap: GestureMap6869 init(70 id: UUID = UUID(),71 name: String,72 visibleControls: [CaptureControl],73 haptics: [CaptureControl.RawValue: HapticProfile] = [:],74 gestureMap: GestureMap = .standard75 ) {76 self.id = id77 self.name = name78 self.visibleControls = visibleControls79 self.haptics = haptics80 self.gestureMap = gestureMap81 }8283 func hapticProfile(for control: CaptureControl) -> HapticProfile {84 haptics[control.rawValue] ?? .subtle85 }8687 // MARK: - Provided profiles (all editable)8889 static let simple = ControlLayout(90 name: "Simple",91 visibleControls: [.exposureBias, .zoom]92 )9394 static let photographe = ControlLayout(95 name: "Photographe",96 visibleControls: [.iso, .shutterSpeed, .exposureBias, .zoom],97 haptics: [CaptureControl.iso.rawValue: .thirdStops,98 CaptureControl.shutterSpeed.rawValue: .thirdStops]99 )100101 static let expert = ControlLayout(102 name: "Expert",103 visibleControls: CaptureControl.allCases,104 haptics: [CaptureControl.iso.rawValue: .thirdStops,105 CaptureControl.shutterSpeed.rawValue: .thirdStops,106 CaptureControl.focus.rawValue: .subtle]107 )108109 static let provided: [ControlLayout] = [.simple, .photographe, .expert]110}111