fix: match controller card to Xbox layout

Realign the card to the physical controller face: trigger bars and
bumper chips in the top corners, sticks side by side in the upper
center (stick clicks light up their rings instead of separate chips),
D-pad lower left, face buttons lower right, and the Guide button
centered between the clusters flanked by Select and Start.

Co-Authored-By: qwen3.8-27b@q3_k_xl: realigned card to Xbox layout
This commit is contained in:
portersky
2026-08-29 14:53:52 +02:00
parent 4931bf5c18
commit a652a9f187
3 changed files with 70 additions and 27 deletions
+43 -24
View File
@@ -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)
}
}
+26 -2
View File
@@ -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