mirror of
https://github.com/yattee/yattee.git
synced 2026-07-21 06:42:01 +00:00
Render the macOS control bar button row and top bar from the active preset's sections via a new MacOSControlsSectionRenderer, replacing the hardcoded QuickTime-style rows in MacOSControlBar. Add a keepOnTop button type and macOS-specific button availability lists, and adapt the player controls settings editors (preset editor, section editor, button configuration) for macOS. Also apply the preset font style to the control bar time labels, and update the built-in macOS Default preset order (queue before transport, fullscreen at the end), bumping builtInPresetsVersion to 7.
233 lines
6.2 KiB
Swift
233 lines
6.2 KiB
Swift
//
|
|
// PresetEditorView.swift
|
|
// Yattee
|
|
//
|
|
// Sheet view for creating, renaming, or duplicating a preset.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
/// Mode for the preset editor.
|
|
enum PresetEditorMode {
|
|
case create(baseLayouts: [LayoutPreset], activePreset: LayoutPreset?)
|
|
case rename(currentName: String)
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .create:
|
|
return String(localized: "settings.playerControls.newPreset")
|
|
case .rename:
|
|
return String(localized: "settings.playerControls.renamePreset")
|
|
}
|
|
}
|
|
|
|
var placeholder: String {
|
|
switch self {
|
|
case .create, .rename:
|
|
return String(localized: "settings.playerControls.presetNamePlaceholder")
|
|
}
|
|
}
|
|
|
|
var initialValue: String {
|
|
switch self {
|
|
case .create:
|
|
return ""
|
|
case .rename(let currentName):
|
|
return currentName
|
|
}
|
|
}
|
|
|
|
var saveButtonTitle: String {
|
|
switch self {
|
|
case .create:
|
|
return String(localized: "settings.playerControls.create")
|
|
case .rename:
|
|
return String(localized: "settings.playerControls.save")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Sheet view for creating or renaming a preset.
|
|
struct PresetEditorView: View {
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
let mode: PresetEditorMode
|
|
let onSave: (String, UUID?) -> Void
|
|
|
|
@State private var name: String
|
|
@State private var selectedBaseLayoutID: UUID?
|
|
@FocusState private var isNameFocused: Bool
|
|
|
|
private var baseLayouts: [LayoutPreset] {
|
|
if case .create(let layouts, _) = mode {
|
|
return layouts
|
|
}
|
|
return []
|
|
}
|
|
|
|
init(mode: PresetEditorMode, onSave: @escaping (String, UUID?) -> Void) {
|
|
self.mode = mode
|
|
self.onSave = onSave
|
|
_name = State(initialValue: mode.initialValue)
|
|
|
|
// Set default selection to active preset for create mode
|
|
if case .create(_, let activePreset) = mode {
|
|
_selectedBaseLayoutID = State(initialValue: activePreset?.id)
|
|
}
|
|
}
|
|
|
|
private var trimmedName: String {
|
|
name.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
}
|
|
|
|
private var isValid: Bool {
|
|
!trimmedName.isEmpty && trimmedName.count <= LayoutPreset.maxNameLength
|
|
}
|
|
|
|
var body: some View {
|
|
#if os(macOS)
|
|
macOSDialog
|
|
#else
|
|
NavigationStack {
|
|
Form {
|
|
// Base Layout Picker (only for create mode)
|
|
if case .create(_, _) = mode {
|
|
Section {
|
|
basePicker
|
|
}
|
|
}
|
|
|
|
Section {
|
|
nameField
|
|
} footer: {
|
|
nameFooter
|
|
}
|
|
}
|
|
.navigationTitle(mode.title)
|
|
#if os(iOS)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
#endif
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button(String(localized: "settings.playerControls.cancel")) {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
Button(mode.saveButtonTitle) {
|
|
saveIfValid()
|
|
}
|
|
.disabled(!isValid)
|
|
}
|
|
}
|
|
.onAppear {
|
|
isNameFocused = true
|
|
}
|
|
}
|
|
#if os(iOS)
|
|
.presentationDetents([.medium])
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
// MARK: - Shared Controls
|
|
|
|
private var basePicker: some View {
|
|
Picker(
|
|
String(localized: "settings.playerControls.baseLayout"),
|
|
selection: $selectedBaseLayoutID
|
|
) {
|
|
ForEach(baseLayouts) { preset in
|
|
Text(preset.name).tag(preset.id as UUID?)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var nameField: some View {
|
|
TextField(mode.placeholder, text: $name)
|
|
.focused($isNameFocused)
|
|
.submitLabel(.done)
|
|
.onSubmit(saveIfValid)
|
|
}
|
|
|
|
private var nameFooter: some View {
|
|
HStack {
|
|
if trimmedName.count > LayoutPreset.maxNameLength {
|
|
Text(String(localized: "settings.playerControls.nameTooLong"))
|
|
.foregroundStyle(.red)
|
|
}
|
|
Spacer()
|
|
Text("\(trimmedName.count)/\(LayoutPreset.maxNameLength)")
|
|
.foregroundStyle(
|
|
trimmedName.count > LayoutPreset.maxNameLength ? .red : .secondary
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - macOS Dialog
|
|
|
|
#if os(macOS)
|
|
/// Compact dialog layout matching native macOS sheets (title, fields,
|
|
/// trailing action buttons) instead of the iOS navigation-bar form.
|
|
private var macOSDialog: some View {
|
|
VStack(alignment: .leading, spacing: 16) {
|
|
Text(mode.title)
|
|
.font(.headline)
|
|
|
|
if case .create(_, _) = mode {
|
|
basePicker
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
nameField
|
|
.textFieldStyle(.roundedBorder)
|
|
|
|
nameFooter
|
|
.font(.caption)
|
|
}
|
|
|
|
HStack {
|
|
Spacer()
|
|
Button(String(localized: "settings.playerControls.cancel")) {
|
|
dismiss()
|
|
}
|
|
.keyboardShortcut(.cancelAction)
|
|
|
|
Button(mode.saveButtonTitle) {
|
|
saveIfValid()
|
|
}
|
|
.keyboardShortcut(.defaultAction)
|
|
.disabled(!isValid)
|
|
}
|
|
}
|
|
.padding(20)
|
|
.frame(width: 400)
|
|
.onAppear {
|
|
isNameFocused = true
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private func saveIfValid() {
|
|
guard isValid else { return }
|
|
onSave(trimmedName, selectedBaseLayoutID)
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
// MARK: - Preview
|
|
|
|
#Preview("Create") {
|
|
PresetEditorView(
|
|
mode: .create(
|
|
baseLayouts: LayoutPreset.allBuiltIn(),
|
|
activePreset: LayoutPreset.defaultPreset()
|
|
)
|
|
) { _, _ in }
|
|
}
|
|
|
|
#Preview("Rename") {
|
|
PresetEditorView(mode: .rename(currentName: "My Custom Preset")) { _, _ in }
|
|
}
|