Rework MPV Options for macOS-native look

Convert the main view to shared SettingsFormContainer/Section helpers,
drop the DisclosureGroup in favor of a single always-visible Default
Options section with monospaced trailing values, and redesign the
Add/Edit MPV option sheets on macOS as native dialogs with a Grid
layout, clearer footers, and keyboard-shortcut actions.
This commit is contained in:
Arkadiusz Fal
2026-04-21 01:12:42 +02:00
parent a5f8bdacfb
commit 79459b8f2e

View File

@@ -14,7 +14,7 @@ struct MPVOptionsSettingsView: View {
@State private var editingOption: (name: String, value: String)? @State private var editingOption: (name: String, value: String)?
var body: some View { var body: some View {
List { SettingsFormContainer {
if let settings = appEnvironment?.settingsManager { if let settings = appEnvironment?.settingsManager {
DefaultOptionsSection() DefaultOptionsSection()
CustomOptionsSection( CustomOptionsSection(
@@ -59,13 +59,9 @@ private struct EditableOption: Identifiable {
// MARK: - Default Options Section // MARK: - Default Options Section
private struct DefaultOptionsSection: View { private struct DefaultOptionsSection: View {
#if !os(tvOS)
@State private var isExpanded = false
#endif
var body: some View { var body: some View {
Section { #if os(tvOS)
#if os(tvOS) SettingsFormSection(footer: "settings.mpvOptions.defaultOptions.footer") {
Text(String(localized: "settings.mpvOptions.defaultOptions")) Text(String(localized: "settings.mpvOptions.defaultOptions"))
.font(.headline) .font(.headline)
ForEach(Self.defaultOptions, id: \.name) { option in ForEach(Self.defaultOptions, id: \.name) { option in
@@ -74,21 +70,22 @@ private struct DefaultOptionsSection: View {
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }
#else
DisclosureGroup(isExpanded: $isExpanded) {
ForEach(Self.defaultOptions, id: \.name) { option in
LabeledContent(option.name) {
Text(option.value)
.foregroundStyle(.secondary)
}
}
} label: {
Text(String(localized: "settings.mpvOptions.defaultOptions"))
}
#endif
} footer: {
Text(String(localized: "settings.mpvOptions.defaultOptions.footer"))
} }
#else
SettingsFormSection("settings.mpvOptions.defaultOptions", footer: "settings.mpvOptions.defaultOptions.footer") {
ForEach(Self.defaultOptions, id: \.name) { option in
HStack(alignment: .firstTextBaseline) {
Text(option.name)
.monospaced()
Spacer()
Text(option.value)
.monospaced()
.foregroundStyle(.secondary)
.multilineTextAlignment(.trailing)
}
}
}
#endif
} }
/// Default MPV options from MPVClient.configureDefaultOptions() /// Default MPV options from MPVClient.configureDefaultOptions()
@@ -135,7 +132,7 @@ private struct CustomOptionsSection: View {
@Binding var editingOption: (name: String, value: String)? @Binding var editingOption: (name: String, value: String)?
var body: some View { var body: some View {
Section { SettingsFormSection("settings.mpvOptions.customOptions", footer: "settings.mpvOptions.customOptions.footer") {
let sortedOptions = settings.customMPVOptions.sorted { $0.key < $1.key } let sortedOptions = settings.customMPVOptions.sorted { $0.key < $1.key }
if sortedOptions.isEmpty { if sortedOptions.isEmpty {
@@ -153,29 +150,46 @@ private struct CustomOptionsSection: View {
Text(value) Text(value)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.contentShape(Rectangle())
} }
} #if os(macOS)
.onDelete { indexSet in .buttonStyle(.plain)
var options = settings.customMPVOptions #endif
for index in indexSet { #if os(iOS)
let key = sortedOptions[index].key .swipeActions(edge: .trailing, allowsFullSwipe: true) {
options.removeValue(forKey: key) Button(role: .destructive) {
removeOption(named: name)
} label: {
Label(String(localized: "common.delete"), systemImage: "trash")
}
}
#endif
.contextMenu {
Button(role: .destructive) {
removeOption(named: name)
} label: {
Label(String(localized: "common.delete"), systemImage: "trash")
}
} }
settings.customMPVOptions = options
} }
} }
Button { HStack {
showingAddSheet = true Button {
} label: { showingAddSheet = true
Label(String(localized: "settings.mpvOptions.addOption"), systemImage: "plus") } label: {
Label(String(localized: "settings.mpvOptions.addOption"), systemImage: "plus")
}
Spacer()
} }
} header: {
Text(String(localized: "settings.mpvOptions.customOptions"))
} footer: {
Text(String(localized: "settings.mpvOptions.customOptions.footer"))
} }
} }
private func removeOption(named name: String) {
var options = settings.customMPVOptions
options.removeValue(forKey: name)
settings.customMPVOptions = options
}
} }
// MARK: - Add MPV Option Sheet // MARK: - Add MPV Option Sheet
@@ -187,7 +201,69 @@ private struct AddMPVOptionSheet: View {
@State private var optionName = "" @State private var optionName = ""
@State private var optionValue = "" @State private var optionValue = ""
private var trimmedName: String {
optionName.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var trimmedValue: String {
optionValue.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var canSave: Bool { !trimmedName.isEmpty && !trimmedValue.isEmpty }
private func save() {
if canSave {
var options = settings.customMPVOptions
options[trimmedName] = trimmedValue
settings.customMPVOptions = options
}
dismiss()
}
var body: some View { var body: some View {
#if os(macOS)
VStack(alignment: .leading, spacing: 16) {
Text(String(localized: "settings.mpvOptions.addOption.title"))
.font(.headline)
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 12, verticalSpacing: 10) {
GridRow {
Text(String(localized: "settings.mpvOptions.optionName"))
.gridColumnAlignment(.trailing)
TextField("", text: $optionName)
.textFieldStyle(.roundedBorder)
.autocorrectionDisabled()
}
GridRow {
Text(String(localized: "settings.mpvOptions.optionValue"))
.gridColumnAlignment(.trailing)
TextField("", text: $optionValue)
.textFieldStyle(.roundedBorder)
.autocorrectionDisabled()
}
}
Text(String(localized: "settings.mpvOptions.addOption.footer"))
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
HStack {
Spacer()
Button(String(localized: "common.cancel")) {
dismiss()
}
.keyboardShortcut(.cancelAction)
Button(String(localized: "common.add")) {
save()
}
.keyboardShortcut(.defaultAction)
.disabled(!canSave)
}
}
.padding(20)
.frame(minWidth: 420)
#else
NavigationStack { NavigationStack {
Form { Form {
Section { Section {
@@ -195,18 +271,14 @@ private struct AddMPVOptionSheet: View {
String(localized: "settings.mpvOptions.optionName"), String(localized: "settings.mpvOptions.optionName"),
text: $optionName text: $optionName
) )
#if os(iOS)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
#endif
.autocorrectionDisabled() .autocorrectionDisabled()
TextField( TextField(
String(localized: "settings.mpvOptions.optionValue"), String(localized: "settings.mpvOptions.optionValue"),
text: $optionValue text: $optionValue
) )
#if os(iOS)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
#endif
.autocorrectionDisabled() .autocorrectionDisabled()
} footer: { } footer: {
Text(String(localized: "settings.mpvOptions.addOption.footer")) Text(String(localized: "settings.mpvOptions.addOption.footer"))
@@ -224,22 +296,12 @@ private struct AddMPVOptionSheet: View {
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
Button(String(localized: "common.add")) { Button(String(localized: "common.add")) {
let name = optionName.trimmingCharacters(in: .whitespacesAndNewlines) save()
let value = optionValue.trimmingCharacters(in: .whitespacesAndNewlines)
if !name.isEmpty && !value.isEmpty {
var options = settings.customMPVOptions
options[name] = value
settings.customMPVOptions = options
}
dismiss()
} }
.disabled(optionName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || .disabled(!canSave)
optionValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
} }
} }
} }
#if os(macOS)
.frame(minWidth: 400, minHeight: 200)
#endif #endif
} }
} }
@@ -256,7 +318,89 @@ private struct EditMPVOptionSheet: View {
@State private var optionValue = "" @State private var optionValue = ""
@State private var showingDeleteConfirmation = false @State private var showingDeleteConfirmation = false
private var trimmedValue: String {
optionValue.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var canSave: Bool { !trimmedValue.isEmpty }
private func save() {
if canSave {
var options = settings.customMPVOptions
options[originalName] = trimmedValue
settings.customMPVOptions = options
}
dismiss()
}
private func delete() {
var options = settings.customMPVOptions
options.removeValue(forKey: originalName)
settings.customMPVOptions = options
dismiss()
}
var body: some View { var body: some View {
#if os(macOS)
VStack(alignment: .leading, spacing: 16) {
Text(String(localized: "settings.mpvOptions.editOption.title"))
.font(.headline)
Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 12, verticalSpacing: 10) {
GridRow {
Text(String(localized: "settings.mpvOptions.optionName"))
.gridColumnAlignment(.trailing)
Text(originalName)
.monospaced()
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
}
GridRow {
Text(String(localized: "settings.mpvOptions.optionValue"))
.gridColumnAlignment(.trailing)
TextField("", text: $optionValue)
.textFieldStyle(.roundedBorder)
.autocorrectionDisabled()
}
}
HStack {
Button(role: .destructive) {
showingDeleteConfirmation = true
} label: {
Label(String(localized: "settings.mpvOptions.deleteOption"), systemImage: "trash")
}
Spacer()
Button(String(localized: "common.cancel")) {
dismiss()
}
.keyboardShortcut(.cancelAction)
Button(String(localized: "common.save")) {
save()
}
.keyboardShortcut(.defaultAction)
.disabled(!canSave)
}
}
.padding(20)
.frame(minWidth: 420)
.confirmationDialog(
String(localized: "settings.mpvOptions.deleteOption.confirmation"),
isPresented: $showingDeleteConfirmation,
titleVisibility: .visible
) {
Button(String(localized: "common.delete"), role: .destructive) {
delete()
}
Button(String(localized: "common.cancel"), role: .cancel) {}
}
.onAppear {
optionValue = initialValue
}
#else
NavigationStack { NavigationStack {
Form { Form {
Section { Section {
@@ -268,9 +412,7 @@ private struct EditMPVOptionSheet: View {
String(localized: "settings.mpvOptions.optionValue"), String(localized: "settings.mpvOptions.optionValue"),
text: $optionValue text: $optionValue
) )
#if os(iOS)
.textInputAutocapitalization(.never) .textInputAutocapitalization(.never)
#endif
.autocorrectionDisabled() .autocorrectionDisabled()
} }
@@ -294,15 +436,9 @@ private struct EditMPVOptionSheet: View {
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
Button(String(localized: "common.save")) { Button(String(localized: "common.save")) {
let value = optionValue.trimmingCharacters(in: .whitespacesAndNewlines) save()
if !value.isEmpty {
var options = settings.customMPVOptions
options[originalName] = value
settings.customMPVOptions = options
}
dismiss()
} }
.disabled(optionValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) .disabled(!canSave)
} }
} }
.confirmationDialog( .confirmationDialog(
@@ -311,21 +447,16 @@ private struct EditMPVOptionSheet: View {
titleVisibility: .visible titleVisibility: .visible
) { ) {
Button(String(localized: "common.delete"), role: .destructive) { Button(String(localized: "common.delete"), role: .destructive) {
var options = settings.customMPVOptions delete()
options.removeValue(forKey: originalName)
settings.customMPVOptions = options
dismiss()
} }
Button(String(localized: "common.cancel"), role: .cancel) {} Button(String(localized: "common.cancel"), role: .cancel) {}
} }
.presentationCompactAdaptation(.sheet) .presentationCompactAdaptation(.sheet)
} }
#if os(macOS)
.frame(minWidth: 400, minHeight: 200)
#endif
.onAppear { .onAppear {
optionValue = initialValue optionValue = initialValue
} }
#endif
} }
} }