feat: add familiar single-key playback controls

This commit is contained in:
2026-10-10 17:30:48 +02:00
parent bdae024c7f
commit 310be5aa61
2 changed files with 50 additions and 18 deletions
+36 -14
View File
@@ -492,7 +492,7 @@ private struct PlayerContent: View {
}
.buttonStyle(.plain)
.accessibilityLabel(model.state.status == PLAYER_PLAYING ? "Pause" : "Play")
.help(model.state.status == PLAYER_PLAYING ? "Pause (Space)" : "Play (Space)")
.help(model.state.status == PLAYER_PLAYING ? "Pause (K / Space)" : "Play (K / Space)")
.disabled(!model.canTogglePlayback)
iconButton("forward.fill", "Seek Forward 5 Seconds") { model.seekRelative(5) }
.disabled(!model.canSeek)
@@ -685,6 +685,35 @@ private struct WindowChrome: NSViewRepresentable {
}
required init?(coder: NSCoder) { nil }
func handleKeyEvent(_ event: NSEvent) -> Bool {
guard event.type == .keyDown, let window = configuredWindow, event.window === window,
!(window.firstResponder is NSTextView),
event.modifierFlags.intersection([.command, .control, .option]).isEmpty else { return false }
if event.keyCode == 53 && model.isFullScreen {
if !event.isARepeat { model.toggleFullScreen() }
return true
}
let key = event.charactersIgnoringModifiers?.lowercased()
switch key {
case "j": model.seekRelative(-10)
case "l": model.seekRelative(10)
case "k", " ":
if !event.isARepeat { model.togglePlayback() }
case "m":
if !event.isARepeat { model.toggleMute() }
case "f":
if !event.isARepeat { model.toggleFullScreen() }
case "p":
if !event.isARepeat { model.toggleOnTop() }
case "o":
if !event.isARepeat { model.openPanel() }
case "q":
if !event.isARepeat { NSApp.terminate(nil) }
default: return false
}
return true
}
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
guard let window else { detach(); return }
@@ -713,17 +742,8 @@ private struct WindowChrome: NSViewRepresentable {
if event.type == .keyDown && event.keyCode == 48 { self.model.revealControls() }
if event.type == .mouseMoved || event.type == .leftMouseDown {
self.model.revealControls()
} else if event.type == .keyDown && !(event.window?.firstResponder is NSSlider) &&
!(event.window?.firstResponder is NSTextView) {
if event.keyCode == 53 && self.model.isFullScreen {
self.model.toggleFullScreen()
return true
}
if event.modifierFlags.intersection([.command, .control, .option]).isEmpty &&
event.charactersIgnoringModifiers?.lowercased() == "f" {
self.model.toggleFullScreen()
return true
}
} else if event.type == .keyDown {
return self.handleKeyEvent(event)
}
return false
}
@@ -797,18 +817,20 @@ struct PlayerApp: App {
}
CommandMenu("Playback") {
Button(model.state.status == PLAYER_PLAYING ? "Pause" : "Play", action: model.togglePlayback)
.keyboardShortcut(.space, modifiers: []).disabled(!model.canTogglePlayback)
.disabled(!model.canTogglePlayback)
Button("Seek Back 5 Seconds") { model.seekRelative(-5) }
.keyboardShortcut(.leftArrow, modifiers: []).disabled(!model.canSeek)
Button("Seek Forward 5 Seconds") { model.seekRelative(5) }
.keyboardShortcut(.rightArrow, modifiers: []).disabled(!model.canSeek)
Button("Seek Back 10 Seconds") { model.seekRelative(-10) }.disabled(!model.canSeek)
Button("Seek Forward 10 Seconds") { model.seekRelative(10) }.disabled(!model.canSeek)
Divider()
Button("Volume Up") { model.setVolume(min(1, model.volume + 0.05)) }
.keyboardShortcut(.upArrow, modifiers: []).disabled(!model.canSetVolume)
Button("Volume Down") { model.setVolume(max(0, model.volume - 0.05)) }
.keyboardShortcut(.downArrow, modifiers: []).disabled(!model.canSetVolume)
Button(model.volume == 0 ? "Unmute" : "Mute", action: model.toggleMute)
.keyboardShortcut("m", modifiers: []).disabled(!model.canSetVolume)
.disabled(!model.canSetVolume)
}
CommandGroup(after: .windowArrangement) {
Button(model.isFullScreen ? "Exit Full Screen" : "Enter Full Screen", action: model.toggleFullScreen)