jellyflood/Swiftfin/Components/GestureView.swift

178 lines
5.2 KiB
Swift

//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
//
import Combine
import Foundation
import Logging
import SwiftUI
// TODO: figure out this directional response stuff
extension EnvironmentValues {
@Entry
var panGestureDirection: Direction = .all
}
struct GestureView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView(frame: .zero)
view.addGestureRecognizer(context.coordinator.longPressGesture)
view.addGestureRecognizer(context.coordinator.panGesture)
view.addGestureRecognizer(context.coordinator.pinchGesture)
view.addGestureRecognizer(context.coordinator.tapGesture)
view.addGestureRecognizer(context.coordinator.doubleTouchGesture)
view.backgroundColor = .clear
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
context.coordinator.longPressAction = context.environment.longPressAction
context.coordinator.panAction = context.environment.panAction
context.coordinator.pinchAction = context.environment.pinchAction
context.coordinator.tapAction = context.environment.tapGestureAction
context.coordinator.panGesture.direction = context.environment.panGestureDirection
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
lazy var doubleTouchGesture: UITapGestureRecognizer! = {
let recognizer = UITapGestureRecognizer(
target: self,
action: #selector(handleTap)
)
recognizer.numberOfTouchesRequired = 2
return recognizer
}()
lazy var longPressGesture: UILongPressGestureRecognizer! = {
let recognizer = UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress)
)
recognizer.minimumPressDuration = 1.2
return recognizer
}()
lazy var panGesture: DirectionalPanGestureRecognizer! = {
.init(
direction: .allButDown,
target: self,
action: #selector(handlePan)
)
}()
lazy var pinchGesture: UIPinchGestureRecognizer! = {
.init(
target: self,
action: #selector(handlePinch)
)
}()
lazy var tapGesture: UITapGestureRecognizer! = {
.init(
target: self,
action: #selector(handleTap)
)
}()
var longPressAction: LongPressAction? {
didSet { longPressGesture.isEnabled = longPressAction != nil }
}
var panAction: PanAction? {
didSet { panGesture.isEnabled = panAction != nil }
}
var pinchAction: PinchAction? {
didSet { pinchGesture.isEnabled = pinchAction != nil }
}
var tapAction: TapAction? {
didSet {
doubleTouchGesture.isEnabled = tapAction != nil
tapGesture.isEnabled = tapAction != nil
}
}
private var didSwipe = false
@objc
func handleLongPress(_ gesture: UILongPressGestureRecognizer) {
guard let view = gesture.view else { return }
let location = gesture.location(in: view)
let unitPoint = UnitPoint(
x: location.x / view.bounds.width,
y: location.y / view.bounds.height
)
longPressAction?(
location: location,
unitPoint: unitPoint,
state: gesture.state
)
}
@objc
func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let view = gesture.view else { return }
let translation = gesture.translation(in: view)
let velocity = gesture.velocity(in: view)
let location = gesture.location(in: view)
let unitPoint = UnitPoint(
x: location.x / view.bounds.width,
y: location.y / view.bounds.height
)
panAction?(
translation: translation,
velocity: velocity,
location: location,
unitPoint: unitPoint,
state: gesture.state
)
}
@objc
func handlePinch(_ gesture: UIPinchGestureRecognizer) {
pinchAction?(
scale: gesture.scale,
velocity: gesture.velocity,
state: gesture.state
)
}
@objc
func handleTap(_ gesture: UITapGestureRecognizer) {
guard let view = gesture.view else { return }
let location = gesture.location(in: gesture.view)
let unitPoint = UnitPoint(
x: location.x / view.bounds.width,
y: location.y / view.bounds.height
)
tapAction?(
location: location,
unitPoint: unitPoint,
count: gesture.numberOfTouches
)
}
}
}