From 7655c36f34730ac8b2ae78fe848acd7e10b7117e Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 11 Jun 2026 14:19:54 +0200 Subject: [PATCH] =?UTF-8?q?fix(apple/tvOS):=20hand-rolled=20selection=20sc?= =?UTF-8?q?reens=20=E2=80=94=20kills=20the=20black-text=20flash=20in=20pic?= =?UTF-8?q?kers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The navigationLink Picker's INTERNAL destination list renders its rows in the focused (dark-text) style while the push animates — black text over the dark backdrop until focus settles (present under the old fade too; a SwiftUI-on-tvOS quirk we don't control). Settings now uses its own primitives instead: - TVSelectionRow: label + current value, pushes… - TVSelectionList: a Settings-app-style option list (plain button rows + checkmark, selecting pops back) — ordinary button chrome, no focused-style pre-rendering. The stream-mode and compositor pickers are gone on tvOS; the Settings screen itself is a plain scroll of rows + footer (no Form), matching the rest of the tv UI. Co-Authored-By: Claude Fable 5 --- .../PunktfunkClient/SettingsView.swift | 34 ++++++------ .../Sources/PunktfunkClient/TVTextEntry.swift | 54 +++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/clients/apple/Sources/PunktfunkClient/SettingsView.swift b/clients/apple/Sources/PunktfunkClient/SettingsView.swift index 81fd5a7..bd5157f 100644 --- a/clients/apple/Sources/PunktfunkClient/SettingsView.swift +++ b/clients/apple/Sources/PunktfunkClient/SettingsView.swift @@ -64,29 +64,29 @@ struct SettingsView: View { if !options.contains(where: { $0.tag == currentTag }) { options.insert(("Custom (\(width)×\(height) @ \(hz))", currentTag), at: 0) } - return Form { - Section { - Picker("Stream mode", selection: modeTag) { - ForEach(options, id: \.tag) { option in - Text(option.label).tag(option.tag) - } - } - .pickerStyle(.navigationLink) - Picker("Compositor", selection: $compositor) { - Text("Automatic").tag(0) - Text("KWin (KDE Plasma)").tag(1) - Text("wlroots (Sway / Hyprland)").tag(2) - Text("Mutter (GNOME)").tag(3) - Text("gamescope").tag(4) - } - .pickerStyle(.navigationLink) - } footer: { + let compositors: [(label: String, tag: Int)] = [ + ("Automatic", 0), + ("KWin (KDE Plasma)", 1), + ("wlroots (Sway / Hyprland)", 2), + ("Mutter (GNOME)", 3), + ("gamescope", 4), + ] + return ScrollView { + VStack(spacing: 16) { + TVSelectionRow(title: "Stream mode", options: options, selection: modeTag) + TVSelectionRow( + title: "Compositor", options: compositors, selection: $compositor) Text("The host creates a virtual output at exactly this mode — native " + "resolution, no scaling. A specific compositor is honored only if " + "available on the host.") .font(.caption) .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .padding(.top, 8) } + .frame(maxWidth: 1000) + .frame(maxWidth: .infinity) + .padding(60) } .navigationTitle("Settings") } diff --git a/clients/apple/Sources/PunktfunkClient/TVTextEntry.swift b/clients/apple/Sources/PunktfunkClient/TVTextEntry.swift index a4b96b0..575e199 100644 --- a/clients/apple/Sources/PunktfunkClient/TVTextEntry.swift +++ b/clients/apple/Sources/PunktfunkClient/TVTextEntry.swift @@ -87,4 +87,58 @@ struct TVFieldRow: View { } } } +/// A Settings-app-style selection screen: pushed list of option rows, checkmark on the +/// current value, selecting pops back. Replaces Picker(.navigationLink), whose internal +/// list renders rows in the focused (dark-text) style while the push animates. +struct TVSelectionList: View { + let title: String + let options: [(label: String, tag: Tag)] + @Binding var selection: Tag + @Environment(\.dismiss) private var dismiss + + var body: some View { + ScrollView { + VStack(spacing: 16) { + ForEach(options, id: \.tag) { option in + Button { + selection = option.tag + dismiss() + } label: { + HStack { + Text(option.label) + Spacer() + if option.tag == selection { + Image(systemName: "checkmark") + } + } + } + } + } + .frame(maxWidth: 900) + .frame(maxWidth: .infinity) + .padding(60) + } + .navigationTitle(title) + } +} + +/// The pushing row for a TVSelectionList: label leading, current value trailing. +struct TVSelectionRow: View { + let title: String + let options: [(label: String, tag: Tag)] + @Binding var selection: Tag + + var body: some View { + NavigationLink { + TVSelectionList(title: title, options: options, selection: $selection) + } label: { + HStack { + Text(title) + Spacer() + Text(options.first { $0.tag == selection }?.label ?? "—") + .foregroundStyle(.secondary) + } + } + } +} #endif