forked from MeloNX/MeloNX
Compare commits
No commits in common. "fd4bc5ccbb9ec2bc2db43f6863024d477a99c79c" and "ceab2f0ac8e0fdbbc7f7c24c15bf616c25f908c4" have entirely different histories.
fd4bc5ccbb
...
ceab2f0ac8
@ -1,592 +0,0 @@
|
|||||||
//
|
|
||||||
// Joystick4FTG.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by RZH on 2025/3/7.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
public struct Joystick4FTG: View {
|
|
||||||
//先声明数据
|
|
||||||
@State var iscool: Bool? = nil
|
|
||||||
@State private var joystickPositionText = ""
|
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
|
||||||
var dragDiameter: CGFloat {
|
|
||||||
var selfs = CGFloat(160)
|
|
||||||
selfs *= controllerScale
|
|
||||||
if UIDevice.current.systemName.contains("iPadOS") {
|
|
||||||
return selfs * 1.2
|
|
||||||
}
|
|
||||||
return selfs
|
|
||||||
}
|
|
||||||
public var body: some View {
|
|
||||||
VStack{
|
|
||||||
// 新增坐标显示(在摇杆上方)
|
|
||||||
//旧的FTGJoystick:
|
|
||||||
/*FTGJoystickBuilder { relativePosition in
|
|
||||||
|
|
||||||
let scaledX = Double(relativePosition.x )
|
|
||||||
let scaledY = Double(relativePosition.y )
|
|
||||||
|
|
||||||
//print("Joystick Position: (\(scaledX), \(scaledY))")
|
|
||||||
|
|
||||||
*/
|
|
||||||
//新版八向摇杆:
|
|
||||||
/*
|
|
||||||
EnhancedEightWayJoystick{relativePosition in
|
|
||||||
let scaledX = Double(relativePosition.x * 10)
|
|
||||||
let scaledY = Double(relativePosition.y * 10)
|
|
||||||
let formattedText = String(format: "X: %5.2f, Y: %5.2f", scaledX, scaledY)
|
|
||||||
print("Joystick Position: \(formattedText)")
|
|
||||||
// 更新显示文本
|
|
||||||
//self.joystickPositionText = formattedText
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
//新版自由摇杆:
|
|
||||||
FreeRoamJoystick { position in
|
|
||||||
let scaledX = Double(position.x * 10)
|
|
||||||
let scaledY = Double(position.y * 10)
|
|
||||||
//print("Joystick Position: \(formattedText)")
|
|
||||||
// 更新显示文本
|
|
||||||
//self.joystickPositionText = formattedText
|
|
||||||
|
|
||||||
*/
|
|
||||||
//九宫格摇杆:
|
|
||||||
/*
|
|
||||||
NineGridJoystick { position in
|
|
||||||
let scaledX = Double(position.x * 10)
|
|
||||||
let scaledY = Double(position.y * 10)
|
|
||||||
*/
|
|
||||||
|
|
||||||
//新的动态九宫格摇杆
|
|
||||||
DynamicNineGridJoystick{ position in
|
|
||||||
let scaledX = Double(position.x * 10)
|
|
||||||
let scaledY = Double(position.y * 10)
|
|
||||||
|
|
||||||
if iscool != nil {
|
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: scaledX, y: scaledY)
|
|
||||||
} else {
|
|
||||||
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: scaledX, y: scaledY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct EnhancedEightWayJoystick: View {
|
|
||||||
var onDirectionChanged: ((x: Int, y: Int)) -> Void
|
|
||||||
|
|
||||||
@State private var currentDirection: (x: Int, y: Int) = (0, 0)
|
|
||||||
private let radius: CGFloat = 50
|
|
||||||
private let centerPoint = CGPoint(x: 50, y: 50)
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
ZStack {
|
|
||||||
// 触摸区域
|
|
||||||
Circle()
|
|
||||||
.fill(Color.gray.opacity(0.2))
|
|
||||||
.frame(width: 200, height: 200)
|
|
||||||
.contentShape(Circle())
|
|
||||||
|
|
||||||
// 方向指示器
|
|
||||||
Circle()
|
|
||||||
.fill(Color.white.opacity(0.7))
|
|
||||||
.frame(width: 75, height: 75)
|
|
||||||
.offset(x: CGFloat(currentDirection.x), y: CGFloat(currentDirection.y))
|
|
||||||
}
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0)
|
|
||||||
.onChanged { gesture in
|
|
||||||
let location = gesture.location
|
|
||||||
//添加使用Geometry计算的dxdy坐标
|
|
||||||
let viewFrame = geometry.frame(in: .local)
|
|
||||||
let center = CGPoint(
|
|
||||||
x: viewFrame.midX,
|
|
||||||
y: viewFrame.midY
|
|
||||||
)
|
|
||||||
|
|
||||||
//let dx = location.x - centerPoint.x
|
|
||||||
//let dy = location.y - centerPoint.y
|
|
||||||
let dx = location.x - center.x
|
|
||||||
let dy = location.y - center.y
|
|
||||||
|
|
||||||
// 计算极坐标
|
|
||||||
let radians = atan2(dy, dx)
|
|
||||||
var degrees = radians * 180 / .pi
|
|
||||||
if degrees < 0 { degrees += 360 }
|
|
||||||
|
|
||||||
// 获取调整后的方向
|
|
||||||
let direction = get45DegreeDirection(from: degrees)
|
|
||||||
|
|
||||||
// 仅在方向变化时更新
|
|
||||||
if direction.x != currentDirection.x || direction.y != currentDirection.y {
|
|
||||||
currentDirection = direction
|
|
||||||
onDirectionChanged(direction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
currentDirection = (0, 0)
|
|
||||||
onDirectionChanged((0, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 45度均匀分布方向判断
|
|
||||||
//调转Y轴
|
|
||||||
//调整scale到 168
|
|
||||||
private func get45DegreeDirection(from degrees: CGFloat) -> (x: Int, y: Int) {
|
|
||||||
switch degrees {
|
|
||||||
// 右 (337.5°-22.5°)
|
|
||||||
case 337.5...360, 0..<22.5:
|
|
||||||
return (50, 0)
|
|
||||||
|
|
||||||
// 右上 (22.5°-67.5°)
|
|
||||||
case 22.5..<67.5:
|
|
||||||
//return (50, -50)
|
|
||||||
return (50, 50)
|
|
||||||
// 上 (67.5°-112.5°)
|
|
||||||
case 67.5..<112.5:
|
|
||||||
//return (0, -50)
|
|
||||||
return (0, 50)
|
|
||||||
// 左上 (112.5°-157.5°)
|
|
||||||
case 112.5..<157.5:
|
|
||||||
//return (-50, -50)
|
|
||||||
return (-50, 50)
|
|
||||||
// 左 (157.5°-202.5°)
|
|
||||||
case 157.5..<202.5:
|
|
||||||
return (-50, 0)
|
|
||||||
|
|
||||||
// 左下 (202.5°-247.5°)
|
|
||||||
case 202.5..<247.5:
|
|
||||||
//return (-50, 50)
|
|
||||||
return (-50, -50)
|
|
||||||
// 下 (247.5°-292.5°)
|
|
||||||
case 247.5..<292.5:
|
|
||||||
//return (0, 50)
|
|
||||||
return (0, -50)
|
|
||||||
// 右下 (292.5°-337.5°)
|
|
||||||
case 292.5..<337.5:
|
|
||||||
//return (50, 50)
|
|
||||||
return (50, -50)
|
|
||||||
default:
|
|
||||||
return (0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct FreeRoamJoystick: View {
|
|
||||||
var onPositionChanged: (CGPoint) -> Void
|
|
||||||
|
|
||||||
private let trackRadius: CGFloat = 90
|
|
||||||
private let thumbRadius: CGFloat = 75
|
|
||||||
@State private var isEightWay = 0 // 0: 自由模式,1: 八向模式
|
|
||||||
@State private var position = CGPoint.zero
|
|
||||||
@State private var viewCenter: CGPoint = .zero
|
|
||||||
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
ZStack {
|
|
||||||
Spacer()
|
|
||||||
// 摇杆背景(点击区域)
|
|
||||||
Circle()
|
|
||||||
.stroke(Color.gray.opacity(0.5), lineWidth: 4)
|
|
||||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
|
||||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
|
||||||
.contentShape(Circle()) // 关键修改点1:设置点击区域形状
|
|
||||||
// 摇杆头
|
|
||||||
let maxMoveRadius = trackRadius - thumbRadius
|
|
||||||
Circle()
|
|
||||||
.fill(Color.white.opacity(0.4))
|
|
||||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
|
||||||
//.offset(x: position.x, y: position.y)
|
|
||||||
.offset(
|
|
||||||
x: min(max(position.x, -maxMoveRadius), maxMoveRadius),
|
|
||||||
y: min(max(position.y, -maxMoveRadius), maxMoveRadius)
|
|
||||||
)
|
|
||||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
|
||||||
}
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0) // 关键修改点2:零距离触发
|
|
||||||
.onChanged { gesture in
|
|
||||||
let viewFrame = geometry.frame(in: .local)
|
|
||||||
let touchPoint = gesture.location
|
|
||||||
|
|
||||||
// 计算相对中心坐标
|
|
||||||
let center = CGPoint(
|
|
||||||
x: viewFrame.midX,
|
|
||||||
y: viewFrame.midY
|
|
||||||
)
|
|
||||||
let delta = CGPoint(
|
|
||||||
x: touchPoint.x - center.x,
|
|
||||||
y: touchPoint.y - center.y
|
|
||||||
)
|
|
||||||
|
|
||||||
// 限制在圆形范围内
|
|
||||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
|
||||||
let clampedDelta = distance > trackRadius ? CGPoint(
|
|
||||||
x: delta.x * trackRadius / distance,
|
|
||||||
y: delta.y * trackRadius / distance
|
|
||||||
) : delta
|
|
||||||
var test_data = clampedDelta
|
|
||||||
// 更新状态
|
|
||||||
//尝试不同返回值
|
|
||||||
if isEightWay == 1 {
|
|
||||||
test_data = getEightDirection(for: test_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
//position = clampedDelta
|
|
||||||
position = test_data
|
|
||||||
//onPositionChanged(clampedDelta)
|
|
||||||
onPositionChanged(test_data)
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
|
|
||||||
position = .zero
|
|
||||||
onPositionChanged(.zero)
|
|
||||||
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.onAppear {
|
|
||||||
viewCenter = CGPoint(
|
|
||||||
x: geometry.size.width/2,
|
|
||||||
y: geometry.size.height/2
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(width: trackRadius, height: trackRadius)
|
|
||||||
//.frame(alignment: .bottomLeading)
|
|
||||||
}
|
|
||||||
private func getEightDirection(for delta: CGPoint) -> CGPoint {
|
|
||||||
|
|
||||||
|
|
||||||
let angle = atan2(-delta.y, delta.x)
|
|
||||||
let degrees = angle * 180 / .pi
|
|
||||||
let normalized = degrees < 0 ? degrees + 360 : degrees
|
|
||||||
|
|
||||||
switch normalized {
|
|
||||||
case 337.5...360, 0..<22.5: // 右
|
|
||||||
return CGPoint(x: trackRadius, y: 0)
|
|
||||||
case 22.5..<67.5: // 右上
|
|
||||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
|
||||||
case 67.5..<112.5: // 上
|
|
||||||
return CGPoint(x: 0, y: -trackRadius)
|
|
||||||
case 112.5..<157.5: // 左上
|
|
||||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
|
||||||
case 157.5..<202.5: // 左
|
|
||||||
return CGPoint(x: -trackRadius, y: 0)
|
|
||||||
case 202.5..<247.5: // 左下
|
|
||||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
|
||||||
case 247.5..<292.5: // 下
|
|
||||||
return CGPoint(x: 0, y: trackRadius)
|
|
||||||
case 292.5..<337.5: // 右下
|
|
||||||
return CGPoint(x: trackRadius, y: trackRadius)
|
|
||||||
default:
|
|
||||||
return .zero
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct NineGridJoystick: View {
|
|
||||||
var onDirectionChanged: (CGPoint) -> Void
|
|
||||||
|
|
||||||
private let trackRadius: CGFloat = 90
|
|
||||||
private let thumbRadius: CGFloat = 35
|
|
||||||
@State private var position = CGPoint.zero
|
|
||||||
@State private var activeColor = Color.white.opacity(0.4)
|
|
||||||
|
|
||||||
// 九宫格配置参数
|
|
||||||
private var gridThreshold: CGFloat {
|
|
||||||
trackRadius / 3 // 56
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
ZStack {
|
|
||||||
// 九宫格背景
|
|
||||||
gridBackgroundView()
|
|
||||||
|
|
||||||
// 摇杆头
|
|
||||||
Circle()
|
|
||||||
.fill(activeColor)
|
|
||||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
|
||||||
.offset(x: position.x, y: position.y)
|
|
||||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
|
||||||
//.animation(.spring(), value: position)
|
|
||||||
}
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0)
|
|
||||||
.onChanged { gesture in
|
|
||||||
let center = CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)
|
|
||||||
let delta = CGPoint(
|
|
||||||
x: gesture.location.x - center.x,
|
|
||||||
y: gesture.location.y - center.y
|
|
||||||
)
|
|
||||||
|
|
||||||
let direction = getGridDirection(for: delta)
|
|
||||||
updatePosition(to: direction)
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
resetPosition()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 九宫格视图
|
|
||||||
private func gridBackgroundView() -> some View {
|
|
||||||
ZStack {
|
|
||||||
// 背景网格线
|
|
||||||
Path { path in
|
|
||||||
// 垂直中线
|
|
||||||
path.move(to: CGPoint(x: trackRadius - gridThreshold, y: 0))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius - gridThreshold, y: trackRadius*2))
|
|
||||||
path.move(to: CGPoint(x: trackRadius + gridThreshold, y: 0))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius + gridThreshold, y: trackRadius*2))
|
|
||||||
// 水平中线
|
|
||||||
path.move(to: CGPoint(x: 0, y: trackRadius - gridThreshold))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius - gridThreshold))
|
|
||||||
path.move(to: CGPoint(x: 0, y: trackRadius + gridThreshold))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + gridThreshold))
|
|
||||||
}
|
|
||||||
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
|
|
||||||
|
|
||||||
// 中心区域高亮
|
|
||||||
Rectangle()
|
|
||||||
.fill(Color.gray.opacity(0.1))
|
|
||||||
.frame(width: gridThreshold*2, height: gridThreshold*2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 方向判断逻辑
|
|
||||||
private func getGridDirection(for delta: CGPoint) -> CGPoint {
|
|
||||||
let xVal = delta.x
|
|
||||||
let yVal = delta.y
|
|
||||||
|
|
||||||
switch (xVal, yVal) {
|
|
||||||
case (..<(-gridThreshold), ..<(-gridThreshold)): // 左上
|
|
||||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
|
||||||
case ((-gridThreshold)...gridThreshold, ..<(-gridThreshold)): // 上
|
|
||||||
return CGPoint(x: 0, y: -trackRadius)
|
|
||||||
case (gridThreshold..., ..<(-gridThreshold)): // 右上
|
|
||||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
|
||||||
case (..<(-gridThreshold), (-gridThreshold)...gridThreshold): // 左
|
|
||||||
return CGPoint(x: -trackRadius, y: 0)
|
|
||||||
case ((-gridThreshold)...gridThreshold, (-gridThreshold)...gridThreshold): // 中
|
|
||||||
return .zero
|
|
||||||
case (gridThreshold..., (-gridThreshold)...gridThreshold): // 右
|
|
||||||
return CGPoint(x: trackRadius, y: 0)
|
|
||||||
case (..<(-gridThreshold), gridThreshold...): // 左下
|
|
||||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
|
||||||
case ((-gridThreshold)...gridThreshold, gridThreshold...): // 下
|
|
||||||
return CGPoint(x: 0, y: trackRadius)
|
|
||||||
case (gridThreshold..., gridThreshold...): // 右下
|
|
||||||
return CGPoint(x: trackRadius, y: trackRadius)
|
|
||||||
default:
|
|
||||||
return .zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 状态更新
|
|
||||||
private func updatePosition(to direction: CGPoint) {
|
|
||||||
//withAnimation(.spring()) {
|
|
||||||
position = direction
|
|
||||||
activeColor = direction == .zero ?
|
|
||||||
Color.white.opacity(0.4) :
|
|
||||||
Color.blue.opacity(0.6)
|
|
||||||
onDirectionChanged(direction)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func resetPosition() {
|
|
||||||
//withAnimation(.spring()) {
|
|
||||||
position = .zero
|
|
||||||
activeColor = Color.white.opacity(0.4)
|
|
||||||
onDirectionChanged(.zero)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct DynamicNineGridJoystick: View {
|
|
||||||
var onDirectionChanged: (CGPoint) -> Void
|
|
||||||
|
|
||||||
private let trackRadius: CGFloat = 100
|
|
||||||
private let thumbRadius: CGFloat = 35
|
|
||||||
@State private var position = CGPoint.zero
|
|
||||||
@State private var activeColor = Color.white.opacity(0.4)
|
|
||||||
@State private var lastDirection: CGPoint = .zero
|
|
||||||
|
|
||||||
// 九宫格配置
|
|
||||||
private var gridThreshold: CGFloat { trackRadius / 3 }
|
|
||||||
private let hapticGenerator = UIImpactFeedbackGenerator(style: .soft)
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
GeometryReader { geometry in
|
|
||||||
ZStack {
|
|
||||||
// 九宫格背景
|
|
||||||
gridBackgroundView()
|
|
||||||
|
|
||||||
// 摇杆头
|
|
||||||
Circle()
|
|
||||||
.fill(activeColor)
|
|
||||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
|
||||||
.offset(x: position.x, y: position.y)
|
|
||||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
|
||||||
//.animation(.spring(), value: position)
|
|
||||||
}
|
|
||||||
// 关键修改点:设置全区域点击识别
|
|
||||||
.contentShape(Rectangle()) // 使用矩形识别区域覆盖整个GeometryReader
|
|
||||||
.gesture(
|
|
||||||
DragGesture(minimumDistance: 0)
|
|
||||||
.onChanged { gesture in
|
|
||||||
handleGestureChange(
|
|
||||||
location: gesture.location,
|
|
||||||
center: CGPoint(x: geometry.size.width/2,
|
|
||||||
y: geometry.size.height/2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.onEnded { _ in
|
|
||||||
resetPosition()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
|
||||||
.onAppear {
|
|
||||||
hapticGenerator.prepare()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 手势处理
|
|
||||||
private func handleGestureChange(location: CGPoint, center: CGPoint) {
|
|
||||||
let delta = CGPoint(
|
|
||||||
x: location.x - center.x,
|
|
||||||
y: location.y - center.y
|
|
||||||
)
|
|
||||||
|
|
||||||
let currentDirection = getGridDirection(for: delta)
|
|
||||||
|
|
||||||
// 仅在新方向变化时触发
|
|
||||||
if currentDirection != lastDirection {
|
|
||||||
updatePosition(to: currentDirection)
|
|
||||||
triggerHapticFeedback()
|
|
||||||
lastDirection = currentDirection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 九宫格方向判断(优化版)
|
|
||||||
private func getGridDirection(for delta: CGPoint) -> CGPoint {
|
|
||||||
let xVal = delta.x
|
|
||||||
let yVal = delta.y
|
|
||||||
|
|
||||||
// 定义九宫格边界
|
|
||||||
let isLeft = xVal < -gridThreshold
|
|
||||||
let isRight = xVal > gridThreshold
|
|
||||||
let isUp = yVal < -gridThreshold
|
|
||||||
let isDown = yVal > gridThreshold
|
|
||||||
|
|
||||||
switch (isLeft, isRight, isUp, isDown) {
|
|
||||||
case (true, false, true, false): // 左上
|
|
||||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
|
||||||
case (false, false, true, false): // 上
|
|
||||||
return CGPoint(x: 0, y: -trackRadius)
|
|
||||||
case (false, true, true, false): // 右上
|
|
||||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
|
||||||
case (true, false, false, false): // 左
|
|
||||||
return CGPoint(x: -trackRadius, y: 0)
|
|
||||||
case (false, false, false, false): // 中
|
|
||||||
return .zero
|
|
||||||
case (false, true, false, false): // 右
|
|
||||||
return CGPoint(x: trackRadius, y: 0)
|
|
||||||
case (true, false, false, true): // 左下
|
|
||||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
|
||||||
case (false, false, false, true): // 下
|
|
||||||
return CGPoint(x: 0, y: trackRadius)
|
|
||||||
case (false, true, false, true): // 右下
|
|
||||||
return CGPoint(x: trackRadius, y: trackRadius)
|
|
||||||
default: // 处理对角线滑动时的中间状态
|
|
||||||
return calculateEdgeCase(delta: delta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理边缘滑动状态
|
|
||||||
private func calculateEdgeCase(delta: CGPoint) -> CGPoint {
|
|
||||||
let angle = atan2(delta.y, delta.x) * 180 / .pi
|
|
||||||
let normalized = angle < 0 ? angle + 360 : angle
|
|
||||||
|
|
||||||
switch normalized {
|
|
||||||
case 45..<135: // 上象限
|
|
||||||
return CGPoint(x: 0, y: -trackRadius)
|
|
||||||
case 135..<225: // 左象限
|
|
||||||
return CGPoint(x: -trackRadius, y: 0)
|
|
||||||
case 225..<315: // 下象限
|
|
||||||
return CGPoint(x: 0, y: trackRadius)
|
|
||||||
default: // 右象限
|
|
||||||
return CGPoint(x: trackRadius, y: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 视觉与反馈更新
|
|
||||||
private func updatePosition(to direction: CGPoint) {
|
|
||||||
//withAnimation(.interactiveSpring()) {
|
|
||||||
|
|
||||||
position = direction
|
|
||||||
activeColor = direction == .zero ?
|
|
||||||
Color.white.opacity(0.4) :
|
|
||||||
Color.blue.opacity(0.6)
|
|
||||||
onDirectionChanged(direction)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private func resetPosition() {
|
|
||||||
//withAnimation(.spring()) {
|
|
||||||
position = .zero
|
|
||||||
activeColor = Color.white.opacity(0.4)
|
|
||||||
lastDirection = .zero
|
|
||||||
onDirectionChanged(.zero)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func triggerHapticFeedback() {
|
|
||||||
guard position != .zero else { return }
|
|
||||||
hapticGenerator.impactOccurred(intensity: 0.7)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - 九宫格视图(优化版)
|
|
||||||
private func gridBackgroundView() -> some View {
|
|
||||||
ZStack {
|
|
||||||
// 网格线
|
|
||||||
Path { path in
|
|
||||||
let offsets = [-gridThreshold, 0, gridThreshold]
|
|
||||||
for offset in offsets {
|
|
||||||
path.move(to: CGPoint(x: trackRadius + offset, y: 0))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius + offset, y: trackRadius*2))
|
|
||||||
path.move(to: CGPoint(x: 0, y: trackRadius + offset))
|
|
||||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
|
|
||||||
|
|
||||||
// 中心区域
|
|
||||||
RoundedRectangle(cornerRadius: 12)
|
|
||||||
.fill(Color.gray.opacity(0.1))
|
|
||||||
.frame(width: gridThreshold*2, height: gridThreshold*2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user