-
-
Notifications
You must be signed in to change notification settings - Fork 84
feat(input): 三指直接平移/缩放(无需旧"平移与缩放"开关) #549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,15 @@ class TouchInputHandler(private val game: Game) { | |
| private var twoFingerStartX = 0f | ||
| private var twoFingerStartY = 0f | ||
|
|
||
| // 三指平移/缩放状态(无需"平移与缩放"开关,第三指按下即进入,全部抬起才退出) | ||
| private var threeFingerMode = false | ||
| private var threeFingerDownTime = 0L | ||
| private var threeFingerMoved = false | ||
| private val threeFingerStartX = FloatArray(3) | ||
| private val threeFingerStartY = FloatArray(3) | ||
| /** 进入三指模式时的原始 pointerId,用于模式尾巴段(部分手指已抬起)仍能按 ID 追踪移动 */ | ||
| private val threeFingerStartPointerIds = IntArray(3) | ||
|
|
||
| private var lastAbsTouchUpTime = 0L | ||
| private var lastAbsTouchDownTime = 0L | ||
| private var lastAbsTouchUpX = 0f | ||
|
|
@@ -456,6 +465,81 @@ class TouchInputHandler(private val game: Game) { | |
| lastButtonState = buttonState | ||
| } else { | ||
| // This case is for fingers | ||
| val actionMasked = event.actionMasked | ||
| val pointerCount = event.pointerCount | ||
|
|
||
| // 三指直接平移/缩放(游戏菜单"三指平移/缩放"开关控制,默认开启): | ||
| // 第三指按下即进入,事件全量交给 PanZoomHandler(平移/缩放联动 | ||
| // streamView 与本地光标),全部抬起才退出。原有的"三指快击唤键盘"行为保留: | ||
| // 无位移、300ms 内抬起、且唤键盘指数配置为 3 时仍触发键盘切换;只要手指 | ||
| // 动了(平移/缩放意图)就不再算快击。 | ||
| if (game.prefConfig.enableThreeFingerPanZoom && (pointerCount >= 3 || threeFingerMode)) { | ||
| if (!threeFingerMode) { | ||
| threeFingerMode = true | ||
| threeFingerDownTime = event.eventTime | ||
| threeFingerMoved = false | ||
| repeat(3) { i -> | ||
| threeFingerStartX[i] = event.getX(i) | ||
| threeFingerStartY[i] = event.getY(i) | ||
| threeFingerStartPointerIds[i] = event.getPointerId(i) | ||
| } | ||
| // 取消此前 1/2 指已发给主机的触摸/鼠标状态,避免游戏侧"粘键/粘指" | ||
| twoFingerTapPending = false | ||
| twoFingerMoved = true | ||
| for (ctx in touchContextMap) ctx?.cancelTouch() | ||
| nativeTouchPointerMap.clear() | ||
| game.conn?.sendTouchEvent( | ||
| MoonBridge.LI_TOUCH_EVENT_CANCEL_ALL, 0, | ||
| 0f, 0f, 0f, 0f, 0f, | ||
| MoonBridge.LI_ROT_UNKNOWN | ||
| ) | ||
| } | ||
|
|
||
| when (actionMasked) { | ||
| MotionEvent.ACTION_MOVE -> { | ||
| // 任一手指相对落点时位移超过阈值 => 视为真正的平移/缩放手势。 | ||
| // 按原始 pointerId 追踪:模式尾巴段(部分手指已抬起、pointerCount | ||
| // 降到 2/1)剩余手指的移动同样计入,避免"抬一指后双指平移仍被 | ||
| // 算作快击"而误触键盘切换。 | ||
| if (!threeFingerMoved) { | ||
| for (i in 0 until 3) { | ||
| val idx = event.findPointerIndex(threeFingerStartPointerIds[i]) | ||
| if (idx < 0) continue // 该指已抬起 | ||
| val dx = event.getX(idx) - threeFingerStartX[i] | ||
| val dy = event.getY(idx) - threeFingerStartY[i] | ||
| if (sqrt(dx * dx + dy * dy) > THREE_FINGER_MOVE_THRESHOLD) { | ||
| threeFingerMoved = true | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
| MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { | ||
| threeFingerMode = false | ||
| if (actionMasked == MotionEvent.ACTION_UP && !threeFingerMoved && | ||
| game.prefConfig.nativeTouchFingersToToggleKeyboard == 3 && | ||
| event.eventTime - threeFingerDownTime < MULTI_FINGER_TAP_THRESHOLD | ||
| ) { | ||
| game.toggleKeyboard() | ||
| } | ||
| } | ||
| else -> {} | ||
| } | ||
|
|
||
| game.panZoomHandler.handleTouchEvent(event) | ||
| return true | ||
| } | ||
|
|
||
| // 三指平移/缩放关闭时回退原行为:第三指落下即取消已下发的 1/2 指触摸, | ||
| // 快击(300ms 内抬起)仍可唤键盘(由下方 UP 分支的 multiFingerDownTime 判定) | ||
| if (!game.prefConfig.enableThreeFingerPanZoom && | ||
| actionMasked == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 3 | ||
| ) { | ||
| multiFingerDownTime = event.eventTime | ||
| for (ctx in touchContextMap) ctx?.cancelTouch() | ||
| return true | ||
| } | ||
|
|
||
| if (event.actionMasked == MotionEvent.ACTION_DOWN) { | ||
| enhancedTouchRouteOwner.finish() | ||
| nativeTouchPointerMap.clear() | ||
|
|
@@ -490,6 +574,10 @@ class TouchInputHandler(private val game: Game) { | |
| nativeTouchPointerMap.clear() | ||
| } | ||
|
|
||
| if (!game.prefConfig.touchscreenTrackpad && game.prefConfig.enableEnhancedTouch && trySendTouchEvent(view, event)) { | ||
| return true | ||
| } | ||
|
|
||
|
Comment on lines
+577
to
+580
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Description: Check whether sendTouchEvent's unsupported result is a cached local check or an actual send.
rg -nP -C5 '\bfun\s+sendTouchEvent\b' --type=kotlin --type=javaRepository: qiin2333/moonlight-vplus Length of output: 162 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- TouchInputHandler relevant definitions and flow ---'
rg -n -C12 'trySendTouchEvent|sendTouchEvent|enhancedTouchRouteOwner' app/src/main/java/com/limelight/TouchInputHandler.kt
printf '%s\n' '--- sendTouchEvent declarations and direct callers ---'
rg -n -C8 '\bsendTouchEvent\b' app/src/main/java app/src/main/jni app/src/main/cpp 2>/dev/null || trueRepository: qiin2333/moonlight-vplus Length of output: 20036 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Native sendTouchEvent implementation and return contract ---'
rg -n -C12 'sendTouchEvent|LI_ERR_UNSUPPORTED' app/src/main app/src 2>/dev/null | head -240
printf '%s\n' '--- Repository review conventions for this scope ---'
find /tmp/coderabbit-repo-knowledge/qiin2333-moonlight-vplus-42a6cf86 -mindepth 2 -maxdepth 2 -type f -name '*.md' -printRepository: qiin2333/moonlight-vplus Length of output: 24703 🏁 Script executed: #!/bin/bash
set -e
cat /tmp/coderabbit-repo-knowledge/qiin2333-moonlight-vplus-42a6cf86/learnings/app-src-main-java-com-limelight-binding-input-dr.mdRepository: qiin2333/moonlight-vplus Length of output: 743 Avoid retrying When the first call returns 🤖 Prompt for AI Agents |
||
| if (game.virtualController != null && | ||
| (game.virtualController?.controllerMode == VirtualController.ControllerMode.MoveButtons || | ||
| game.virtualController?.controllerMode == VirtualController.ControllerMode.ResizeButtons) | ||
|
|
@@ -499,13 +587,6 @@ class TouchInputHandler(private val game: Game) { | |
|
|
||
| val actionIndex = event.actionIndex | ||
|
|
||
| // 三指手势特殊处理 | ||
| if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN && event.pointerCount == 3) { | ||
| multiFingerDownTime = event.eventTime | ||
| for (ctx in touchContextMap) ctx?.cancelTouch() | ||
| return true | ||
| } | ||
|
|
||
| val context = getTouchContext(actionIndex) ?: return false | ||
|
|
||
| when (event.actionMasked) { | ||
|
|
@@ -1401,10 +1482,11 @@ class TouchInputHandler(private val game: Game) { | |
| const val TOUCH_CONTEXT_LENGTH = 2 | ||
| private const val TWO_FINGER_TAP_THRESHOLD = 100L | ||
| private const val TWO_FINGER_MOVE_THRESHOLD = 40f | ||
| // Matches the verified HarmonyOS DS5 touchpad behavior (24vp tap slop, | ||
| // Matches the verified HarmonyOS DS5 touchpad behavior (24vp tap slop, | ||
| // 50ms clickpad hold). | ||
| private const val SCREEN_DS5_TAP_MOVEMENT_DP = 24f | ||
| private const val SCREEN_DS5_CLICK_RELEASE_MS = 50L | ||
| private const val THREE_FINGER_MOVE_THRESHOLD = 40f | ||
| private const val STYLUS_DOWN_DEAD_ZONE_DELAY = 100L | ||
| private const val STYLUS_DOWN_DEAD_ZONE_RADIUS = 20f | ||
| private const val STYLUS_UP_DEAD_ZONE_DELAY = 150L | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2156,19 +2156,31 @@ class GameMenu( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inlineControl = InlineControl.Segmented(buildTouchModeSegments(compactLabels = true)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 三指平移/缩放(默认开启,状态持久化;关闭时回退"三指快击唤键盘") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 行点击与复选框点击共用同一切换逻辑:runnable 走行点击, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // toggleAction 走复选框(InlineControl.Toggle 无 toggleAction 时点击复选框不生效) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val threeFingerPanZoomToggle = Runnable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val enabled = !game.prefConfig.enableThreeFingerPanZoom | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| game.prefConfig.enableThreeFingerPanZoom = enabled | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| android.preference.PreferenceManager.getDefaultSharedPreferences(game).edit { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| putBoolean(PreferenceConfiguration.THREE_FINGER_PAN_ZOOM_PREF_STRING, enabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(game, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (enabled) getString(R.string.toast_three_finger_pan_zoom_enabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else getString(R.string.toast_three_finger_pan_zoom_disabled), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| normalOptions.add(MenuOption( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label = getString(R.string.game_menu_enable_pan_zoom).trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label = getString(R.string.game_menu_enable_three_finger_pan_zoom).trim(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isWithGameFocus = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runnable = Runnable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.makeText(game, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (game.getisTouchOverrideEnabled()) getString(R.string.toast_pan_zoom_disabled) else getString(R.string.toast_pan_zoom_enabled), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Toast.LENGTH_SHORT).show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| game.setisTouchOverrideEnabled(!game.getisTouchOverrideEnabled()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iconKey = "game_menu_mouse_emulation", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runnable = threeFingerPanZoomToggle, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| iconKey = "game_menu_enable_three_finger_pan_zoom", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isShowIcon = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isKeepDialog = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inlineControl = InlineControl.Toggle(game.getisTouchOverrideEnabled()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inlineControl = InlineControl.Toggle( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checked = game.prefConfig.enableThreeFingerPanZoom, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toggleAction = threeFingerPanZoomToggle | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2159
to
2184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Row tap does not refresh the toggle display.
🔧 Proposed fix normalOptions.add(MenuOption(
label = getString(R.string.game_menu_enable_three_finger_pan_zoom).trim(),
isWithGameFocus = false,
runnable = threeFingerPanZoomToggle,
iconKey = "game_menu_enable_three_finger_pan_zoom",
isShowIcon = true,
isKeepDialog = true,
inlineControl = InlineControl.Toggle(
checked = game.prefConfig.enableThreeFingerPanZoom,
toggleAction = threeFingerPanZoomToggle
- )
+ ),
+ presentation = GameMenuOptionPresentation.COMPATIBLE_ACTION
))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 王冠功能 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2301,6 +2313,7 @@ class GameMenu( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "game_menu_cancel" to R.drawable.ic_cancel_cute, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "mouse_mode" to R.drawable.ic_mouse_cute, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "game_menu_mouse_emulation" to R.drawable.ic_mouse_emulation_cute, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "game_menu_enable_three_finger_pan_zoom" to R.drawable.ic_mouse_emulation_cute, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "crown_function_menu" to R.drawable.ic_super_crown, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "crown_visibility" to R.drawable.ic_ui_settings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "crown_touch" to R.drawable.ic_touch_settings, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: qiin2333/moonlight-vplus
Length of output: 162
🏁 Script executed:
Repository: qiin2333/moonlight-vplus
Length of output: 14009
🏁 Script executed:
Repository: qiin2333/moonlight-vplus
Length of output: 6413
Preserve DS5 touchpad precedence for three-finger contacts. When
screenDs5Touchpadis enabled andpointerCount >= 3, this branch sends the event togame.panZoomHandler.handleTouchEvent(event)and returns beforetrySendScreenDs5TouchpadEvent. The DS5 handler supports all pointers duringACTION_MOVE, so this changes three-finger DS5 input into pan/zoom input. Keep the DS5 branch ahead of this interception or exclude DS5 touchpad mode.🤖 Prompt for AI Agents