diff --git a/CMakeLists.txt b/CMakeLists.txt index 26b794b..4680224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT CMAKE_GENERATOR MATCHES "^(Ninja|Xcode)$") endif() cmake_minimum_required(VERSION 3.21) -project(xone_macos VERSION 0.1.21 LANGUAGES CXX Swift) +project(xone_macos VERSION 0.1.22 LANGUAGES CXX Swift) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/src/app/ControllerCardView.swift b/src/app/ControllerCardView.swift index 2b79c77..d0e22d8 100644 --- a/src/app/ControllerCardView.swift +++ b/src/app/ControllerCardView.swift @@ -1,18 +1,19 @@ import SwiftUI +// Card for one connected controller. The layout mirrors the physical Xbox +// controller face: shoulders (triggers + bumpers) along the top, sticks side +// by side below them, D-pad lower left, face buttons lower right, and the +// Guide button centered between the two clusters flanked by Select and Start. + struct ControllerCardView: View { let controller: ControllerInput var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 14) { header - HStack(spacing: 28) { - DPadView(controller: controller) - FaceButtonsView(controller: controller) - } - .frame(maxWidth: .infinity) - sticksAndTriggers - systemButtons + shoulders + sticks + clusters footer } .padding(14) @@ -34,27 +35,45 @@ struct ControllerCardView: View { } } - private var sticksAndTriggers: some View { - HStack(alignment: .top, spacing: 12) { - StickPadView(label: "L", x: controller.stickLeftX, y: controller.stickLeftY) + // Shoulder area: trigger bar above its bumper, in each top corner. + private var shoulders: some View { + HStack(alignment: .top) { + VStack(spacing: 5) { + TriggerBarView(label: "LT", value: controller.triggerLeft) + ButtonChip(button: .leftBumper, pressed: controller.isPressed(.leftBumper)) + } Spacer() - TriggerBarView(label: "LT", value: controller.triggerLeft) - TriggerBarView(label: "RT", value: controller.triggerRight) - Spacer() - StickPadView(label: "R", x: controller.stickRightX, y: controller.stickRightY) + VStack(spacing: 5) { + TriggerBarView(label: "RT", value: controller.triggerRight) + ButtonChip(button: .rightBumper, pressed: controller.isPressed(.rightBumper)) + } } } - private var systemButtons: some View { - HStack(spacing: 6) { - ButtonChip(button: .guide, pressed: controller.guideDown) - ButtonChip(button: .start, pressed: controller.isPressed(.start)) - ButtonChip(button: .select, pressed: controller.isPressed(.select)) + // Sticks sit side by side in the upper center of the face. A stick click + // (LS/RS) lights up its ring. + private var sticks: some View { + HStack(spacing: 48) { + StickPadView(label: "L", x: controller.stickLeftX, y: controller.stickLeftY, + pressed: controller.isPressed(.leftStick)) + StickPadView(label: "R", x: controller.stickRightX, y: controller.stickRightY, + pressed: controller.isPressed(.rightStick)) + } + .frame(maxWidth: .infinity) + } + + // Lower clusters with the system buttons centered between them. + private var clusters: some View { + HStack(alignment: .center, spacing: 12) { + DPadView(controller: controller) Spacer() - ButtonChip(button: .leftBumper, pressed: controller.isPressed(.leftBumper)) - ButtonChip(button: .rightBumper, pressed: controller.isPressed(.rightBumper)) - ButtonChip(button: .leftStick, pressed: controller.isPressed(.leftStick)) - ButtonChip(button: .rightStick, pressed: controller.isPressed(.rightStick)) + HStack(spacing: 10) { + ButtonChip(button: .select, pressed: controller.isPressed(.select)) + GuideButtonView(pressed: controller.guideDown) + ButtonChip(button: .start, pressed: controller.isPressed(.start)) + } + Spacer() + FaceButtonsView(controller: controller) } } diff --git a/src/app/GamepadViews.swift b/src/app/GamepadViews.swift index 2ed969a..a9f8d0a 100644 --- a/src/app/GamepadViews.swift +++ b/src/app/GamepadViews.swift @@ -62,6 +62,8 @@ 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 @@ -71,8 +73,9 @@ struct StickPadView: View { Circle() .fill(Color.secondary.opacity(0.15)) Circle() - .stroke(Color.secondary.opacity(0.35), lineWidth: 1) - .padding(6) + .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) @@ -122,6 +125,27 @@ struct TriggerBarView: View { } } +// 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