Block player dismiss pan from the panel resize grabber strip

Starting a column resize drag with a diagonal (downward) motion let the
window-level dismiss pan begin, cancelling the resize and dragging the
player down instead. The grabber now publishes its strip frame (with 8pt
slop on each side) to NavigationCoordinator, and the dismiss pan refuses
to begin from touches inside it — same exclusion pattern as the corner
layout's below-video panel frame.
This commit is contained in:
Arkadiusz Fal
2026-09-23 18:38:24 +02:00
parent 05a03723f6
commit 52ec8c9f05
3 changed files with 38 additions and 0 deletions

View File

@@ -162,6 +162,11 @@ final class NavigationCoordinator {
/// inside it must not start the sheet dismiss pan (drag handle conflict).
var cornerBelowPanelFrame: CGRect = .zero
/// Panel resize grabber strip frame in screen coordinates — touches inside
/// it must not start the sheet dismiss pan (a diagonal resize drag would
/// otherwise be cancelled and dismiss the player instead).
var panelResizeGrabberFrame: CGRect = .zero
/// Progress bar frame in screen coordinates (for gesture conflict resolution).
var progressBarFrame: CGRect = .zero

View File

@@ -146,6 +146,8 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe
var getPortraitPanelFrame: (() -> CGRect)?
/// Returns the corner layout below-video panel frame in screen coordinates (blocks dismiss pan)
var getCornerBelowPanelFrame: (() -> CGRect)?
/// Returns the panel resize grabber strip frame in screen coordinates (blocks dismiss pan)
var getPanelResizeGrabberFrame: (() -> CGRect)?
/// Returns whether the portrait panel is currently visible (not hidden off-screen)
var isPortraitPanelVisible: (() -> Bool)?
/// Returns the progress bar frame in screen coordinates (for gesture conflict resolution)
@@ -378,6 +380,16 @@ private final class DragToDismissGestureHandler: NSObject, UIGestureRecognizerDe
}
}
// Don't begin dismiss gesture from the panel resize grabber strip —
// a diagonal resize drag would otherwise be cancelled mid-resize
let grabberFrame = getPanelResizeGrabberFrame?() ?? .zero
if !grabberFrame.isEmpty {
let touchLocation = panGesture.location(in: nil)
if grabberFrame.contains(touchLocation) {
return false
}
}
// When portrait panel is visible, only allow dismiss from player area (above panel)
// This prevents race conditions where isPanelDragging isn't set yet when gesture begins
let panelFrame = getPortraitPanelFrame?() ?? .zero
@@ -860,6 +872,9 @@ final class ExpandedPlayerWindowManager {
handler.getCornerBelowPanelFrame = { [weak self] in
self?.appEnvironment?.navigationCoordinator.cornerBelowPanelFrame ?? .zero
}
handler.getPanelResizeGrabberFrame = { [weak self] in
self?.appEnvironment?.navigationCoordinator.panelResizeGrabberFrame ?? .zero
}
handler.isPortraitPanelVisible = { [weak self] in
self?.appEnvironment?.navigationCoordinator.isPortraitPanelVisible ?? true
}

View File

@@ -11,6 +11,8 @@ import SwiftUI
/// A draggable handle for resizing the panel width.
struct PanelResizeGrabber: View {
@Environment(\.appEnvironment) private var appEnvironment
@Binding var panelWidth: CGFloat
let minWidth: CGFloat
let maxWidth: CGFloat
@@ -54,6 +56,22 @@ struct PanelResizeGrabber: View {
.frame(width: 4, height: 36)
}
.gesture(dragGesture)
#if os(iOS)
// Publish the strip frame (with slop) so the window-level dismiss
// pan never begins from a resize drag — a diagonal start would
// otherwise cancel the resize and dismiss the player instead
.background {
GeometryReader { proxy in
Color.clear
.onChange(of: proxy.frame(in: .global), initial: true) { _, frame in
appEnvironment?.navigationCoordinator.panelResizeGrabberFrame = frame.insetBy(dx: -8, dy: 0)
}
.onDisappear {
appEnvironment?.navigationCoordinator.panelResizeGrabberFrame = .zero
}
}
}
#endif
#if os(macOS)
.onHover { hovering in
isHovering = hovering