iOS: line up the pause menu row titles

Change Disc sat about twelve points left of everything around it in Game Tools,
and Controller Skin did the same in This Game.

Every row in those cards puts its title 34 points in, from a 22 point icon frame
plus 12 points of spacing. Those two never went through the row components though.
They are Menus built over in GameScreenView, handed to the pause menu as opaque
AnyViews, and their labels are plain Labels, which bring their own narrower icon
column along with them.

There is one label style for that column now, applied where the menus get hosted
rather than at the two labels themselves, so anything injected later lines up
without the author having to know the rule.

The 22 and the 12 have names. The caption under the Virtual Pad toggle was
carrying a hardcoded 34 to hang under the title, which is exactly the sort of
number that quietly stops matching.
This commit is contained in:
J1coding
2026-08-02 18:58:24 +02:00
committed by Jeen
parent adc77e819d
commit 759889df1a
2 changed files with 33 additions and 8 deletions
@@ -69,6 +69,14 @@ enum OverlayTheme {
static let glassTopHighlight = Color.white.opacity(0.10)
static let cardTopHighlight = Color.white.opacity(0.07)
static let cardShadow = Color.black.opacity(0.18)
// MARK: Row metrics the icon column every overlay row shares
/// Icon column width. Every row title starts at `rowLabelInset` so they line up down the card.
static let rowIconWidth: CGFloat = 22
static let rowIconSpacing: CGFloat = 12
/// Where a row title starts. Use this to hang anything under a row, don't re-add the two.
static let rowLabelInset: CGFloat = rowIconWidth + rowIconSpacing
}
// MARK: - Overlay Components
@@ -282,6 +290,22 @@ struct OverlayHeader: View {
// MARK: - Rows
/// Puts a plain `Label` on the same icon column as the rows below, so an injected `Menu` lines up
/// with the hand-built rows either side of it instead of using `Label`'s own narrower icon slot.
struct OverlayRowLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: OverlayTheme.rowIconSpacing) {
configuration.icon
.frame(width: OverlayTheme.rowIconWidth)
.foregroundStyle(OverlayTheme.textSecondary)
configuration.title
.foregroundStyle(OverlayTheme.textPrimary)
.lineLimit(1)
.layoutPriority(1)
}
}
}
/// A fixed-min-height overlay action row that GUARANTEES the main label wins over a trailing
/// value: the label gets `layoutPriority(1)` and the trailing value `layoutPriority(-1)` with tail
/// truncation, so on a narrow width the trailing value elides first while the label stays intact.
@@ -309,10 +333,10 @@ struct OverlayActionRow: View {
var body: some View {
Button(action: action) {
HStack(spacing: 12) {
HStack(spacing: OverlayTheme.rowIconSpacing) {
if let systemImage {
Image(systemName: systemImage)
.frame(width: 22)
.frame(width: OverlayTheme.rowIconWidth)
.foregroundStyle(isDestructive ? OverlayTheme.destructive : OverlayTheme.textSecondary)
}
Text(label)
@@ -351,9 +375,9 @@ struct OverlayToggleRow: View {
var body: some View {
Toggle(isOn: $isOn) {
HStack(spacing: 12) {
HStack(spacing: OverlayTheme.rowIconSpacing) {
Image(systemName: systemImage)
.frame(width: 22)
.frame(width: OverlayTheme.rowIconWidth)
.foregroundStyle(OverlayTheme.textSecondary)
Text(label)
.foregroundStyle(OverlayTheme.textPrimary)
@@ -193,7 +193,7 @@ struct QuickMenuView: View {
.font(.caption)
.foregroundStyle(OverlayTheme.textSecondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 34)
.padding(.leading, OverlayTheme.rowLabelInset)
.padding(.bottom, 4)
}
@@ -269,12 +269,13 @@ struct QuickMenuView: View {
.accessibilityHint(settings.localized("Quits this game and returns to the library"))
}
/// Hosts an injected SwiftUI `Menu` (controller skin / change disc) as a row that matches the
/// action rows as closely as an opaque AnyView allows. The menu's own action
/// semantics are untouched.
/// Hosts an injected SwiftUI `Menu` (controller skin / change disc) as a row matching the
/// action rows. Their labels are plain `Label`s, so without the style they sit on `Label`'s own
/// icon column and land short of everything else in the card.
@ViewBuilder
private func injectedMenuRow(_ menu: AnyView) -> some View {
menu
.labelStyle(OverlayRowLabelStyle())
.foregroundStyle(OverlayTheme.textPrimary)
.frame(maxWidth: .infinity, minHeight: 44, alignment: .leading)
}