From 33356afe472c925c78a9b4946537dec7a2e75400 Mon Sep 17 00:00:00 2001 From: portersky Date: Sat, 29 Aug 2026 15:02:01 +0200 Subject: [PATCH] fix: compact, aligned controller face layout Group each stick above its cluster (L over D-pad, R over face buttons) in equal-height columns so the rows line up, cap the face at a natural controller proportion so wide cards do not stretch it, and replace the Select/Start chips with small round icon buttons flanking the centered Guide button. Co-Authored-By: qwen3.8-27b@q3_k_xl: compacted and aligned the face --- CMakeLists.txt | 2 +- src/app/ControllerCardView.swift | 70 ++++++++++++++++++-------------- src/app/GamepadViews.swift | 23 +++++++++++ 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4680224..f363891 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.22 LANGUAGES CXX Swift) +project(xone_macos VERSION 0.1.23 LANGUAGES CXX Swift) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/src/app/ControllerCardView.swift b/src/app/ControllerCardView.swift index d0e22d8..7927404 100644 --- a/src/app/ControllerCardView.swift +++ b/src/app/ControllerCardView.swift @@ -1,19 +1,23 @@ 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. +// Card for one connected controller. The face mirrors the physical Xbox +// controller: shoulders (triggers + bumpers) along the top, left stick above +// the D-pad, right stick above the face buttons, and the Guide button +// centered between the two columns flanked by Select and Start. The face is +// capped at a natural controller proportion so wide cards do not stretch it. struct ControllerCardView: View { let controller: ControllerInput var body: some View { - VStack(alignment: .leading, spacing: 14) { + VStack(alignment: .leading, spacing: 12) { header - shoulders - sticks - clusters + VStack(spacing: 12) { + shoulders + face + } + .frame(maxWidth: 420) + .frame(maxWidth: .infinity) footer } .padding(14) @@ -50,30 +54,34 @@ struct ControllerCardView: View { } } - // 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() - HStack(spacing: 10) { - ButtonChip(button: .select, pressed: controller.isPressed(.select)) - GuideButtonView(pressed: controller.guideDown) - ButtonChip(button: .start, pressed: controller.isPressed(.start)) + // The face: two equal-width columns (stick over cluster) with the system + // buttons centered between them. Both columns are the same height, so + // sticks line up and clusters line up. + private var face: some View { + HStack(alignment: .center, spacing: 0) { + VStack(spacing: 12) { + StickPadView(label: "L", x: controller.stickLeftX, y: controller.stickLeftY, + pressed: controller.isPressed(.leftStick)) + DPadView(controller: controller) + .frame(height: 104) } - Spacer() - FaceButtonsView(controller: controller) + .frame(maxWidth: .infinity) + + HStack(spacing: 8) { + SystemButtonView(button: .select, symbol: "square.on.square", + pressed: controller.isPressed(.select)) + GuideButtonView(pressed: controller.guideDown) + SystemButtonView(button: .start, symbol: "line.3.horizontal", + pressed: controller.isPressed(.start)) + } + + VStack(spacing: 12) { + StickPadView(label: "R", x: controller.stickRightX, y: controller.stickRightY, + pressed: controller.isPressed(.rightStick)) + FaceButtonsView(controller: controller) + .frame(height: 104) + } + .frame(maxWidth: .infinity) } } diff --git a/src/app/GamepadViews.swift b/src/app/GamepadViews.swift index a9f8d0a..4c69c2b 100644 --- a/src/app/GamepadViews.swift +++ b/src/app/GamepadViews.swift @@ -125,6 +125,29 @@ struct TriggerBarView: View { } } +// Small round system buttons flanking the Guide button (Select and Start). +struct SystemButtonView: View { + let button: XboxButton + let symbol: String + let pressed: Bool + + var body: some View { + VStack(spacing: 4) { + ZStack { + Circle() + .fill(pressed ? Color.green : Color.secondary.opacity(0.25)) + Image(systemName: symbol) + .font(.system(size: 10, weight: .medium)) + .foregroundStyle(pressed ? .white : .secondary) + } + .frame(width: 24, height: 24) + Text(button.label) + .font(.caption2) + .foregroundStyle(.secondary) + } + } +} + // The large Guide button, centered between the D-pad and face buttons. struct GuideButtonView: View { let pressed: Bool