Honor Lock Controls on macOS player

The macOS controls stack (MacOSPlayerControlsView + MacOSControlBar)
never read playerState.isControlsLocked, so locking via the settings
sheet had no effect: buttons, the scrubber, the volume slider, and the
AppKit keyboard shortcuts all kept working.

When locked, dim and disable transport, volume, the progress bar,
queue, the more menu, PiP, the pin/floating toggle, and the title-tap
details button; ignore playback keyboard shortcuts (space/arrows/mute)
while keeping F/Esc for fullscreen. Settings and Close stay enabled so
the user can reopen Settings to unlock.
This commit is contained in:
Arkadiusz Fal
2026-05-30 22:22:18 +02:00
parent 2f2e436fe2
commit 5950dbb22a
2 changed files with 41 additions and 2 deletions

View File

@@ -58,6 +58,11 @@ struct MacOSControlBar: View {
playerState.isTransportDisabled
}
/// Whether the controls are locked (playback buttons/gestures disabled except Settings).
private var isLocked: Bool {
playerState.isControlsLocked
}
private var playPauseIcon: String {
switch playerState.playbackState {
case .playing:
@@ -133,11 +138,15 @@ struct MacOSControlBar: View {
HStack(spacing: 0) {
// Left: Volume controls
volumeControls
.disabled(isLocked)
.opacity(isLocked ? 0.5 : 1.0)
Spacer()
// Center: Transport controls
transportControls
.disabled(isLocked)
.opacity(isLocked ? 0.5 : 1.0)
Spacer()
@@ -264,6 +273,7 @@ struct MacOSControlBar: View {
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
guard !isLocked else { return }
if !isDragging {
isDragging = true
onInteractionStarted?()
@@ -272,6 +282,7 @@ struct MacOSControlBar: View {
dragProgress = progress
}
.onEnded { value in
guard !isLocked else { return }
isDragging = false
let progress = max(0, min(1, value.location.x / geometry.size.width))
let seekTime = progress * playerState.duration
@@ -288,8 +299,10 @@ struct MacOSControlBar: View {
isHoveringProgress = false
}
}
.allowsHitTesting(!isLocked)
}
.frame(height: 20)
.opacity(isLocked ? 0.5 : 1.0)
}
private func seekPreviewPosition(
@@ -338,6 +351,8 @@ struct MacOSControlBar: View {
.contentShape(Rectangle())
}
.buttonStyle(MacOSControlButtonStyle())
.disabled(isLocked)
.opacity(isLocked ? 0.5 : 1.0)
}
// More (context menu)
@@ -351,6 +366,8 @@ struct MacOSControlBar: View {
)
.menuStyle(.borderlessButton)
.fixedSize()
.disabled(isLocked)
.opacity(isLocked ? 0.5 : 1.0)
}
// Settings
@@ -377,7 +394,8 @@ struct MacOSControlBar: View {
.contentShape(Rectangle())
}
.buttonStyle(MacOSControlButtonStyle())
.disabled(!playerState.isPiPPossible)
.disabled(isLocked || !playerState.isPiPPossible)
.opacity(isLocked ? 0.5 : 1.0)
}
}
}

View File

@@ -107,7 +107,8 @@ struct MacOSPlayerControlsView: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.disabled(onTitleTap == nil)
.disabled(onTitleTap == nil || playerState.isControlsLocked)
.opacity(playerState.isControlsLocked ? 0.5 : 1.0)
.help(Text("player.controls.info"))
}
@@ -129,6 +130,8 @@ struct MacOSPlayerControlsView: View {
.buttonStyle(.plain)
.help(Text("player.controls.keepOnTop"))
.accessibilityLabel(Text("player.controls.keepOnTop"))
.disabled(playerState.isControlsLocked)
.opacity(playerState.isControlsLocked ? 0.5 : 1.0)
}
if onClose != nil {
@@ -273,6 +276,24 @@ struct MacOSPlayerControlsView: View {
// Only handle events when the player window is key (active)
guard NSApp.keyWindow != nil else { return event }
// When controls are locked, ignore playback keys (space/arrows/mute) and
// only keep fullscreen (F) and exit-fullscreen (Esc) working.
if playerState.isControlsLocked {
switch event.keyCode {
case 3: // F
onToggleFullscreen?()
return nil
case 53: // Escape
if isFullscreen {
onToggleFullscreen?()
return nil
}
return event
default:
return event // Pass through space/arrows/M while locked
}
}
// Handle keyboard shortcuts
switch event.keyCode {
case 49: // Space