// // ControlLayout.swift // Focale // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // // User-placeable capture interface (CLAUDE.md §6): which controls are // visible, in what order, and how haptics behave. Three provided // profiles — Simple / Photographe / Expert — all editable. // import Foundation /// A manual control the user can place in the capture interface. enum CaptureControl: String, Codable, Sendable, CaseIterable, Identifiable { case iso case shutterSpeed case exposureBias case focus case whiteBalance case zoom var id: String { rawValue } /// User-facing label, in French. var displayName: String { switch self { case .iso: "ISO" case .shutterSpeed: "Vitesse" case .exposureBias: "Expo" case .focus: "Mise au point" case .whiteBalance: "Balance" case .zoom: "Zoom" } } var symbolName: String { switch self { case .iso: "camera.aperture" case .shutterSpeed: "timer" case .exposureBias: "plusminus.circle" case .focus: "scope" case .whiteBalance: "thermometer.sun" case .zoom: "plus.magnifyingglass" } } } /// Per-control haptic configuration (e.g. a detent every third of a stop). struct HapticProfile: Codable, Sendable { var enabled: Bool /// Number of detents across the control's full range. 0 = continuous. var detentCount: Int static let subtle = HapticProfile(enabled: true, detentCount: 0) static let thirdStops = HapticProfile(enabled: true, detentCount: 18) static let off = HapticProfile(enabled: false, detentCount: 0) } struct ControlLayout: Codable, Sendable, Identifiable { var id: UUID var name: String /// Controls shown in the dial strip, in order. var visibleControls: [CaptureControl] var haptics: [CaptureControl.RawValue: HapticProfile] var gestureMap: GestureMap init( id: UUID = UUID(), name: String, visibleControls: [CaptureControl], haptics: [CaptureControl.RawValue: HapticProfile] = [:], gestureMap: GestureMap = .standard ) { self.id = id self.name = name self.visibleControls = visibleControls self.haptics = haptics self.gestureMap = gestureMap } func hapticProfile(for control: CaptureControl) -> HapticProfile { haptics[control.rawValue] ?? .subtle } // MARK: - Provided profiles (all editable) static let simple = ControlLayout( name: "Simple", visibleControls: [.exposureBias, .zoom] ) static let photographe = ControlLayout( name: "Photographe", visibleControls: [.iso, .shutterSpeed, .exposureBias, .zoom], haptics: [CaptureControl.iso.rawValue: .thirdStops, CaptureControl.shutterSpeed.rawValue: .thirdStops] ) static let expert = ControlLayout( name: "Expert", visibleControls: CaptureControl.allCases, haptics: [CaptureControl.iso.rawValue: .thirdStops, CaptureControl.shutterSpeed.rawValue: .thirdStops, CaptureControl.focus.rawValue: .subtle] ) static let provided: [ControlLayout] = [.simple, .photographe, .expert] }