fix: enable window dragging outside interactive controls
Use background-only native drag gestures so scroll content and overlays remain draggable without stealing control, text, or queue-row input. Compact mode uses the same explicit drag surfaces rather than broad native background movement.
This commit is contained in:
@@ -2101,6 +2101,7 @@ private struct PlayerContent: View {
|
||||
@ViewState private var queueFrames: [UUID: CGRect] = [:]
|
||||
@ViewState private var queueDrag: PlaybackQueueDrag?
|
||||
@GestureState private var queueDragging = false
|
||||
@GestureState private var windowDragging = false
|
||||
@ViewState private var queueScroll = ScrollPosition(y: 0)
|
||||
@ViewState private var queueScrollOffset: CGFloat = 0
|
||||
@ViewState private var queueViewportHeight: CGFloat = 0
|
||||
@@ -2228,6 +2229,8 @@ private struct PlayerContent: View {
|
||||
.padding(.horizontal, size.width < 600 ? 14 : 30)
|
||||
.padding(.top, 64)
|
||||
.padding(.bottom, 24)
|
||||
.frame(maxWidth: .infinity, minHeight: size.height, alignment: .topLeading)
|
||||
.background(windowDragBackground)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
@@ -2337,6 +2340,7 @@ private struct PlayerContent: View {
|
||||
.padding(.trailing, 92 - 88 * model.queueWidth / 260)
|
||||
.frame(height: 32)
|
||||
.foregroundStyle(Color(SunsetPalette.text))
|
||||
.background(windowDragBackground)
|
||||
.onHover(perform: model.setTitleHovered)
|
||||
.animation(controlsVisibilityAnimation) { content in
|
||||
content.opacity(model.controlsVisible ? 1 : 0)
|
||||
@@ -2357,6 +2361,7 @@ private struct PlayerContent: View {
|
||||
}
|
||||
.padding(.horizontal, 4 * (1 - model.queueWidth / 260))
|
||||
.frame(height: 24)
|
||||
.background(windowDragBackground)
|
||||
.background {
|
||||
PlayerGlass(cornerRadius: 8)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
@@ -2434,6 +2439,7 @@ private struct PlayerContent: View {
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, showsBufferReadout ? 6 : 8)
|
||||
.foregroundStyle(Color(SunsetPalette.text))
|
||||
.background(windowDragBackground)
|
||||
.background(PlayerGlass(cornerRadius: 10).clipShape(RoundedRectangle(cornerRadius: 10)))
|
||||
.onHover(perform: model.setControlsHovered)
|
||||
.animation(controlsVisibilityAnimation) { content in
|
||||
@@ -2673,6 +2679,8 @@ private struct PlayerContent: View {
|
||||
.highPriorityGesture(queueRowDrag(item))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: queueViewportHeight, alignment: .top)
|
||||
.background(windowDragBackground)
|
||||
.coordinateSpace(name: "queueContent")
|
||||
.onGeometryChange(for: CGFloat.self) { $0.size.height } action: { queueContentHeight = $0 }
|
||||
}
|
||||
@@ -2743,6 +2751,7 @@ private struct PlayerContent: View {
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.background(windowDragBackground)
|
||||
.background { queueChromeBackground(top: false) }
|
||||
}
|
||||
.frame(width: 260)
|
||||
@@ -2794,6 +2803,14 @@ private struct PlayerContent: View {
|
||||
.foregroundStyle(Color(SunsetPalette.text))
|
||||
.tint(Color(SunsetPalette.accent))
|
||||
.background(WindowChrome(model: model))
|
||||
.onChange(of: windowDragging) { _, active in model.setControlsDragging(active) }
|
||||
}
|
||||
|
||||
private var windowDragBackground: some View {
|
||||
Color.clear.contentShape(Rectangle())
|
||||
.gesture(WindowDragGesture()
|
||||
.updating($windowDragging) { _, active, _ in active = true })
|
||||
.padding(5) // Preserve the native resize border.
|
||||
}
|
||||
|
||||
private var videoContent: some View {
|
||||
@@ -2837,6 +2854,7 @@ private struct PlayerContent: View {
|
||||
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false))
|
||||
.padding(.top, 44).padding(.trailing, 16)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
if let error = model.errorText {
|
||||
VStack(spacing: 12) {
|
||||
@@ -2849,6 +2867,7 @@ private struct PlayerContent: View {
|
||||
.frame(maxWidth: 420)
|
||||
.background(PlayerGlass().clipShape(RoundedRectangle(cornerRadius: 12)).allowsHitTesting(false))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(windowDragBackground)
|
||||
} else if model.isBusy {
|
||||
VStack(spacing: 12) {
|
||||
ProgressView().controlSize(.large)
|
||||
@@ -2857,6 +2876,7 @@ private struct PlayerContent: View {
|
||||
.font(.callout).foregroundStyle(Color(SunsetPalette.secondaryText))
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.allowsHitTesting(false)
|
||||
} else if model.fileName == nil {
|
||||
startScreen(in: geometry.size)
|
||||
} else if let device = model.castDevice {
|
||||
@@ -2867,6 +2887,7 @@ private struct PlayerContent: View {
|
||||
}
|
||||
.foregroundStyle(Color(SunsetPalette.text))
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(windowDragBackground)
|
||||
} else if !model.state.has_video && model.state.has_audio {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "waveform").font(.system(size: 64, weight: .ultraLight))
|
||||
@@ -2875,6 +2896,7 @@ private struct PlayerContent: View {
|
||||
.foregroundStyle(Color(SunsetPalette.secondaryText))
|
||||
.padding(40)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
if model.state.buffering && !model.isBusy && model.errorText == nil {
|
||||
ProgressView("Buffering…")
|
||||
@@ -2928,6 +2950,7 @@ private struct PlayerContent: View {
|
||||
if model.canTogglePlayback {
|
||||
playbackControls
|
||||
.frame(width: 440, height: controlHeight)
|
||||
.background(windowDragBackground)
|
||||
.background {
|
||||
PlayerGlass()
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
|
||||
Reference in New Issue
Block a user