mirror of
https://github.com/yattee/yattee.git
synced 2026-07-21 06:42:01 +00:00
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:
@@ -58,6 +58,11 @@ struct MacOSControlBar: View {
|
|||||||
playerState.isTransportDisabled
|
playerState.isTransportDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the controls are locked (playback buttons/gestures disabled except Settings).
|
||||||
|
private var isLocked: Bool {
|
||||||
|
playerState.isControlsLocked
|
||||||
|
}
|
||||||
|
|
||||||
private var playPauseIcon: String {
|
private var playPauseIcon: String {
|
||||||
switch playerState.playbackState {
|
switch playerState.playbackState {
|
||||||
case .playing:
|
case .playing:
|
||||||
@@ -133,11 +138,15 @@ struct MacOSControlBar: View {
|
|||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
// Left: Volume controls
|
// Left: Volume controls
|
||||||
volumeControls
|
volumeControls
|
||||||
|
.disabled(isLocked)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Center: Transport controls
|
// Center: Transport controls
|
||||||
transportControls
|
transportControls
|
||||||
|
.disabled(isLocked)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
@@ -264,6 +273,7 @@ struct MacOSControlBar: View {
|
|||||||
.gesture(
|
.gesture(
|
||||||
DragGesture(minimumDistance: 0)
|
DragGesture(minimumDistance: 0)
|
||||||
.onChanged { value in
|
.onChanged { value in
|
||||||
|
guard !isLocked else { return }
|
||||||
if !isDragging {
|
if !isDragging {
|
||||||
isDragging = true
|
isDragging = true
|
||||||
onInteractionStarted?()
|
onInteractionStarted?()
|
||||||
@@ -272,6 +282,7 @@ struct MacOSControlBar: View {
|
|||||||
dragProgress = progress
|
dragProgress = progress
|
||||||
}
|
}
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
|
guard !isLocked else { return }
|
||||||
isDragging = false
|
isDragging = false
|
||||||
let progress = max(0, min(1, value.location.x / geometry.size.width))
|
let progress = max(0, min(1, value.location.x / geometry.size.width))
|
||||||
let seekTime = progress * playerState.duration
|
let seekTime = progress * playerState.duration
|
||||||
@@ -288,8 +299,10 @@ struct MacOSControlBar: View {
|
|||||||
isHoveringProgress = false
|
isHoveringProgress = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.allowsHitTesting(!isLocked)
|
||||||
}
|
}
|
||||||
.frame(height: 20)
|
.frame(height: 20)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func seekPreviewPosition(
|
private func seekPreviewPosition(
|
||||||
@@ -338,6 +351,8 @@ struct MacOSControlBar: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
.buttonStyle(MacOSControlButtonStyle())
|
||||||
|
.disabled(isLocked)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// More (context menu)
|
// More (context menu)
|
||||||
@@ -351,6 +366,8 @@ struct MacOSControlBar: View {
|
|||||||
)
|
)
|
||||||
.menuStyle(.borderlessButton)
|
.menuStyle(.borderlessButton)
|
||||||
.fixedSize()
|
.fixedSize()
|
||||||
|
.disabled(isLocked)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
@@ -377,7 +394,8 @@ struct MacOSControlBar: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(MacOSControlButtonStyle())
|
.buttonStyle(MacOSControlButtonStyle())
|
||||||
.disabled(!playerState.isPiPPossible)
|
.disabled(isLocked || !playerState.isPiPPossible)
|
||||||
|
.opacity(isLocked ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ struct MacOSPlayerControlsView: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.disabled(onTitleTap == nil)
|
.disabled(onTitleTap == nil || playerState.isControlsLocked)
|
||||||
|
.opacity(playerState.isControlsLocked ? 0.5 : 1.0)
|
||||||
.help(Text("player.controls.info"))
|
.help(Text("player.controls.info"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +130,8 @@ struct MacOSPlayerControlsView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.help(Text("player.controls.keepOnTop"))
|
.help(Text("player.controls.keepOnTop"))
|
||||||
.accessibilityLabel(Text("player.controls.keepOnTop"))
|
.accessibilityLabel(Text("player.controls.keepOnTop"))
|
||||||
|
.disabled(playerState.isControlsLocked)
|
||||||
|
.opacity(playerState.isControlsLocked ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if onClose != nil {
|
if onClose != nil {
|
||||||
@@ -273,6 +276,24 @@ struct MacOSPlayerControlsView: View {
|
|||||||
// Only handle events when the player window is key (active)
|
// Only handle events when the player window is key (active)
|
||||||
guard NSApp.keyWindow != nil else { return event }
|
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
|
// Handle keyboard shortcuts
|
||||||
switch event.keyCode {
|
switch event.keyCode {
|
||||||
case 49: // Space
|
case 49: // Space
|
||||||
|
|||||||
Reference in New Issue
Block a user