mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Changes applied: Still fucked but working
This commit is contained in:
parent
4136f6d9b9
commit
310dde6573
8 changed files with 18739 additions and 1538 deletions
|
|
@ -5,97 +5,107 @@
|
|||
// Created by Richard Kunkli on 21/08/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
extension View {
|
||||
func panGesture(direction: PanDirection, action: @escaping (CGFloat, NSEvent.Phase) -> Void) -> some View {
|
||||
background(
|
||||
PanGestureView(direction: direction, action: action)
|
||||
.frame(maxWidth: 0, maxHeight: 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct PanGestureView: NSViewRepresentable {
|
||||
let direction: PanDirection
|
||||
let action: (CGFloat, NSEvent.Phase) -> Void
|
||||
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
let view = NSView()
|
||||
NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { event in
|
||||
if event.window == view.window {
|
||||
context.coordinator.handleEvent(event)
|
||||
}
|
||||
return event
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: NSView, context: Context) {
|
||||
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(direction: direction, action: action)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject {
|
||||
let direction: PanDirection
|
||||
let action: (CGFloat, NSEvent.Phase) -> Void
|
||||
|
||||
var accumulatedScrollDeltaX: CGFloat = 0
|
||||
var accumulatedScrollDeltaY: CGFloat = 0
|
||||
|
||||
init(direction: PanDirection, action: @escaping (CGFloat, NSEvent.Phase) -> Void) {
|
||||
self.direction = direction
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@objc func handleEvent(_ event: NSEvent) {
|
||||
if event.type == .scrollWheel {
|
||||
accumulatedScrollDeltaX += event.scrollingDeltaX
|
||||
accumulatedScrollDeltaY += event.scrollingDeltaY
|
||||
|
||||
switch direction {
|
||||
case .down:
|
||||
if accumulatedScrollDeltaY > 0 {
|
||||
handle()
|
||||
}
|
||||
case .up:
|
||||
if accumulatedScrollDeltaY < 0 {
|
||||
handle()
|
||||
}
|
||||
case .left:
|
||||
if accumulatedScrollDeltaX < 0 {
|
||||
handle()
|
||||
}
|
||||
case .right:
|
||||
if accumulatedScrollDeltaX > 0 {
|
||||
handle()
|
||||
}
|
||||
}
|
||||
|
||||
func handle() {
|
||||
if (direction == .left || direction == .right) {
|
||||
action(abs(accumulatedScrollDeltaX), event.phase)
|
||||
} else {
|
||||
action(abs(accumulatedScrollDeltaY), event.phase)
|
||||
}
|
||||
}
|
||||
|
||||
if event.phase == .ended {
|
||||
accumulatedScrollDeltaY = 0
|
||||
accumulatedScrollDeltaX = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import SwiftUI
|
||||
|
||||
enum PanDirection {
|
||||
case left
|
||||
case right
|
||||
case up
|
||||
case down
|
||||
case left, right, up, down
|
||||
|
||||
var isHorizontal: Bool { self == .left || self == .right }
|
||||
var sign: CGFloat { (self == .right || self == .down) ? 1 : -1 }
|
||||
|
||||
func signed(from translation: CGSize) -> CGFloat { (isHorizontal ? translation.width : translation.height) * sign }
|
||||
func signed(deltaX: CGFloat, deltaY: CGFloat) -> CGFloat { (isHorizontal ? deltaX : deltaY) * sign }
|
||||
}
|
||||
|
||||
extension View {
|
||||
func panGesture(direction: PanDirection, threshold: CGFloat = 4, action: @escaping (CGFloat, NSEvent.Phase) -> Void) -> some View {
|
||||
self
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { value in
|
||||
let s = direction.signed(from: value.translation)
|
||||
guard s > 0, s.magnitude >= threshold else { return }
|
||||
action(s.magnitude, .changed)
|
||||
}
|
||||
.onEnded { _ in action(0, .ended) }
|
||||
)
|
||||
.background(ScrollMonitor(direction: direction, threshold: threshold, action: action))
|
||||
}
|
||||
}
|
||||
|
||||
private struct ScrollMonitor: NSViewRepresentable {
|
||||
let direction: PanDirection
|
||||
let threshold: CGFloat
|
||||
let action: (CGFloat, NSEvent.Phase) -> Void
|
||||
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
let view = NSView()
|
||||
context.coordinator.installMonitor(on: view)
|
||||
return view
|
||||
}
|
||||
func updateNSView(_ nsView: NSView, context: Context) {}
|
||||
static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) { coordinator.removeMonitor() }
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(direction: direction, threshold: threshold, action: action)
|
||||
}
|
||||
|
||||
@MainActor final class Coordinator: NSObject {
|
||||
private let direction: PanDirection
|
||||
private let threshold: CGFloat
|
||||
private let action: (CGFloat, NSEvent.Phase) -> Void
|
||||
private var monitor: Any?
|
||||
private var accumulated: CGFloat = 0
|
||||
private var active = false
|
||||
private let noiseThreshold: CGFloat = 0.2
|
||||
|
||||
init(direction: PanDirection, threshold: CGFloat, action: @escaping (CGFloat, NSEvent.Phase) -> Void) {
|
||||
self.direction = direction
|
||||
self.threshold = threshold
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func installMonitor(on view: NSView) {
|
||||
removeMonitor()
|
||||
monitor = NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { [weak self, weak view] event in
|
||||
guard let self = self, event.window === view?.window else { return event }
|
||||
self.handleScroll(event)
|
||||
return event
|
||||
}
|
||||
}
|
||||
|
||||
func removeMonitor() {
|
||||
if let monitor = monitor {
|
||||
NSEvent.removeMonitor(monitor)
|
||||
self.monitor = nil
|
||||
}
|
||||
accumulated = 0
|
||||
active = false
|
||||
}
|
||||
|
||||
private func handleScroll(_ event: NSEvent) {
|
||||
if event.phase == .ended || event.momentumPhase == .ended {
|
||||
if active {
|
||||
action(accumulated.magnitude, .ended)
|
||||
} else {
|
||||
action(0, .ended)
|
||||
}
|
||||
active = false
|
||||
accumulated = 0
|
||||
return
|
||||
}
|
||||
|
||||
let s = direction.signed(deltaX: event.scrollingDeltaX, deltaY: event.scrollingDeltaY)
|
||||
guard s.magnitude > noiseThreshold else { return }
|
||||
accumulated = s > 0 ? accumulated + s : 0
|
||||
|
||||
if !active && accumulated >= threshold {
|
||||
active = true
|
||||
action(accumulated.magnitude, .began)
|
||||
} else if active {
|
||||
action(accumulated.magnitude, .changed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue