Files
yattee/Yattee/Views/Player/Gestures/PlayerGestureOverlay.swift
2026-02-08 18:33:56 +01:00

118 lines
4.2 KiB
Swift

//
// PlayerGestureOverlay.swift
// Yattee
//
// SwiftUI overlay for handling player gestures.
//
#if os(iOS)
import SwiftUI
import UIKit
/// Overlay view that handles tap and seek gestures on the player.
struct PlayerGestureOverlay: View {
let settings: GesturesSettings
let isActive: Bool
let isSeekable: Bool
let onTapAction: (TapGestureAction, TapZonePosition) -> Void
let onSingleTap: () -> Void
let onSeekGestureStarted: () -> Void
let onSeekGestureChanged: (CGFloat) -> Void
let onSeekGestureEnded: (CGFloat) -> Void
/// Returns true if a pinch gesture is currently active (blocks seek gesture).
var isPinchGestureActive: (() -> Bool)? = nil
/// Returns true if panel drag is active (blocks seek gesture).
var isPanelDragging: (() -> Bool)? = nil
var body: some View {
GeometryReader { geometry in
GestureRecognizerView(
settings: settings,
bounds: CGRect(origin: .zero, size: geometry.size),
isActive: isActive,
isSeekable: isSeekable,
onDoubleTap: { position in
if let config = settings.tapGestures.configuration(for: position) {
onTapAction(config.action, position)
}
},
onSingleTap: onSingleTap,
onSeekGestureStarted: onSeekGestureStarted,
onSeekGestureChanged: onSeekGestureChanged,
onSeekGestureEnded: onSeekGestureEnded,
isPinchGestureActive: isPinchGestureActive,
isPanelDragging: isPanelDragging
)
}
// Always allow hit testing - single tap to toggle controls should work
// regardless of whether controls are visible. The coordinator handles
// disabling double-tap gestures when isActive is false.
.allowsHitTesting(true)
}
}
// MARK: - UIViewRepresentable
private struct GestureRecognizerView: UIViewRepresentable {
let settings: GesturesSettings
let bounds: CGRect
let isActive: Bool
let isSeekable: Bool
let onDoubleTap: (TapZonePosition) -> Void
let onSingleTap: () -> Void
let onSeekGestureStarted: () -> Void
let onSeekGestureChanged: (CGFloat) -> Void
let onSeekGestureEnded: (CGFloat) -> Void
var isPinchGestureActive: (() -> Bool)? = nil
var isPanelDragging: (() -> Bool)? = nil
func makeCoordinator() -> PlayerGestureCoordinator {
PlayerGestureCoordinator(
tapSettings: settings.tapGestures,
seekSettings: settings.seekGesture
)
}
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let coordinator = context.coordinator
coordinator.isActive = isActive
coordinator.isSeekable = isSeekable
coordinator.onDoubleTap = onDoubleTap
coordinator.onSingleTap = onSingleTap
coordinator.onSeekGestureStarted = onSeekGestureStarted
coordinator.onSeekGestureChanged = onSeekGestureChanged
coordinator.onSeekGestureEnded = onSeekGestureEnded
coordinator.isPinchGestureActive = isPinchGestureActive
coordinator.isPanelDragging = isPanelDragging
coordinator.attach(to: view)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
let coordinator = context.coordinator
coordinator.tapSettings = settings.tapGestures
coordinator.seekSettings = settings.seekGesture
coordinator.bounds = bounds
coordinator.isActive = isActive
coordinator.isSeekable = isSeekable
// Update callbacks
coordinator.onDoubleTap = onDoubleTap
coordinator.onSingleTap = onSingleTap
coordinator.onSeekGestureStarted = onSeekGestureStarted
coordinator.onSeekGestureChanged = onSeekGestureChanged
coordinator.onSeekGestureEnded = onSeekGestureEnded
coordinator.isPinchGestureActive = isPinchGestureActive
coordinator.isPanelDragging = isPanelDragging
}
static func dismantleUIView(_ uiView: UIView, coordinator: PlayerGestureCoordinator) {
coordinator.detach()
}
}
#endif