Add contextual tooltips for project editing and result preview

This commit is contained in:
Mikei386
2026-09-13 09:27:37 +02:00
parent 73961f6afe
commit 0f64561062
+18 -3
View File
@@ -35,7 +35,7 @@ struct ContentView: View {
Button("Projekt laden") {
selectProjectFile()
}
.help("Lädt eine gespeicherte .growthlapse-project.json, inklusive Segmentfenstern und Variantenwahl.")
.help("Öffnet ein gespeichertes Projekt mit Ausschnitten, Varianten und Render-Einstellungen. Deine lokalen FFmpeg-Pfade bleiben erhalten.")
}
.disabled(processor.state.isRunning)
@@ -67,6 +67,7 @@ struct ContentView: View {
settings.ffprobeExecutable = nil
}
.disabled(settings.ffmpegExecutable == nil && settings.ffprobeExecutable == nil)
.help("Entfernt die manuell gewählten Werkzeugpfade und sucht FFmpeg und FFprobe automatisch auf diesem Mac.")
}
.disabled(processor.state.isRunning)
@@ -147,7 +148,7 @@ struct ContentView: View {
Toggle("Segment aus der Mitte nehmen", isOn: $settings.takeMiddleSegment)
.help("Wählt den Ausschnitt aus der Videomitte. Start-Offset verschiebt diesen Mittelpunkt.")
Toggle("Zwischenclips behalten", isOn: $settings.keepIntermediateClips)
.help("Behält segment_*, stabilized_*, clip_* und xfade_* Dateien zur Kontrolle. Sonst wird der Temp-Ordner nach Erfolg gelöscht.")
.help("Behält nach erfolgreichem Rendern zusätzliche Zwischenstufen zur Kontrolle. Der Cache für die Nachbearbeitung wird separat eingestellt.")
Toggle("Render-Cache für Nachbearbeitung behalten", isOn: $settings.keepRenderCacheForReview)
.help("Behält die normalisierten clip_*.mp4 Dateien und eine Projektdatei neben dem Output. So können einzelne Segmentfenster später angepasst und schneller neu gerendert werden.")
Toggle("Clipnummer einblenden", isOn: $settings.burnInClipNumbers)
@@ -203,6 +204,7 @@ struct ContentView: View {
.help("Angestrebte Gesichtshöhe relativ zur Ausgabehöhe. 28% ist ein guter Startwert für Portrait-/GrowthLapse-Material.")
Slider(value: $settings.targetFaceHeightRatio, in: 0.18...0.40, step: 0.01)
.help("Gewünschte Gesichtshöhe im Ergebnis: 26 % entspricht etwa einem Viertel der Bildhöhe. Bildränder können die Anpassung begrenzen.")
.disabled(!settings.faceNormalizationEnabled)
}
.disabled(processor.state.isRunning)
@@ -219,7 +221,7 @@ struct ContentView: View {
Button("STOP") {
processor.cancel()
}
.help("Bricht den laufenden ffmpeg-Prozess ab und räumt temporäre Dateien auf.")
.help("Stoppt den laufenden Render einschließlich Bildanalyse. Das bisherige Ergebnis und dessen Cache bleiben erhalten.")
.tint(.red)
.disabled(!processor.state.isRunning)
@@ -367,6 +369,7 @@ struct ContentView: View {
.font(.caption)
.foregroundStyle(.secondary)
Button("Ergebnis ansehen") { showingResult = true }
.help("Öffnet das zuletzt gespeicherte Ergebnisvideo in der App. Noch nicht gerenderte Clipänderungen sind darin nicht enthalten.")
.disabled(!FileManager.default.fileExists(atPath: project.outputFilePath))
}
HStack {
@@ -384,7 +387,9 @@ struct ContentView: View {
Spacer(minLength: 0)
Menu("Projekt") {
Button("Projekt speichern") { processor.saveCurrentProject() }
.help("Speichert Ausschnitte und Varianten. Render-Einstellungen werden beim erfolgreichen Rendern ins Projekt übernommen.")
Button("Render-Cache löschen") { processor.confirmProjectAndDeleteCache() }
.help("Gibt den Speicherplatz der Zwischenclips frei. Video, Projekt und Quellen bleiben erhalten. Beim nächsten Rendern werden die Zwischenclips neu erstellt.")
}
.fixedSize()
.help("Projekt speichern oder den zugehörigen Cache löschen. Video und Quelldateien bleiben erhalten.")
@@ -488,27 +493,34 @@ struct ContentView: View {
if maxStart > 0 {
Slider(value: startBinding, in: 0...maxStart, step: 0.1)
.help("Verschiebt den Ausschnitt in Schritten von 0,1 Sekunden. Die Änderung wird gespeichert und mit ‚Änderungen neu rendern‘ ins Video übernommen.")
} else {
Slider(value: .constant(0), in: 0...1)
.disabled(true)
.help("Der Ausschnitt umfasst bereits die ganze Quelle und kann nicht verschoben werden.")
}
HStack {
Button("-1 s") {
processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 1)
}
.help("Verschiebt den Ausschnitt um eine Sekunde nach vorn zum Videoanfang. Die Videogrenzen werden eingehalten.")
Button("-0,5 s") {
processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 0.5)
}
.help("Verschiebt den Ausschnitt um eine halbe Sekunde zum Videoanfang. Die Videogrenzen werden eingehalten.")
Button("Zur Mitte") {
processor.updateClip(clip, segmentStart: max(0, (liveClip.duration - liveClip.segmentLength) / 2 + settings.startOffset))
}
.help("Setzt den Ausschnitt in die Videomitte und berücksichtigt den eingestellten Start-Offset.")
Button("+0,5 s") {
processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 0.5)
}
.help("Verschiebt den Ausschnitt um eine halbe Sekunde zum Videoende. Die Videogrenzen werden eingehalten.")
Button("+1 s") {
processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 1)
}
.help("Verschiebt den Ausschnitt um eine Sekunde zum Videoende. Die Videogrenzen werden eingehalten.")
}
.controlSize(.small)
.disabled(processor.state.isRunning || maxStart <= 0)
@@ -879,6 +891,7 @@ private struct ClipVariantPickerView: View {
Button("Schließen") {
dismiss()
}
.help("Schließt die Variantenwahl, ohne eine andere Variante zu übernehmen.")
}
if let selectedVariant {
@@ -907,6 +920,7 @@ private struct ClipVariantPickerView: View {
onSelect(variant)
}
.disabled(variant.sourcePath == clip.sourcePath)
.help("Übernimmt diese Aufnahme für den Clip. Anschließend neu rendern, damit sie im Ergebnis erscheint.")
}
.contentShape(Rectangle())
.onTapGesture {
@@ -984,6 +998,7 @@ private struct OutputPreviewView: View {
Text(url.lastPathComponent).font(.headline)
Spacer()
Button("Schließen") { dismiss() }
.help("Schließt die Ergebnisvorschau und stoppt ihre Wiedergabe. Auch mit Escape möglich.")
.keyboardShortcut(.cancelAction)
}
VideoPlayer(player: player)