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