import SwiftUI // Gamepad-style visualization of a controller input snapshot: D-pad and face // buttons on top, stick pads and triggers below, mirroring the physical // layout of an Xbox controller. struct DPadView: View { let controller: ControllerInput var body: some View { VStack(spacing: 3) { cell(.dPadUp) HStack(spacing: 3) { cell(.dPadLeft) RoundedRectangle(cornerRadius: 5) .fill(Color.secondary.opacity(0.28)) .frame(width: 30, height: 26) cell(.dPadRight) } cell(.dPadDown) } } private func cell(_ button: XboxButton) -> some View { let pressed = controller.isPressed(button) return RoundedRectangle(cornerRadius: 5) .fill(pressed ? Color.green : Color.secondary.opacity(0.18)) .frame(width: 30, height: 26) .overlay( Image(systemName: button.dPadSymbol ?? "") .font(.system(size: 9)) .foregroundStyle(pressed ? .white : .secondary) ) } } struct FaceButtonsView: View { let controller: ControllerInput var body: some View { VStack(spacing: 4) { faceButton(.y) HStack(spacing: 10) { faceButton(.x) faceButton(.b) } faceButton(.a) } } private func faceButton(_ button: XboxButton) -> some View { let pressed = controller.isPressed(button) return Text(button.label) .font(.callout.weight(.semibold)) .frame(width: 32, height: 32) .background(Circle().fill(pressed ? Color.green : Color.secondary.opacity(0.18))) .foregroundStyle(pressed ? .white : .primary) } } struct StickPadView: View { let label: String let x: Int16 let y: Int16 // True while the stick is clicked in (LS/RS). var pressed = false private let diameter: CGFloat = 64 var body: some View { VStack(spacing: 4) { ZStack { Circle() .fill(Color.secondary.opacity(0.15)) Circle() .stroke(pressed ? Color.green : Color.secondary.opacity(0.35), lineWidth: pressed ? 2.5 : 1) .padding(4) Circle() .fill(Color.accentColor) .frame(width: 12, height: 12) .offset(x: offset.width, y: offset.height) } .frame(width: diameter, height: diameter) Text(label) .font(.caption2) .foregroundStyle(.secondary) } } // Map the signed 16-bit stick values onto the pad, keeping the dot inside // the ring. private var offset: CGSize { let travel = diameter / 2 - 10 return CGSize( width: CGFloat(Double(x) / 32768.0 * travel), height: CGFloat(Double(y) / 32768.0 * travel) ) } } struct TriggerBarView: View { let label: String let value: UInt16 private let barWidth: CGFloat = 56 var body: some View { VStack(spacing: 4) { ZStack(alignment: .leading) { Color.secondary.opacity(0.18) Color.accentColor .frame(width: barWidth * fraction, alignment: .leading) } .frame(width: barWidth, height: 6) .clipShape(RoundedRectangle(cornerRadius: 3)) Text(label) .font(.caption2) .foregroundStyle(.secondary) } } private var fraction: CGFloat { min(max(CGFloat(Double(value) / 1023.0), 0), 1) } } // The large Guide button, centered between the D-pad and face buttons. struct GuideButtonView: View { let pressed: Bool var body: some View { VStack(spacing: 4) { ZStack { Circle() .fill(pressed ? Color.green : Color.secondary.opacity(0.25)) Image(systemName: "gamecontroller.fill") .font(.system(size: 14)) .foregroundStyle(pressed ? .white : .secondary) } .frame(width: 36, height: 36) Text("Guide") .font(.caption2) .foregroundStyle(.secondary) } } } struct ButtonChip: View { let button: XboxButton let pressed: Bool var body: some View { Text(button.label) .font(.caption2.weight(.medium)) .frame(minWidth: 30, minHeight: 20) .background(pressed ? Color.green : Color.secondary.opacity(0.15)) .foregroundStyle(pressed ? .white : .primary) .clipShape(RoundedRectangle(cornerRadius: 4)) } }