Skip to content

Commit 4b2e349

Browse files
committed
feat: 游戏内使用手柄前弹出偏好设置对话框
1 parent 298844b commit 4b2e349

9 files changed

Lines changed: 224 additions & 1 deletion

File tree

‎ZalithLauncher/src/main/java/com/movtery/zalithlauncher/game/launch/handler/GameHandler.kt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ class GameHandler(
161161
if (event.action == KeyEvent.ACTION_UP && (event.flags and KeyEvent.FLAG_CANCELED) != 0) return false
162162

163163
if (event.isGamepadKeyEvent()) {
164+
if (AllSettings.gamepadControl.state && gamepadViewModel.checkModePrompt()) {
165+
//模式选择询问完成前,吞掉所有手柄按键输入
166+
return false
167+
}
164168
if (AllSettings.gamepadControl.state && AllSettings.gamepadInputMode.state == GamepadInputMode.SdlDirect) {
165169
//SDL 直通模式下,按键原样交给 SDL native
166170
if (SdlBridge.sdlEnabled) {

‎ZalithLauncher/src/main/java/com/movtery/zalithlauncher/setting/AllSettings.kt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,11 @@ object AllSettings : SettingsRegistry() {
258258
*/
259259
val gamepadInputMode = enumSetting("gamepadInputMode", GamepadInputMode.Mapped)
260260

261+
/**
262+
* 是否已完成手柄输入模式的选择询问
263+
*/
264+
val gamepadInputModePrompted = boolSetting("gamepadInputModePrompted", false)
265+
261266
/**
262267
* 摇杆死区缩放
263268
*/

‎ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/control/gamepad/GamepadEvents.kt‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ fun SimpleGamepadCapture(
109109

110110
DisposableEffect(view, gamepadViewModel) {
111111
val motionListener = View.OnGenericMotionListener { motionView, event ->
112-
if (isBinding()) {
112+
if (event.isGamepadEvent() && AllSettings.gamepadControl.state && gamepadViewModel.checkModePrompt()) {
113+
//模式选择询问优先级最高
114+
true
115+
} else if (isBinding()) {
113116
remapperViewModel.sendEvent(
114117
GamepadRemapperViewModel.Event.Axis(event)
115118
)

‎ZalithLauncher/src/main/java/com/movtery/zalithlauncher/ui/screens/game/GameScreen.kt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import com.movtery.zalithlauncher.ui.control.mouse.SwitchableMouseLayout
103103
import com.movtery.zalithlauncher.ui.screens.game.elements.DraggableGameBall
104104
import com.movtery.zalithlauncher.ui.screens.game.elements.ForceCloseOperation
105105
import com.movtery.zalithlauncher.ui.screens.game.elements.GameMenuSubscreen
106+
import com.movtery.zalithlauncher.ui.screens.game.elements.GamepadModePromptDialog
106107
import com.movtery.zalithlauncher.ui.screens.game.elements.LogBox
107108
import com.movtery.zalithlauncher.ui.screens.game.elements.LogState
108109
import com.movtery.zalithlauncher.ui.screens.game.elements.ReplacementControlOperation
@@ -693,6 +694,14 @@ fun GameScreen(
693694
)
694695
}
695696

697+
//手柄输入模式选择询问
698+
GamepadModePromptDialog(
699+
visible = gamepadViewModel.modePromptVisible,
700+
onConfirm = { mode ->
701+
gamepadViewModel.confirmModePrompt(mode)
702+
}
703+
)
704+
696705
if (viewModel.isEditingLayout) {
697706
viewModel.currentControlFile?.let {
698707
ControlEditor(
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Zalith Launcher 2
3+
* Copyright (C) 2025 MovTery <movtery228@qq.com> and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13+
* See the GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
17+
*/
18+
19+
package com.movtery.zalithlauncher.ui.screens.game.elements
20+
21+
import androidx.compose.foundation.clickable
22+
import androidx.compose.foundation.layout.Arrangement
23+
import androidx.compose.foundation.layout.BoxWithConstraints
24+
import androidx.compose.foundation.layout.Column
25+
import androidx.compose.foundation.layout.Row
26+
import androidx.compose.foundation.layout.fillMaxHeight
27+
import androidx.compose.foundation.layout.fillMaxWidth
28+
import androidx.compose.foundation.layout.heightIn
29+
import androidx.compose.foundation.layout.padding
30+
import androidx.compose.foundation.layout.wrapContentHeight
31+
import androidx.compose.foundation.rememberScrollState
32+
import androidx.compose.foundation.verticalScroll
33+
import androidx.compose.material3.FilledTonalButton
34+
import androidx.compose.material3.MaterialTheme
35+
import androidx.compose.material3.RadioButton
36+
import androidx.compose.material3.Surface
37+
import androidx.compose.material3.Text
38+
import androidx.compose.runtime.Composable
39+
import androidx.compose.runtime.getValue
40+
import androidx.compose.runtime.mutableStateOf
41+
import androidx.compose.runtime.remember
42+
import androidx.compose.runtime.setValue
43+
import androidx.compose.ui.Alignment
44+
import androidx.compose.ui.Modifier
45+
import androidx.compose.ui.draw.clip
46+
import androidx.compose.ui.focus.focusProperties
47+
import androidx.compose.ui.res.stringResource
48+
import androidx.compose.ui.unit.dp
49+
import androidx.compose.ui.window.Dialog
50+
import androidx.compose.ui.window.DialogProperties
51+
import com.movtery.zalithlauncher.R
52+
import com.movtery.zalithlauncher.setting.AllSettings
53+
import com.movtery.zalithlauncher.setting.enums.GamepadInputMode
54+
import com.movtery.zalithlauncher.ui.components.rememberDialogMaxHeight
55+
import com.movtery.zalithlauncher.ui.theme.cardColor
56+
import com.movtery.zalithlauncher.ui.theme.onCardColor
57+
58+
/**
59+
* 手柄输入模式选择对话框
60+
*/
61+
@Composable
62+
fun GamepadModePromptDialog(
63+
visible: Boolean,
64+
onConfirm: (GamepadInputMode) -> Unit
65+
) {
66+
if (!visible) return
67+
var selected by remember {
68+
mutableStateOf(AllSettings.gamepadInputMode.state)
69+
}
70+
71+
Dialog(
72+
onDismissRequest = {},
73+
properties = DialogProperties(
74+
dismissOnBackPress = false,
75+
dismissOnClickOutside = false,
76+
usePlatformDefaultWidth = false
77+
)
78+
) {
79+
BoxWithConstraints(
80+
modifier = Modifier
81+
.fillMaxWidth(0.65f)
82+
.heightIn(max = rememberDialogMaxHeight())
83+
.fillMaxHeight(),
84+
contentAlignment = Alignment.Center
85+
) {
86+
Surface(
87+
modifier = Modifier
88+
.padding(all = 6.dp)
89+
.heightIn(max = (maxHeight - 12.dp).coerceAtMost(rememberDialogMaxHeight()))
90+
.wrapContentHeight(),
91+
shape = MaterialTheme.shapes.extraLarge,
92+
color = cardColor(false),
93+
contentColor = onCardColor(),
94+
shadowElevation = 6.dp
95+
) {
96+
Column(
97+
modifier = Modifier.padding(all = 24.dp),
98+
verticalArrangement = Arrangement.spacedBy(12.dp)
99+
) {
100+
Text(
101+
text = stringResource(R.string.settings_gamepad_input_mode_title),
102+
style = MaterialTheme.typography.headlineSmall
103+
)
104+
105+
// 内容区域
106+
Column(
107+
modifier = Modifier
108+
.weight(1f, fill = false)
109+
.verticalScroll(rememberScrollState()),
110+
verticalArrangement = Arrangement.spacedBy(12.dp)
111+
) {
112+
Text(
113+
text = stringResource(R.string.gamepad_mode_prompt_description),
114+
style = MaterialTheme.typography.bodyMedium,
115+
color = MaterialTheme.colorScheme.onSurfaceVariant
116+
)
117+
118+
GamepadInputMode.entries.forEach { mode ->
119+
Row(
120+
modifier = Modifier
121+
.fillMaxWidth()
122+
.clip(MaterialTheme.shapes.medium)
123+
.clickable { selected = mode },
124+
verticalAlignment = Alignment.CenterVertically
125+
) {
126+
RadioButton(
127+
selected = selected == mode,
128+
onClick = { selected = mode }
129+
)
130+
Column(
131+
modifier = Modifier.padding(start = 4.dp)
132+
) {
133+
Text(
134+
text = stringResource(mode.titleRes),
135+
style = MaterialTheme.typography.bodyMedium
136+
)
137+
Text(
138+
text = stringResource(mode.summaryRes),
139+
style = MaterialTheme.typography.bodySmall,
140+
color = MaterialTheme.colorScheme.onSurfaceVariant
141+
)
142+
}
143+
}
144+
}
145+
146+
Text(
147+
text = stringResource(R.string.gamepad_mode_prompt_hint),
148+
style = MaterialTheme.typography.bodySmall,
149+
color = MaterialTheme.colorScheme.onSurfaceVariant
150+
)
151+
}
152+
153+
Row(
154+
modifier = Modifier.fillMaxWidth(),
155+
horizontalArrangement = Arrangement.End
156+
) {
157+
FilledTonalButton(
158+
//确认按钮不参与焦点导航,手柄输入也已在更上游被吞掉,避免误触
159+
modifier = Modifier.focusProperties { canFocus = false },
160+
onClick = { onConfirm(selected) }
161+
) {
162+
Text(stringResource(R.string.generic_confirm))
163+
}
164+
}
165+
}
166+
}
167+
}
168+
}
169+
}

‎ZalithLauncher/src/main/java/com/movtery/zalithlauncher/viewmodel/GamepadViewModel.kt‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.compose.ui.geometry.Offset
2727
import androidx.core.util.set
2828
import androidx.lifecycle.ViewModel
2929
import com.movtery.zalithlauncher.setting.AllSettings
30+
import com.movtery.zalithlauncher.setting.enums.GamepadInputMode
3031
import com.movtery.zalithlauncher.ui.control.gamepad.DpadDirection
3132
import com.movtery.zalithlauncher.ui.control.gamepad.GamepadMap
3233
import com.movtery.zalithlauncher.ui.control.gamepad.GamepadMapping
@@ -120,6 +121,32 @@ class GamepadViewModel : ViewModel() {
120121
reloadAllMappings()
121122
}
122123

124+
/**
125+
* 手柄输入模式选择询问是否正在显示
126+
*/
127+
var modePromptVisible by mutableStateOf(false)
128+
private set
129+
130+
/**
131+
* 手柄活动上报:尚未完成选择询问时弹出对话框
132+
* @return true 表示询问未完成,调用方应吞掉本次手柄输入,
133+
* 保证选择确认前映射、SDL 直通、绑定向导均不响应手柄
134+
*/
135+
fun checkModePrompt(): Boolean {
136+
if (AllSettings.gamepadInputModePrompted.state) return false
137+
modePromptVisible = true
138+
return true
139+
}
140+
141+
/**
142+
* 用户确认了模式选择,保存并关闭
143+
*/
144+
fun confirmModePrompt(selected: GamepadInputMode) {
145+
AllSettings.gamepadInputMode.save(selected)
146+
AllSettings.gamepadInputModePrompted.save(true)
147+
modePromptVisible = false
148+
}
149+
123150
/**
124151
* 检查并更新手柄是否活动中
125152
* @return 当前轮询频率等级

‎ZalithLauncher/src/main/res/values-zh-rCN/strings.xml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,8 @@
983983
<string name="settings_gamepad_input_mode_mapped_summary">手柄按键与摇杆作为虚拟键盘和鼠标使用</string>
984984
<string name="settings_gamepad_input_mode_sdl">SDL 直通</string>
985985
<string name="settings_gamepad_input_mode_sdl_summary">手柄输入经 SDL/GLFW gamepad API 原样交给游戏,不经过按键映射</string>
986+
<string name="gamepad_mode_prompt_description">检测到手柄输入。启动器支持两种手柄输入模式,请选择您希望使用的模式。</string>
987+
<string name="gamepad_mode_prompt_hint">稍后可以在游戏菜单的“手柄”分页或启动器设置中更改。</string>
986988
<string name="settings_gamepad_deadzone_title">手柄死区缩放</string>
987989
<string name="settings_gamepad_deadzone_summary">设置摇杆中心无响应区域的比例</string>
988990
<string name="settings_gamepad_joystick_mode_title">摇杆操作模式</string>

‎ZalithLauncher/src/main/res/values-zh-rTW/strings.xml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,8 @@
894894
<string name="settings_gamepad_input_mode_mapped_summary">手把按鍵與搖桿作為虛擬鍵盤和滑鼠使用</string>
895895
<string name="settings_gamepad_input_mode_sdl">SDL 直通</string>
896896
<string name="settings_gamepad_input_mode_sdl_summary">手把輸入經 SDL/GLFW gamepad API 原樣交給遊戲,不經過按鍵映射</string>
897+
<string name="gamepad_mode_prompt_description">偵測到手把輸入。啟動器支援兩種手把輸入模式,請選擇您希望使用的模式。</string>
898+
<string name="gamepad_mode_prompt_hint">稍後可以在遊戲選單的「手把」分頁或啟動器設定中更改。</string>
897899
<string name="settings_gamepad_deadzone_title">手把死區縮放</string>
898900
<string name="settings_gamepad_deadzone_summary">設定搖桿中心無反應區域的比例</string>
899901
<string name="settings_gamepad_joystick_mode_title">搖桿操作模式</string>

‎ZalithLauncher/src/main/res/values/strings.xml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,8 @@
983983
<string name="settings_gamepad_input_mode_mapped_summary">Gamepad buttons and joysticks act as virtual keyboard and mouse</string>
984984
<string name="settings_gamepad_input_mode_sdl">SDL Direct</string>
985985
<string name="settings_gamepad_input_mode_sdl_summary">Raw gamepad input is delivered to the game via the SDL/GLFW gamepad API; button remapping is bypassed</string>
986+
<string name="gamepad_mode_prompt_description">Gamepad input detected. The launcher supports two gamepad input modes; please choose the one you want to use.</string>
987+
<string name="gamepad_mode_prompt_hint">You can change this later in the "Gamepad" tab of the in-game menu or in the launcher settings.</string>
986988
<string name="settings_gamepad_deadzone_title">Gamepad Deadzone Scaling</string>
987989
<string name="settings_gamepad_deadzone_summary">Set the ratio of the joystick\'s non-responsive center area</string>
988990
<string name="settings_gamepad_joystick_mode_title">Joystick Operation Mode</string>

0 commit comments

Comments
 (0)