diff options
author | pineappleEA <pineaea@gmail.com> | 2024-02-18 07:19:28 +0100 |
---|---|---|
committer | pineappleEA <pineaea@gmail.com> | 2024-02-18 07:19:28 +0100 |
commit | d2d37b01f7f421d3e6667eee15c220ab11bce5a3 (patch) | |
tree | efedbb07409a24a206796b369a1b876c2a905738 | |
parent | ebd0305be19f4156952811ce1fbe20ecc137eb6f (diff) |
early-access version 4146EA-4146
135 files changed, 7044 insertions, 1984 deletions
@@ -1,7 +1,7 @@ | |||
1 | yuzu emulator early access | 1 | yuzu emulator early access |
2 | ============= | 2 | ============= |
3 | 3 | ||
4 | This is the source code for early-access 4145. | 4 | This is the source code for early-access 4146. |
5 | 5 | ||
6 | ## Legal Notice | 6 | ## Legal Notice |
7 | 7 | ||
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index 7890b30ca..b037fc055 100755 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
@@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
14 | <uses-permission android:name="android.permission.INTERNET" /> | 14 | <uses-permission android:name="android.permission.INTERNET" /> |
15 | <uses-permission android:name="android.permission.NFC" /> | 15 | <uses-permission android:name="android.permission.NFC" /> |
16 | <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> | 16 | <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> |
17 | <uses-permission android:name="android.permission.VIBRATE" /> | ||
17 | 18 | ||
18 | <application | 19 | <application |
19 | android:name="org.yuzu.yuzu_emu.YuzuApplication" | 20 | android:name="org.yuzu.yuzu_emu.YuzuApplication" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 6ebb46af7..02a20dacf 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
@@ -3,24 +3,21 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu | 4 | package org.yuzu.yuzu_emu |
5 | 5 | ||
6 | import android.app.Dialog | ||
7 | import android.content.DialogInterface | 6 | import android.content.DialogInterface |
8 | import android.net.Uri | 7 | import android.net.Uri |
9 | import android.os.Bundle | ||
10 | import android.text.Html | 8 | import android.text.Html |
11 | import android.text.method.LinkMovementMethod | 9 | import android.text.method.LinkMovementMethod |
12 | import android.view.Surface | 10 | import android.view.Surface |
13 | import android.view.View | 11 | import android.view.View |
14 | import android.widget.TextView | 12 | import android.widget.TextView |
15 | import androidx.annotation.Keep | 13 | import androidx.annotation.Keep |
16 | import androidx.fragment.app.DialogFragment | ||
17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
18 | import java.lang.ref.WeakReference | 15 | import java.lang.ref.WeakReference |
19 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 16 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
17 | import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment | ||
20 | import org.yuzu.yuzu_emu.utils.DocumentsTree | 18 | import org.yuzu.yuzu_emu.utils.DocumentsTree |
21 | import org.yuzu.yuzu_emu.utils.FileUtil | 19 | import org.yuzu.yuzu_emu.utils.FileUtil |
22 | import org.yuzu.yuzu_emu.utils.Log | 20 | import org.yuzu.yuzu_emu.utils.Log |
23 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | ||
24 | import org.yuzu.yuzu_emu.model.InstallResult | 21 | import org.yuzu.yuzu_emu.model.InstallResult |
25 | import org.yuzu.yuzu_emu.model.Patch | 22 | import org.yuzu.yuzu_emu.model.Patch |
26 | import org.yuzu.yuzu_emu.model.GameVerificationResult | 23 | import org.yuzu.yuzu_emu.model.GameVerificationResult |
@@ -30,34 +27,6 @@ import org.yuzu.yuzu_emu.model.GameVerificationResult | |||
30 | * with the native side of the Yuzu code. | 27 | * with the native side of the Yuzu code. |
31 | */ | 28 | */ |
32 | object NativeLibrary { | 29 | object NativeLibrary { |
33 | /** | ||
34 | * Default controller id for each device | ||
35 | */ | ||
36 | const val Player1Device = 0 | ||
37 | const val Player2Device = 1 | ||
38 | const val Player3Device = 2 | ||
39 | const val Player4Device = 3 | ||
40 | const val Player5Device = 4 | ||
41 | const val Player6Device = 5 | ||
42 | const val Player7Device = 6 | ||
43 | const val Player8Device = 7 | ||
44 | const val ConsoleDevice = 8 | ||
45 | |||
46 | /** | ||
47 | * Controller type for each device | ||
48 | */ | ||
49 | const val ProController = 3 | ||
50 | const val Handheld = 4 | ||
51 | const val JoyconDual = 5 | ||
52 | const val JoyconLeft = 6 | ||
53 | const val JoyconRight = 7 | ||
54 | const val GameCube = 8 | ||
55 | const val Pokeball = 9 | ||
56 | const val NES = 10 | ||
57 | const val SNES = 11 | ||
58 | const val N64 = 12 | ||
59 | const val SegaGenesis = 13 | ||
60 | |||
61 | @JvmField | 30 | @JvmField |
62 | var sEmulationActivity = WeakReference<EmulationActivity?>(null) | 31 | var sEmulationActivity = WeakReference<EmulationActivity?>(null) |
63 | 32 | ||
@@ -127,112 +96,6 @@ object NativeLibrary { | |||
127 | FileUtil.getFilename(Uri.parse(path)) | 96 | FileUtil.getFilename(Uri.parse(path)) |
128 | } | 97 | } |
129 | 98 | ||
130 | /** | ||
131 | * Returns true if pro controller isn't available and handheld is | ||
132 | */ | ||
133 | external fun isHandheldOnly(): Boolean | ||
134 | |||
135 | /** | ||
136 | * Changes controller type for a specific device. | ||
137 | * | ||
138 | * @param Device The input descriptor of the gamepad. | ||
139 | * @param Type The NpadStyleIndex of the gamepad. | ||
140 | */ | ||
141 | external fun setDeviceType(Device: Int, Type: Int): Boolean | ||
142 | |||
143 | /** | ||
144 | * Handles event when a gamepad is connected. | ||
145 | * | ||
146 | * @param Device The input descriptor of the gamepad. | ||
147 | */ | ||
148 | external fun onGamePadConnectEvent(Device: Int): Boolean | ||
149 | |||
150 | /** | ||
151 | * Handles event when a gamepad is disconnected. | ||
152 | * | ||
153 | * @param Device The input descriptor of the gamepad. | ||
154 | */ | ||
155 | external fun onGamePadDisconnectEvent(Device: Int): Boolean | ||
156 | |||
157 | /** | ||
158 | * Handles button press events for a gamepad. | ||
159 | * | ||
160 | * @param Device The input descriptor of the gamepad. | ||
161 | * @param Button Key code identifying which button was pressed. | ||
162 | * @param Action Mask identifying which action is happening (button pressed down, or button released). | ||
163 | * @return If we handled the button press. | ||
164 | */ | ||
165 | external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean | ||
166 | |||
167 | /** | ||
168 | * Handles joystick movement events. | ||
169 | * | ||
170 | * @param Device The device ID of the gamepad. | ||
171 | * @param Axis The axis ID | ||
172 | * @param x_axis The value of the x-axis represented by the given ID. | ||
173 | * @param y_axis The value of the y-axis represented by the given ID. | ||
174 | */ | ||
175 | external fun onGamePadJoystickEvent( | ||
176 | Device: Int, | ||
177 | Axis: Int, | ||
178 | x_axis: Float, | ||
179 | y_axis: Float | ||
180 | ): Boolean | ||
181 | |||
182 | /** | ||
183 | * Handles motion events. | ||
184 | * | ||
185 | * @param delta_timestamp The finger id corresponding to this event | ||
186 | * @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor. | ||
187 | * @param accel_x,accel_y,accel_z The value of the y-axis | ||
188 | */ | ||
189 | external fun onGamePadMotionEvent( | ||
190 | Device: Int, | ||
191 | delta_timestamp: Long, | ||
192 | gyro_x: Float, | ||
193 | gyro_y: Float, | ||
194 | gyro_z: Float, | ||
195 | accel_x: Float, | ||
196 | accel_y: Float, | ||
197 | accel_z: Float | ||
198 | ): Boolean | ||
199 | |||
200 | /** | ||
201 | * Signals and load a nfc tag | ||
202 | * | ||
203 | * @param data Byte array containing all the data from a nfc tag | ||
204 | */ | ||
205 | external fun onReadNfcTag(data: ByteArray?): Boolean | ||
206 | |||
207 | /** | ||
208 | * Removes current loaded nfc tag | ||
209 | */ | ||
210 | external fun onRemoveNfcTag(): Boolean | ||
211 | |||
212 | /** | ||
213 | * Handles touch press events. | ||
214 | * | ||
215 | * @param finger_id The finger id corresponding to this event | ||
216 | * @param x_axis The value of the x-axis. | ||
217 | * @param y_axis The value of the y-axis. | ||
218 | */ | ||
219 | external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float) | ||
220 | |||
221 | /** | ||
222 | * Handles touch movement. | ||
223 | * | ||
224 | * @param x_axis The value of the instantaneous x-axis. | ||
225 | * @param y_axis The value of the instantaneous y-axis. | ||
226 | */ | ||
227 | external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float) | ||
228 | |||
229 | /** | ||
230 | * Handles touch release events. | ||
231 | * | ||
232 | * @param finger_id The finger id corresponding to this event | ||
233 | */ | ||
234 | external fun onTouchReleased(finger_id: Int) | ||
235 | |||
236 | external fun setAppDirectory(directory: String) | 99 | external fun setAppDirectory(directory: String) |
237 | 100 | ||
238 | /** | 101 | /** |
@@ -318,46 +181,13 @@ object NativeLibrary { | |||
318 | ErrorUnknown | 181 | ErrorUnknown |
319 | } | 182 | } |
320 | 183 | ||
321 | private var coreErrorAlertResult = false | 184 | var coreErrorAlertResult = false |
322 | private val coreErrorAlertLock = Object() | 185 | val coreErrorAlertLock = Object() |
323 | |||
324 | class CoreErrorDialogFragment : DialogFragment() { | ||
325 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
326 | val title = requireArguments().serializable<String>("title") | ||
327 | val message = requireArguments().serializable<String>("message") | ||
328 | |||
329 | return MaterialAlertDialogBuilder(requireActivity()) | ||
330 | .setTitle(title) | ||
331 | .setMessage(message) | ||
332 | .setPositiveButton(R.string.continue_button, null) | ||
333 | .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> | ||
334 | coreErrorAlertResult = false | ||
335 | synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } | ||
336 | } | ||
337 | .create() | ||
338 | } | ||
339 | |||
340 | override fun onDismiss(dialog: DialogInterface) { | ||
341 | coreErrorAlertResult = true | ||
342 | synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } | ||
343 | } | ||
344 | |||
345 | companion object { | ||
346 | fun newInstance(title: String?, message: String?): CoreErrorDialogFragment { | ||
347 | val frag = CoreErrorDialogFragment() | ||
348 | val args = Bundle() | ||
349 | args.putString("title", title) | ||
350 | args.putString("message", message) | ||
351 | frag.arguments = args | ||
352 | return frag | ||
353 | } | ||
354 | } | ||
355 | } | ||
356 | 186 | ||
357 | private fun onCoreErrorImpl(title: String, message: String) { | 187 | private fun onCoreErrorImpl(title: String, message: String) { |
358 | val emulationActivity = sEmulationActivity.get() | 188 | val emulationActivity = sEmulationActivity.get() |
359 | if (emulationActivity == null) { | 189 | if (emulationActivity == null) { |
360 | error("[NativeLibrary] EmulationActivity not present") | 190 | Log.error("[NativeLibrary] EmulationActivity not present") |
361 | return | 191 | return |
362 | } | 192 | } |
363 | 193 | ||
@@ -373,7 +203,7 @@ object NativeLibrary { | |||
373 | fun onCoreError(error: CoreError?, details: String): Boolean { | 203 | fun onCoreError(error: CoreError?, details: String): Boolean { |
374 | val emulationActivity = sEmulationActivity.get() | 204 | val emulationActivity = sEmulationActivity.get() |
375 | if (emulationActivity == null) { | 205 | if (emulationActivity == null) { |
376 | error("[NativeLibrary] EmulationActivity not present") | 206 | Log.error("[NativeLibrary] EmulationActivity not present") |
377 | return false | 207 | return false |
378 | } | 208 | } |
379 | 209 | ||
@@ -404,7 +234,7 @@ object NativeLibrary { | |||
404 | } | 234 | } |
405 | 235 | ||
406 | // Show the AlertDialog on the main thread. | 236 | // Show the AlertDialog on the main thread. |
407 | emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) }) | 237 | emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) } |
408 | 238 | ||
409 | // Wait for the lock to notify that it is complete. | 239 | // Wait for the lock to notify that it is complete. |
410 | synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() } | 240 | synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() } |
@@ -629,46 +459,4 @@ object NativeLibrary { | |||
629 | * Checks if all necessary keys are present for decryption | 459 | * Checks if all necessary keys are present for decryption |
630 | */ | 460 | */ |
631 | external fun areKeysPresent(): Boolean | 461 | external fun areKeysPresent(): Boolean |
632 | |||
633 | /** | ||
634 | * Button type for use in onTouchEvent | ||
635 | */ | ||
636 | object ButtonType { | ||
637 | const val BUTTON_A = 0 | ||
638 | const val BUTTON_B = 1 | ||
639 | const val BUTTON_X = 2 | ||
640 | const val BUTTON_Y = 3 | ||
641 | const val STICK_L = 4 | ||
642 | const val STICK_R = 5 | ||
643 | const val TRIGGER_L = 6 | ||
644 | const val TRIGGER_R = 7 | ||
645 | const val TRIGGER_ZL = 8 | ||
646 | const val TRIGGER_ZR = 9 | ||
647 | const val BUTTON_PLUS = 10 | ||
648 | const val BUTTON_MINUS = 11 | ||
649 | const val DPAD_LEFT = 12 | ||
650 | const val DPAD_UP = 13 | ||
651 | const val DPAD_RIGHT = 14 | ||
652 | const val DPAD_DOWN = 15 | ||
653 | const val BUTTON_SL = 16 | ||
654 | const val BUTTON_SR = 17 | ||
655 | const val BUTTON_HOME = 18 | ||
656 | const val BUTTON_CAPTURE = 19 | ||
657 | } | ||
658 | |||
659 | /** | ||
660 | * Stick type for use in onTouchEvent | ||
661 | */ | ||
662 | object StickType { | ||
663 | const val STICK_L = 0 | ||
664 | const val STICK_R = 1 | ||
665 | } | ||
666 | |||
667 | /** | ||
668 | * Button states | ||
669 | */ | ||
670 | object ButtonState { | ||
671 | const val RELEASED = 0 | ||
672 | const val PRESSED = 1 | ||
673 | } | ||
674 | } | 462 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 76778c10a..72943f33e 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt | |||
@@ -7,6 +7,7 @@ import android.app.Application | |||
7 | import android.app.NotificationChannel | 7 | import android.app.NotificationChannel |
8 | import android.app.NotificationManager | 8 | import android.app.NotificationManager |
9 | import android.content.Context | 9 | import android.content.Context |
10 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
10 | import java.io.File | 11 | import java.io.File |
11 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
12 | import org.yuzu.yuzu_emu.utils.DocumentsTree | 13 | import org.yuzu.yuzu_emu.utils.DocumentsTree |
@@ -37,6 +38,7 @@ class YuzuApplication : Application() { | |||
37 | documentsTree = DocumentsTree() | 38 | documentsTree = DocumentsTree() |
38 | DirectoryInitialization.start() | 39 | DirectoryInitialization.start() |
39 | GpuDriverHelper.initializeDriverParameters() | 40 | GpuDriverHelper.initializeDriverParameters() |
41 | NativeInput.reloadInputDevices() | ||
40 | NativeLibrary.logDeviceInfo() | 42 | NativeLibrary.logDeviceInfo() |
41 | Log.logDeviceInfo() | 43 | Log.logDeviceInfo() |
42 | 44 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 7a8d03610..0b70fccec 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
@@ -39,6 +39,7 @@ import org.yuzu.yuzu_emu.NativeLibrary | |||
39 | import org.yuzu.yuzu_emu.R | 39 | import org.yuzu.yuzu_emu.R |
40 | import org.yuzu.yuzu_emu.YuzuApplication | 40 | import org.yuzu.yuzu_emu.YuzuApplication |
41 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | 41 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding |
42 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
42 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 43 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
43 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 44 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
44 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 45 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
@@ -47,7 +48,9 @@ import org.yuzu.yuzu_emu.model.Game | |||
47 | import org.yuzu.yuzu_emu.utils.InputHandler | 48 | import org.yuzu.yuzu_emu.utils.InputHandler |
48 | import org.yuzu.yuzu_emu.utils.Log | 49 | import org.yuzu.yuzu_emu.utils.Log |
49 | import org.yuzu.yuzu_emu.utils.MemoryUtil | 50 | import org.yuzu.yuzu_emu.utils.MemoryUtil |
51 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
50 | import org.yuzu.yuzu_emu.utils.NfcReader | 52 | import org.yuzu.yuzu_emu.utils.NfcReader |
53 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
51 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 54 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
52 | import java.text.NumberFormat | 55 | import java.text.NumberFormat |
53 | import kotlin.math.roundToInt | 56 | import kotlin.math.roundToInt |
@@ -63,8 +66,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
63 | private var motionTimestamp: Long = 0 | 66 | private var motionTimestamp: Long = 0 |
64 | private var flipMotionOrientation: Boolean = false | 67 | private var flipMotionOrientation: Boolean = false |
65 | 68 | ||
66 | private var controllerIds = InputHandler.getGameControllerIds() | ||
67 | |||
68 | private val actionPause = "ACTION_EMULATOR_PAUSE" | 69 | private val actionPause = "ACTION_EMULATOR_PAUSE" |
69 | private val actionPlay = "ACTION_EMULATOR_PLAY" | 70 | private val actionPlay = "ACTION_EMULATOR_PLAY" |
70 | private val actionMute = "ACTION_EMULATOR_MUTE" | 71 | private val actionMute = "ACTION_EMULATOR_MUTE" |
@@ -78,6 +79,27 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
78 | 79 | ||
79 | super.onCreate(savedInstanceState) | 80 | super.onCreate(savedInstanceState) |
80 | 81 | ||
82 | InputHandler.updateControllerData() | ||
83 | val playerOne = NativeConfig.getInputSettings(true)[0] | ||
84 | if (!playerOne.hasMapping() && InputHandler.androidControllers.isNotEmpty()) { | ||
85 | var params: ParamPackage? = null | ||
86 | for (controller in InputHandler.registeredControllers) { | ||
87 | if (controller.get("port", -1) == 0) { | ||
88 | params = controller | ||
89 | break | ||
90 | } | ||
91 | } | ||
92 | |||
93 | if (params != null) { | ||
94 | NativeInput.updateMappingsWithDefault( | ||
95 | 0, | ||
96 | params, | ||
97 | params.get("display", getString(R.string.unknown)) | ||
98 | ) | ||
99 | NativeConfig.saveGlobalConfig() | ||
100 | } | ||
101 | } | ||
102 | |||
81 | binding = ActivityEmulationBinding.inflate(layoutInflater) | 103 | binding = ActivityEmulationBinding.inflate(layoutInflater) |
82 | setContentView(binding.root) | 104 | setContentView(binding.root) |
83 | 105 | ||
@@ -95,8 +117,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
95 | nfcReader = NfcReader(this) | 117 | nfcReader = NfcReader(this) |
96 | nfcReader.initialize() | 118 | nfcReader.initialize() |
97 | 119 | ||
98 | InputHandler.initialize() | ||
99 | |||
100 | val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 120 | val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
101 | if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { | 121 | if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) { |
102 | if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) { | 122 | if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) { |
@@ -147,7 +167,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
147 | super.onResume() | 167 | super.onResume() |
148 | nfcReader.startScanning() | 168 | nfcReader.startScanning() |
149 | startMotionSensorListener() | 169 | startMotionSensorListener() |
150 | InputHandler.updateControllerIds() | 170 | InputHandler.updateControllerData() |
151 | 171 | ||
152 | buildPictureInPictureParams() | 172 | buildPictureInPictureParams() |
153 | } | 173 | } |
@@ -172,6 +192,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
172 | super.onNewIntent(intent) | 192 | super.onNewIntent(intent) |
173 | setIntent(intent) | 193 | setIntent(intent) |
174 | nfcReader.onNewIntent(intent) | 194 | nfcReader.onNewIntent(intent) |
195 | InputHandler.updateControllerData() | ||
175 | } | 196 | } |
176 | 197 | ||
177 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { | 198 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { |
@@ -244,8 +265,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
244 | } | 265 | } |
245 | val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000 | 266 | val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000 |
246 | motionTimestamp = event.timestamp | 267 | motionTimestamp = event.timestamp |
247 | NativeLibrary.onGamePadMotionEvent( | 268 | NativeInput.onDeviceMotionEvent( |
248 | NativeLibrary.Player1Device, | 269 | NativeInput.Player1Device, |
249 | deltaTimestamp, | 270 | deltaTimestamp, |
250 | gyro[0], | 271 | gyro[0], |
251 | gyro[1], | 272 | gyro[1], |
@@ -254,8 +275,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
254 | accel[1], | 275 | accel[1], |
255 | accel[2] | 276 | accel[2] |
256 | ) | 277 | ) |
257 | NativeLibrary.onGamePadMotionEvent( | 278 | NativeInput.onDeviceMotionEvent( |
258 | NativeLibrary.ConsoleDevice, | 279 | NativeInput.ConsoleDevice, |
259 | deltaTimestamp, | 280 | deltaTimestamp, |
260 | gyro[0], | 281 | gyro[0], |
261 | gyro[1], | 282 | gyro[1], |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt index f218c76ef..50663ad91 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt | |||
@@ -3,15 +3,15 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.text.TextUtils | ||
7 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
8 | import android.view.View | ||
9 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
10 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
11 | import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding | 9 | import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding |
12 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting |
13 | import org.yuzu.yuzu_emu.model.Driver | 11 | import org.yuzu.yuzu_emu.model.Driver |
14 | import org.yuzu.yuzu_emu.model.DriverViewModel | 12 | import org.yuzu.yuzu_emu.model.DriverViewModel |
13 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
14 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
15 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 15 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
16 | 16 | ||
17 | class DriverAdapter(private val driverViewModel: DriverViewModel) : | 17 | class DriverAdapter(private val driverViewModel: DriverViewModel) : |
@@ -44,25 +44,15 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) : | |||
44 | } | 44 | } |
45 | 45 | ||
46 | // Delay marquee by 3s | 46 | // Delay marquee by 3s |
47 | title.postDelayed( | 47 | title.marquee() |
48 | { | 48 | version.marquee() |
49 | title.isSelected = true | 49 | description.marquee() |
50 | title.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
51 | version.isSelected = true | ||
52 | version.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
53 | description.isSelected = true | ||
54 | description.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
55 | }, | ||
56 | 3000 | ||
57 | ) | ||
58 | title.text = model.title | 50 | title.text = model.title |
59 | version.text = model.version | 51 | version.text = model.version |
60 | description.text = model.description | 52 | description.text = model.description |
61 | if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) { | 53 | buttonDelete.setVisible( |
62 | buttonDelete.visibility = View.VISIBLE | 54 | model.title != binding.root.context.getString(R.string.system_gpu_driver) |
63 | } else { | 55 | ) |
64 | buttonDelete.visibility = View.GONE | ||
65 | } | ||
66 | } | 56 | } |
67 | } | 57 | } |
68 | } | 58 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt index 3d8f0bda8..5cbd15d2a 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt | |||
@@ -4,7 +4,6 @@ | |||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.net.Uri | 6 | import android.net.Uri |
7 | import android.text.TextUtils | ||
8 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
9 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
10 | import androidx.fragment.app.FragmentActivity | 9 | import androidx.fragment.app.FragmentActivity |
@@ -12,6 +11,7 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding | |||
12 | import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment | 11 | import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment |
13 | import org.yuzu.yuzu_emu.model.GameDir | 12 | import org.yuzu.yuzu_emu.model.GameDir |
14 | import org.yuzu.yuzu_emu.model.GamesViewModel | 13 | import org.yuzu.yuzu_emu.model.GamesViewModel |
14 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
15 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 15 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
16 | 16 | ||
17 | class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : | 17 | class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) : |
@@ -29,13 +29,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie | |||
29 | override fun bind(model: GameDir) { | 29 | override fun bind(model: GameDir) { |
30 | binding.apply { | 30 | binding.apply { |
31 | path.text = Uri.parse(model.uriString).path | 31 | path.text = Uri.parse(model.uriString).path |
32 | path.postDelayed( | 32 | path.marquee() |
33 | { | ||
34 | path.isSelected = true | ||
35 | path.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
36 | }, | ||
37 | 3000 | ||
38 | ) | ||
39 | 33 | ||
40 | buttonEdit.setOnClickListener { | 34 | buttonEdit.setOnClickListener { |
41 | GameFolderPropertiesDialogFragment.newInstance(model) | 35 | GameFolderPropertiesDialogFragment.newInstance(model) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 85c8249e6..b1f247ac3 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt | |||
@@ -4,7 +4,6 @@ | |||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.net.Uri | 6 | import android.net.Uri |
7 | import android.text.TextUtils | ||
8 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
9 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
10 | import android.widget.ImageView | 9 | import android.widget.ImageView |
@@ -27,6 +26,7 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding | |||
27 | import org.yuzu.yuzu_emu.model.Game | 26 | import org.yuzu.yuzu_emu.model.Game |
28 | import org.yuzu.yuzu_emu.model.GamesViewModel | 27 | import org.yuzu.yuzu_emu.model.GamesViewModel |
29 | import org.yuzu.yuzu_emu.utils.GameIconUtils | 28 | import org.yuzu.yuzu_emu.utils.GameIconUtils |
29 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
30 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 30 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
31 | 31 | ||
32 | class GameAdapter(private val activity: AppCompatActivity) : | 32 | class GameAdapter(private val activity: AppCompatActivity) : |
@@ -44,14 +44,7 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
44 | 44 | ||
45 | binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ") | 45 | binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ") |
46 | 46 | ||
47 | binding.textGameTitle.postDelayed( | 47 | binding.textGameTitle.marquee() |
48 | { | ||
49 | binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
50 | binding.textGameTitle.isSelected = true | ||
51 | }, | ||
52 | 3000 | ||
53 | ) | ||
54 | |||
55 | binding.cardGame.setOnClickListener { onClick(model) } | 48 | binding.cardGame.setOnClickListener { onClick(model) } |
56 | binding.cardGame.setOnLongClickListener { onLongClick(model) } | 49 | binding.cardGame.setOnLongClickListener { onLongClick(model) } |
57 | } | 50 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt index 0046d5314..7366e2c77 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt | |||
@@ -3,21 +3,18 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.text.TextUtils | ||
7 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
8 | import android.view.View | ||
9 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
10 | import androidx.core.content.res.ResourcesCompat | 8 | import androidx.core.content.res.ResourcesCompat |
11 | import androidx.lifecycle.Lifecycle | ||
12 | import androidx.lifecycle.LifecycleOwner | 9 | import androidx.lifecycle.LifecycleOwner |
13 | import androidx.lifecycle.lifecycleScope | ||
14 | import androidx.lifecycle.repeatOnLifecycle | ||
15 | import kotlinx.coroutines.launch | ||
16 | import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding | 10 | import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding |
17 | import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding | 11 | import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding |
18 | import org.yuzu.yuzu_emu.model.GameProperty | 12 | import org.yuzu.yuzu_emu.model.GameProperty |
19 | import org.yuzu.yuzu_emu.model.InstallableProperty | 13 | import org.yuzu.yuzu_emu.model.InstallableProperty |
20 | import org.yuzu.yuzu_emu.model.SubmenuProperty | 14 | import org.yuzu.yuzu_emu.model.SubmenuProperty |
15 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
16 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
17 | import org.yuzu.yuzu_emu.utils.collect | ||
21 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 18 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
22 | 19 | ||
23 | class GamePropertiesAdapter( | 20 | class GamePropertiesAdapter( |
@@ -76,23 +73,15 @@ class GamePropertiesAdapter( | |||
76 | ) | 73 | ) |
77 | ) | 74 | ) |
78 | 75 | ||
79 | binding.details.postDelayed({ | 76 | binding.details.marquee() |
80 | binding.details.isSelected = true | ||
81 | binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
82 | }, 3000) | ||
83 | |||
84 | if (submenuProperty.details != null) { | 77 | if (submenuProperty.details != null) { |
85 | binding.details.visibility = View.VISIBLE | 78 | binding.details.setVisible(true) |
86 | binding.details.text = submenuProperty.details.invoke() | 79 | binding.details.text = submenuProperty.details.invoke() |
87 | } else if (submenuProperty.detailsFlow != null) { | 80 | } else if (submenuProperty.detailsFlow != null) { |
88 | binding.details.visibility = View.VISIBLE | 81 | binding.details.setVisible(true) |
89 | viewLifecycle.lifecycleScope.launch { | 82 | submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it } |
90 | viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
91 | submenuProperty.detailsFlow.collect { binding.details.text = it } | ||
92 | } | ||
93 | } | ||
94 | } else { | 83 | } else { |
95 | binding.details.visibility = View.GONE | 84 | binding.details.setVisible(false) |
96 | } | 85 | } |
97 | } | 86 | } |
98 | } | 87 | } |
@@ -112,14 +101,10 @@ class GamePropertiesAdapter( | |||
112 | ) | 101 | ) |
113 | ) | 102 | ) |
114 | 103 | ||
115 | if (installableProperty.install != null) { | 104 | binding.buttonInstall.setVisible(installableProperty.install != null) |
116 | binding.buttonInstall.visibility = View.VISIBLE | 105 | binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() } |
117 | binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() } | 106 | binding.buttonExport.setVisible(installableProperty.export != null) |
118 | } | 107 | binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() } |
119 | if (installableProperty.export != null) { | ||
120 | binding.buttonExport.visibility = View.VISIBLE | ||
121 | binding.buttonExport.setOnClickListener { installableProperty.export.invoke() } | ||
122 | } | ||
123 | } | 108 | } |
124 | } | 109 | } |
125 | 110 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index b512845d5..0bd196673 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
@@ -3,22 +3,19 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.text.TextUtils | ||
7 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
8 | import android.view.View | ||
9 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
10 | import androidx.appcompat.app.AppCompatActivity | 8 | import androidx.appcompat.app.AppCompatActivity |
11 | import androidx.core.content.ContextCompat | 9 | import androidx.core.content.ContextCompat |
12 | import androidx.core.content.res.ResourcesCompat | 10 | import androidx.core.content.res.ResourcesCompat |
13 | import androidx.lifecycle.Lifecycle | ||
14 | import androidx.lifecycle.LifecycleOwner | 11 | import androidx.lifecycle.LifecycleOwner |
15 | import androidx.lifecycle.lifecycleScope | ||
16 | import androidx.lifecycle.repeatOnLifecycle | ||
17 | import kotlinx.coroutines.launch | ||
18 | import org.yuzu.yuzu_emu.R | 12 | import org.yuzu.yuzu_emu.R |
19 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding | 13 | import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding |
20 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | 14 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
21 | import org.yuzu.yuzu_emu.model.HomeSetting | 15 | import org.yuzu.yuzu_emu.model.HomeSetting |
16 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
17 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
18 | import org.yuzu.yuzu_emu.utils.collect | ||
22 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 19 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
23 | 20 | ||
24 | class HomeSettingAdapter( | 21 | class HomeSettingAdapter( |
@@ -59,18 +56,8 @@ class HomeSettingAdapter( | |||
59 | binding.optionIcon.alpha = 0.5f | 56 | binding.optionIcon.alpha = 0.5f |
60 | } | 57 | } |
61 | 58 | ||
62 | viewLifecycle.lifecycleScope.launch { | 59 | model.details.collect(viewLifecycle) { updateOptionDetails(it) } |
63 | viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { | 60 | binding.optionDetail.marquee() |
64 | model.details.collect { updateOptionDetails(it) } | ||
65 | } | ||
66 | } | ||
67 | binding.optionDetail.postDelayed( | ||
68 | { | ||
69 | binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
70 | binding.optionDetail.isSelected = true | ||
71 | }, | ||
72 | 3000 | ||
73 | ) | ||
74 | 61 | ||
75 | binding.root.setOnClickListener { onClick(model) } | 62 | binding.root.setOnClickListener { onClick(model) } |
76 | } | 63 | } |
@@ -90,7 +77,7 @@ class HomeSettingAdapter( | |||
90 | private fun updateOptionDetails(detailString: String) { | 77 | private fun updateOptionDetails(detailString: String) { |
91 | if (detailString.isNotEmpty()) { | 78 | if (detailString.isNotEmpty()) { |
92 | binding.optionDetail.text = detailString | 79 | binding.optionDetail.text = detailString |
93 | binding.optionDetail.visibility = View.VISIBLE | 80 | binding.optionDetail.setVisible(true) |
94 | } | 81 | } |
95 | } | 82 | } |
96 | } | 83 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt index 4218c4e52..1ba75fa2f 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/InstallableAdapter.kt | |||
@@ -4,10 +4,10 @@ | |||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
7 | import android.view.View | ||
8 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
9 | import org.yuzu.yuzu_emu.databinding.CardInstallableBinding | 8 | import org.yuzu.yuzu_emu.databinding.CardInstallableBinding |
10 | import org.yuzu.yuzu_emu.model.Installable | 9 | import org.yuzu.yuzu_emu.model.Installable |
10 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
11 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 11 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
12 | 12 | ||
13 | class InstallableAdapter(installables: List<Installable>) : | 13 | class InstallableAdapter(installables: List<Installable>) : |
@@ -26,14 +26,10 @@ class InstallableAdapter(installables: List<Installable>) : | |||
26 | binding.title.setText(model.titleId) | 26 | binding.title.setText(model.titleId) |
27 | binding.description.setText(model.descriptionId) | 27 | binding.description.setText(model.descriptionId) |
28 | 28 | ||
29 | if (model.install != null) { | 29 | binding.buttonInstall.setVisible(model.install != null) |
30 | binding.buttonInstall.visibility = View.VISIBLE | 30 | binding.buttonInstall.setOnClickListener { model.install?.invoke() } |
31 | binding.buttonInstall.setOnClickListener { model.install.invoke() } | 31 | binding.buttonExport.setVisible(model.export != null) |
32 | } | 32 | binding.buttonExport.setOnClickListener { model.export?.invoke() } |
33 | if (model.export != null) { | ||
34 | binding.buttonExport.visibility = View.VISIBLE | ||
35 | binding.buttonExport.setOnClickListener { model.export.invoke() } | ||
36 | } | ||
37 | } | 33 | } |
38 | } | 34 | } |
39 | } | 35 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt index 38bb1f96f..1379968f9 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/LicenseAdapter.kt | |||
@@ -4,12 +4,12 @@ | |||
4 | package org.yuzu.yuzu_emu.adapters | 4 | package org.yuzu.yuzu_emu.adapters |
5 | 5 | ||
6 | import android.view.LayoutInflater | 6 | import android.view.LayoutInflater |
7 | import android.view.View | ||
8 | import android.view.ViewGroup | 7 | import android.view.ViewGroup |
9 | import androidx.appcompat.app.AppCompatActivity | 8 | import androidx.appcompat.app.AppCompatActivity |
10 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 9 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
11 | import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment | 10 | import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment |
12 | import org.yuzu.yuzu_emu.model.License | 11 | import org.yuzu.yuzu_emu.model.License |
12 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 13 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
14 | 14 | ||
15 | class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) : | 15 | class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) : |
@@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic | |||
25 | binding.apply { | 25 | binding.apply { |
26 | textSettingName.text = root.context.getString(model.titleId) | 26 | textSettingName.text = root.context.getString(model.titleId) |
27 | textSettingDescription.text = root.context.getString(model.descriptionId) | 27 | textSettingDescription.text = root.context.getString(model.descriptionId) |
28 | textSettingValue.visibility = View.GONE | 28 | textSettingValue.setVisible(false) |
29 | 29 | ||
30 | root.setOnClickListener { onClick(model) } | 30 | root.setOnClickListener { onClick(model) } |
31 | } | 31 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt index 02118e1a8..a5f610b31 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt | |||
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.adapters | |||
5 | 5 | ||
6 | import android.text.Html | 6 | import android.text.Html |
7 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
8 | import android.view.View | ||
9 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
10 | import androidx.appcompat.app.AppCompatActivity | 9 | import androidx.appcompat.app.AppCompatActivity |
11 | import androidx.core.content.res.ResourcesCompat | 10 | import androidx.core.content.res.ResourcesCompat |
@@ -17,6 +16,7 @@ import org.yuzu.yuzu_emu.model.SetupCallback | |||
17 | import org.yuzu.yuzu_emu.model.SetupPage | 16 | import org.yuzu.yuzu_emu.model.SetupPage |
18 | import org.yuzu.yuzu_emu.model.StepState | 17 | import org.yuzu.yuzu_emu.model.StepState |
19 | import org.yuzu.yuzu_emu.utils.ViewUtils | 18 | import org.yuzu.yuzu_emu.utils.ViewUtils |
19 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
20 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | 20 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder |
21 | 21 | ||
22 | class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) : | 22 | class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) : |
@@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) : | |||
30 | AbstractViewHolder<SetupPage>(binding), SetupCallback { | 30 | AbstractViewHolder<SetupPage>(binding), SetupCallback { |
31 | override fun bind(model: SetupPage) { | 31 | override fun bind(model: SetupPage) { |
32 | if (model.stepCompleted.invoke() == StepState.COMPLETE) { | 32 | if (model.stepCompleted.invoke() == StepState.COMPLETE) { |
33 | binding.buttonAction.visibility = View.INVISIBLE | 33 | binding.buttonAction.setVisible(visible = false, gone = false) |
34 | binding.textConfirmation.visibility = View.VISIBLE | 34 | binding.textConfirmation.setVisible(true) |
35 | } | 35 | } |
36 | 36 | ||
37 | binding.icon.setImageDrawable( | 37 | binding.icon.setImageDrawable( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt new file mode 100755 index 000000000..15d776311 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/NativeInput.kt | |||
@@ -0,0 +1,416 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input | ||
5 | |||
6 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
7 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
8 | import org.yuzu.yuzu_emu.features.input.model.InputType | ||
9 | import org.yuzu.yuzu_emu.features.input.model.ButtonName | ||
10 | import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex | ||
11 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
12 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
13 | import android.view.InputDevice | ||
14 | |||
15 | object NativeInput { | ||
16 | /** | ||
17 | * Default controller id for each device | ||
18 | */ | ||
19 | const val Player1Device = 0 | ||
20 | const val Player2Device = 1 | ||
21 | const val Player3Device = 2 | ||
22 | const val Player4Device = 3 | ||
23 | const val Player5Device = 4 | ||
24 | const val Player6Device = 5 | ||
25 | const val Player7Device = 6 | ||
26 | const val Player8Device = 7 | ||
27 | const val ConsoleDevice = 8 | ||
28 | |||
29 | /** | ||
30 | * Button states | ||
31 | */ | ||
32 | object ButtonState { | ||
33 | const val RELEASED = 0 | ||
34 | const val PRESSED = 1 | ||
35 | } | ||
36 | |||
37 | /** | ||
38 | * Returns true if pro controller isn't available and handheld is. | ||
39 | * Intended to check where the input overlay should direct its inputs. | ||
40 | */ | ||
41 | external fun isHandheldOnly(): Boolean | ||
42 | |||
43 | /** | ||
44 | * Handles button press events for a gamepad. | ||
45 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. | ||
46 | * @param port Port determined by controller connection order. | ||
47 | * @param buttonId The Android Keycode corresponding to this event. | ||
48 | * @param action Mask identifying which action is happening (button pressed down, or button released). | ||
49 | */ | ||
50 | external fun onGamePadButtonEvent( | ||
51 | guid: String, | ||
52 | port: Int, | ||
53 | buttonId: Int, | ||
54 | action: Int | ||
55 | ) | ||
56 | |||
57 | /** | ||
58 | * Handles axis movement events. | ||
59 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. | ||
60 | * @param port Port determined by controller connection order. | ||
61 | * @param axis The axis ID. | ||
62 | * @param value Value along the given axis. | ||
63 | */ | ||
64 | external fun onGamePadAxisEvent(guid: String, port: Int, axis: Int, value: Float) | ||
65 | |||
66 | /** | ||
67 | * Handles motion events. | ||
68 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. | ||
69 | * @param port Port determined by controller connection order. | ||
70 | * @param deltaTimestamp The finger id corresponding to this event. | ||
71 | * @param xGyro The value of the x-axis for the gyroscope. | ||
72 | * @param yGyro The value of the y-axis for the gyroscope. | ||
73 | * @param zGyro The value of the z-axis for the gyroscope. | ||
74 | * @param xAccel The value of the x-axis for the accelerometer. | ||
75 | * @param yAccel The value of the y-axis for the accelerometer. | ||
76 | * @param zAccel The value of the z-axis for the accelerometer. | ||
77 | */ | ||
78 | external fun onGamePadMotionEvent( | ||
79 | guid: String, | ||
80 | port: Int, | ||
81 | deltaTimestamp: Long, | ||
82 | xGyro: Float, | ||
83 | yGyro: Float, | ||
84 | zGyro: Float, | ||
85 | xAccel: Float, | ||
86 | yAccel: Float, | ||
87 | zAccel: Float | ||
88 | ) | ||
89 | |||
90 | /** | ||
91 | * Signals and load a nfc tag | ||
92 | * @param data Byte array containing all the data from a nfc tag. | ||
93 | */ | ||
94 | external fun onReadNfcTag(data: ByteArray?) | ||
95 | |||
96 | /** | ||
97 | * Removes current loaded nfc tag. | ||
98 | */ | ||
99 | external fun onRemoveNfcTag() | ||
100 | |||
101 | /** | ||
102 | * Handles touch press events. | ||
103 | * @param fingerId The finger id corresponding to this event. | ||
104 | * @param xAxis The value of the x-axis on the touchscreen. | ||
105 | * @param yAxis The value of the y-axis on the touchscreen. | ||
106 | */ | ||
107 | external fun onTouchPressed(fingerId: Int, xAxis: Float, yAxis: Float) | ||
108 | |||
109 | /** | ||
110 | * Handles touch movement. | ||
111 | * @param fingerId The finger id corresponding to this event. | ||
112 | * @param xAxis The value of the x-axis on the touchscreen. | ||
113 | * @param yAxis The value of the y-axis on the touchscreen. | ||
114 | */ | ||
115 | external fun onTouchMoved(fingerId: Int, xAxis: Float, yAxis: Float) | ||
116 | |||
117 | /** | ||
118 | * Handles touch release events. | ||
119 | * @param fingerId The finger id corresponding to this event | ||
120 | */ | ||
121 | external fun onTouchReleased(fingerId: Int) | ||
122 | |||
123 | /** | ||
124 | * Sends a button input to the global virtual controllers. | ||
125 | * @param port Port determined by controller connection order. | ||
126 | * @param button The [NativeButton] corresponding to this event. | ||
127 | * @param action Mask identifying which action is happening (button pressed down, or button released). | ||
128 | */ | ||
129 | fun onOverlayButtonEvent(port: Int, button: NativeButton, action: Int) = | ||
130 | onOverlayButtonEventImpl(port, button.int, action) | ||
131 | |||
132 | private external fun onOverlayButtonEventImpl(port: Int, buttonId: Int, action: Int) | ||
133 | |||
134 | /** | ||
135 | * Sends a joystick input to the global virtual controllers. | ||
136 | * @param port Port determined by controller connection order. | ||
137 | * @param stick The [NativeAnalog] corresponding to this event. | ||
138 | * @param xAxis Value along the X axis. | ||
139 | * @param yAxis Value along the Y axis. | ||
140 | */ | ||
141 | fun onOverlayJoystickEvent(port: Int, stick: NativeAnalog, xAxis: Float, yAxis: Float) = | ||
142 | onOverlayJoystickEventImpl(port, stick.int, xAxis, yAxis) | ||
143 | |||
144 | private external fun onOverlayJoystickEventImpl( | ||
145 | port: Int, | ||
146 | stickId: Int, | ||
147 | xAxis: Float, | ||
148 | yAxis: Float | ||
149 | ) | ||
150 | |||
151 | /** | ||
152 | * Handles motion events for the global virtual controllers. | ||
153 | * @param port Port determined by controller connection order | ||
154 | * @param deltaTimestamp The finger id corresponding to this event. | ||
155 | * @param xGyro The value of the x-axis for the gyroscope. | ||
156 | * @param yGyro The value of the y-axis for the gyroscope. | ||
157 | * @param zGyro The value of the z-axis for the gyroscope. | ||
158 | * @param xAccel The value of the x-axis for the accelerometer. | ||
159 | * @param yAccel The value of the y-axis for the accelerometer. | ||
160 | * @param zAccel The value of the z-axis for the accelerometer. | ||
161 | */ | ||
162 | external fun onDeviceMotionEvent( | ||
163 | port: Int, | ||
164 | deltaTimestamp: Long, | ||
165 | xGyro: Float, | ||
166 | yGyro: Float, | ||
167 | zGyro: Float, | ||
168 | xAccel: Float, | ||
169 | yAccel: Float, | ||
170 | zAccel: Float | ||
171 | ) | ||
172 | |||
173 | /** | ||
174 | * Reloads all input devices from the currently loaded Settings::values.players into HID Core | ||
175 | */ | ||
176 | external fun reloadInputDevices() | ||
177 | |||
178 | /** | ||
179 | * Registers a controller to be used with mapping | ||
180 | * @param device An [InputDevice] or the input overlay wrapped with [YuzuInputDevice] | ||
181 | */ | ||
182 | external fun registerController(device: YuzuInputDevice) | ||
183 | |||
184 | /** | ||
185 | * Gets the names of input devices that have been registered with the input subsystem via [registerController] | ||
186 | */ | ||
187 | external fun getInputDevices(): Array<String> | ||
188 | |||
189 | /** | ||
190 | * Reads all input profiles from disk. Must be called before creating a profile picker. | ||
191 | */ | ||
192 | external fun loadInputProfiles() | ||
193 | |||
194 | /** | ||
195 | * Gets the names of each available input profile. | ||
196 | */ | ||
197 | external fun getInputProfileNames(): Array<String> | ||
198 | |||
199 | /** | ||
200 | * Checks if the user-provided name for an input profile is valid. | ||
201 | * @param name User-provided name for an input profile. | ||
202 | * @return Whether [name] is valid or not. | ||
203 | */ | ||
204 | external fun isProfileNameValid(name: String): Boolean | ||
205 | |||
206 | /** | ||
207 | * Creates a new input profile. | ||
208 | * @param name The new profile's name. | ||
209 | * @param playerIndex Index of the player that's currently being edited. Used to write the profile | ||
210 | * name to this player's config. | ||
211 | * @return Whether creating the profile was successful or not. | ||
212 | */ | ||
213 | external fun createProfile(name: String, playerIndex: Int): Boolean | ||
214 | |||
215 | /** | ||
216 | * Deletes an input profile. | ||
217 | * @param name Name of the profile to delete. | ||
218 | * @param playerIndex Index of the player that's currently being edited. Used to remove the profile | ||
219 | * name from this player's config if they have it loaded. | ||
220 | * @return Whether deleting this profile was successful or not. | ||
221 | */ | ||
222 | external fun deleteProfile(name: String, playerIndex: Int): Boolean | ||
223 | |||
224 | /** | ||
225 | * Loads an input profile. | ||
226 | * @param name Name of the input profile to load. | ||
227 | * @param playerIndex Index of the player that will have this profile loaded. | ||
228 | * @return Whether loading this profile was successful or not. | ||
229 | */ | ||
230 | external fun loadProfile(name: String, playerIndex: Int): Boolean | ||
231 | |||
232 | /** | ||
233 | * Saves an input profile. | ||
234 | * @param name Name of the profile to save. | ||
235 | * @param playerIndex Index of the player that's currently being edited. Used to write the profile | ||
236 | * name to this player's config. | ||
237 | * @return Whether saving the profile was successful or not. | ||
238 | */ | ||
239 | external fun saveProfile(name: String, playerIndex: Int): Boolean | ||
240 | |||
241 | /** | ||
242 | * Intended to be used immediately before a call to [NativeConfig.saveControlPlayerValues] | ||
243 | * Must be used while per-game config is loaded. | ||
244 | */ | ||
245 | external fun loadPerGameConfiguration( | ||
246 | playerIndex: Int, | ||
247 | selectedIndex: Int, | ||
248 | selectedProfileName: String | ||
249 | ) | ||
250 | |||
251 | /** | ||
252 | * Tells the input subsystem to start listening for inputs to map. | ||
253 | * @param type Type of input to map as shown by the int property in each [InputType]. | ||
254 | */ | ||
255 | external fun beginMapping(type: Int) | ||
256 | |||
257 | /** | ||
258 | * Gets an input's [ParamPackage] as a serialized string. Used for input verification before mapping. | ||
259 | * Must be run after [beginMapping] and before [stopMapping]. | ||
260 | */ | ||
261 | external fun getNextInput(): String | ||
262 | |||
263 | /** | ||
264 | * Tells the input subsystem to stop listening for inputs to map. | ||
265 | */ | ||
266 | external fun stopMapping() | ||
267 | |||
268 | /** | ||
269 | * Updates a controller's mappings with auto-mapping params. | ||
270 | * @param playerIndex Index of the player to auto-map. | ||
271 | * @param deviceParams [ParamPackage] representing the device to auto-map as received | ||
272 | * from [getInputDevices]. | ||
273 | * @param displayName Name of the device to auto-map as received from the "display" param in [deviceParams]. | ||
274 | * Intended to be a way to provide a default name for a controller if the "display" param is empty. | ||
275 | */ | ||
276 | fun updateMappingsWithDefault( | ||
277 | playerIndex: Int, | ||
278 | deviceParams: ParamPackage, | ||
279 | displayName: String | ||
280 | ) = updateMappingsWithDefaultImpl(playerIndex, deviceParams.serialize(), displayName) | ||
281 | |||
282 | private external fun updateMappingsWithDefaultImpl( | ||
283 | playerIndex: Int, | ||
284 | deviceParams: String, | ||
285 | displayName: String | ||
286 | ) | ||
287 | |||
288 | /** | ||
289 | * Gets the params for a specific button. | ||
290 | * @param playerIndex Index of the player to get params from. | ||
291 | * @param button The [NativeButton] to get params for. | ||
292 | * @return A [ParamPackage] representing a player's specific button. | ||
293 | */ | ||
294 | fun getButtonParam(playerIndex: Int, button: NativeButton): ParamPackage = | ||
295 | ParamPackage(getButtonParamImpl(playerIndex, button.int)) | ||
296 | |||
297 | private external fun getButtonParamImpl(playerIndex: Int, buttonId: Int): String | ||
298 | |||
299 | /** | ||
300 | * Sets the params for a specific button. | ||
301 | * @param playerIndex Index of the player to set params for. | ||
302 | * @param button The [NativeButton] to set params for. | ||
303 | * @param param A [ParamPackage] to set. | ||
304 | */ | ||
305 | fun setButtonParam(playerIndex: Int, button: NativeButton, param: ParamPackage) = | ||
306 | setButtonParamImpl(playerIndex, button.int, param.serialize()) | ||
307 | |||
308 | private external fun setButtonParamImpl(playerIndex: Int, buttonId: Int, param: String) | ||
309 | |||
310 | /** | ||
311 | * Gets the params for a specific stick. | ||
312 | * @param playerIndex Index of the player to get params from. | ||
313 | * @param stick The [NativeAnalog] to get params for. | ||
314 | * @return A [ParamPackage] representing a player's specific stick. | ||
315 | */ | ||
316 | fun getStickParam(playerIndex: Int, stick: NativeAnalog): ParamPackage = | ||
317 | ParamPackage(getStickParamImpl(playerIndex, stick.int)) | ||
318 | |||
319 | private external fun getStickParamImpl(playerIndex: Int, stickId: Int): String | ||
320 | |||
321 | /** | ||
322 | * Sets the params for a specific stick. | ||
323 | * @param playerIndex Index of the player to set params for. | ||
324 | * @param stick The [NativeAnalog] to set params for. | ||
325 | * @param param A [ParamPackage] to set. | ||
326 | */ | ||
327 | fun setStickParam(playerIndex: Int, stick: NativeAnalog, param: ParamPackage) = | ||
328 | setStickParamImpl(playerIndex, stick.int, param.serialize()) | ||
329 | |||
330 | private external fun setStickParamImpl(playerIndex: Int, stickId: Int, param: String) | ||
331 | |||
332 | /** | ||
333 | * Gets the int representation of a [ButtonName]. Tells you what to show as the mapped input for | ||
334 | * a button/analog/other. | ||
335 | * @param param A [ParamPackage] that represents a specific button's params. | ||
336 | * @return The [ButtonName] for [param]. | ||
337 | */ | ||
338 | fun getButtonName(param: ParamPackage): ButtonName = | ||
339 | ButtonName.from(getButtonNameImpl(param.serialize())) | ||
340 | |||
341 | private external fun getButtonNameImpl(param: String): Int | ||
342 | |||
343 | /** | ||
344 | * Gets each supported [NpadStyleIndex] for a given player. | ||
345 | * @param playerIndex Index of the player to get supported indexes for. | ||
346 | * @return List of each supported [NpadStyleIndex]. | ||
347 | */ | ||
348 | fun getSupportedStyleTags(playerIndex: Int): List<NpadStyleIndex> = | ||
349 | getSupportedStyleTagsImpl(playerIndex).map { NpadStyleIndex.from(it) } | ||
350 | |||
351 | private external fun getSupportedStyleTagsImpl(playerIndex: Int): IntArray | ||
352 | |||
353 | /** | ||
354 | * Gets the [NpadStyleIndex] for a given player. | ||
355 | * @param playerIndex Index of the player to get an [NpadStyleIndex] from. | ||
356 | * @return The [NpadStyleIndex] for a given player. | ||
357 | */ | ||
358 | fun getStyleIndex(playerIndex: Int): NpadStyleIndex = | ||
359 | NpadStyleIndex.from(getStyleIndexImpl(playerIndex)) | ||
360 | |||
361 | private external fun getStyleIndexImpl(playerIndex: Int): Int | ||
362 | |||
363 | /** | ||
364 | * Sets the [NpadStyleIndex] for a given player. | ||
365 | * @param playerIndex Index of the player to change. | ||
366 | * @param style The new style to set. | ||
367 | */ | ||
368 | fun setStyleIndex(playerIndex: Int, style: NpadStyleIndex) = | ||
369 | setStyleIndexImpl(playerIndex, style.int) | ||
370 | |||
371 | private external fun setStyleIndexImpl(playerIndex: Int, styleIndex: Int) | ||
372 | |||
373 | /** | ||
374 | * Checks if a device is a controller. | ||
375 | * @param params [ParamPackage] for an input device retrieved from [getInputDevices] | ||
376 | * @return Whether the device is a controller or not. | ||
377 | */ | ||
378 | fun isController(params: ParamPackage): Boolean = isControllerImpl(params.serialize()) | ||
379 | |||
380 | private external fun isControllerImpl(params: String): Boolean | ||
381 | |||
382 | /** | ||
383 | * Checks if a controller is connected | ||
384 | * @param playerIndex Index of the player to check. | ||
385 | * @return Whether the player is connected or not. | ||
386 | */ | ||
387 | external fun getIsConnected(playerIndex: Int): Boolean | ||
388 | |||
389 | /** | ||
390 | * Connects/disconnects a controller and ensures that connection order stays in-tact. | ||
391 | * @param playerIndex Index of the player to connect/disconnect. | ||
392 | * @param connected Whether to connect or disconnect this controller. | ||
393 | */ | ||
394 | fun connectControllers(playerIndex: Int, connected: Boolean = true) { | ||
395 | val connectedControllers = mutableListOf<Boolean>().apply { | ||
396 | if (connected) { | ||
397 | for (i in 0 until 8) { | ||
398 | add(i <= playerIndex) | ||
399 | } | ||
400 | } else { | ||
401 | for (i in 0 until 8) { | ||
402 | add(i < playerIndex) | ||
403 | } | ||
404 | } | ||
405 | } | ||
406 | connectControllersImpl(connectedControllers.toBooleanArray()) | ||
407 | } | ||
408 | |||
409 | private external fun connectControllersImpl(connected: BooleanArray) | ||
410 | |||
411 | /** | ||
412 | * Resets all of the button and analog mappings for a player. | ||
413 | * @param playerIndex Index of the player that will have its mappings reset. | ||
414 | */ | ||
415 | external fun resetControllerMappings(playerIndex: Int) | ||
416 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt new file mode 100755 index 000000000..15cc38c7f --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuInputDevice.kt | |||
@@ -0,0 +1,93 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input | ||
5 | |||
6 | import android.view.InputDevice | ||
7 | import androidx.annotation.Keep | ||
8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
9 | import org.yuzu.yuzu_emu.R | ||
10 | import org.yuzu.yuzu_emu.utils.InputHandler.getGUID | ||
11 | |||
12 | @Keep | ||
13 | interface YuzuInputDevice { | ||
14 | fun getName(): String | ||
15 | |||
16 | fun getGUID(): String | ||
17 | |||
18 | fun getPort(): Int | ||
19 | |||
20 | fun getSupportsVibration(): Boolean | ||
21 | |||
22 | fun vibrate(intensity: Float) | ||
23 | |||
24 | fun getAxes(): Array<Int> = arrayOf() | ||
25 | fun hasKeys(keys: IntArray): BooleanArray = BooleanArray(0) | ||
26 | } | ||
27 | |||
28 | class YuzuPhysicalDevice( | ||
29 | private val device: InputDevice, | ||
30 | private val port: Int, | ||
31 | useSystemVibrator: Boolean | ||
32 | ) : YuzuInputDevice { | ||
33 | private val vibrator = if (useSystemVibrator) { | ||
34 | YuzuVibrator.getSystemVibrator() | ||
35 | } else { | ||
36 | YuzuVibrator.getControllerVibrator(device) | ||
37 | } | ||
38 | |||
39 | override fun getName(): String { | ||
40 | return device.name | ||
41 | } | ||
42 | |||
43 | override fun getGUID(): String { | ||
44 | return device.getGUID() | ||
45 | } | ||
46 | |||
47 | override fun getPort(): Int { | ||
48 | return port | ||
49 | } | ||
50 | |||
51 | override fun getSupportsVibration(): Boolean { | ||
52 | return vibrator.supportsVibration() | ||
53 | } | ||
54 | |||
55 | override fun vibrate(intensity: Float) { | ||
56 | vibrator.vibrate(intensity) | ||
57 | } | ||
58 | |||
59 | override fun getAxes(): Array<Int> = device.motionRanges.map { it.axis }.toTypedArray() | ||
60 | override fun hasKeys(keys: IntArray): BooleanArray = device.hasKeys(*keys) | ||
61 | } | ||
62 | |||
63 | class YuzuInputOverlayDevice( | ||
64 | private val vibration: Boolean, | ||
65 | private val port: Int | ||
66 | ) : YuzuInputDevice { | ||
67 | private val vibrator = YuzuVibrator.getSystemVibrator() | ||
68 | |||
69 | override fun getName(): String { | ||
70 | return YuzuApplication.appContext.getString(R.string.input_overlay) | ||
71 | } | ||
72 | |||
73 | override fun getGUID(): String { | ||
74 | return "00000000000000000000000000000000" | ||
75 | } | ||
76 | |||
77 | override fun getPort(): Int { | ||
78 | return port | ||
79 | } | ||
80 | |||
81 | override fun getSupportsVibration(): Boolean { | ||
82 | if (vibration) { | ||
83 | return vibrator.supportsVibration() | ||
84 | } | ||
85 | return false | ||
86 | } | ||
87 | |||
88 | override fun vibrate(intensity: Float) { | ||
89 | if (vibration) { | ||
90 | vibrator.vibrate(intensity) | ||
91 | } | ||
92 | } | ||
93 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt new file mode 100755 index 000000000..aac49ecae --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/YuzuVibrator.kt | |||
@@ -0,0 +1,76 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input | ||
5 | |||
6 | import android.content.Context | ||
7 | import android.os.Build | ||
8 | import android.os.CombinedVibration | ||
9 | import android.os.VibrationEffect | ||
10 | import android.os.Vibrator | ||
11 | import android.os.VibratorManager | ||
12 | import android.view.InputDevice | ||
13 | import androidx.annotation.Keep | ||
14 | import androidx.annotation.RequiresApi | ||
15 | import org.yuzu.yuzu_emu.YuzuApplication | ||
16 | |||
17 | @Keep | ||
18 | @Suppress("DEPRECATION") | ||
19 | interface YuzuVibrator { | ||
20 | fun supportsVibration(): Boolean | ||
21 | |||
22 | fun vibrate(intensity: Float) | ||
23 | |||
24 | companion object { | ||
25 | fun getControllerVibrator(device: InputDevice): YuzuVibrator = | ||
26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
27 | YuzuVibratorManager(device.vibratorManager) | ||
28 | } else { | ||
29 | YuzuVibratorManagerCompat(device.vibrator) | ||
30 | } | ||
31 | |||
32 | fun getSystemVibrator(): YuzuVibrator = | ||
33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
34 | val vibratorManager = YuzuApplication.appContext | ||
35 | .getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager | ||
36 | YuzuVibratorManager(vibratorManager) | ||
37 | } else { | ||
38 | val vibrator = YuzuApplication.appContext | ||
39 | .getSystemService(Context.VIBRATOR_SERVICE) as Vibrator | ||
40 | YuzuVibratorManagerCompat(vibrator) | ||
41 | } | ||
42 | |||
43 | fun getVibrationEffect(intensity: Float): VibrationEffect? { | ||
44 | if (intensity > 0f) { | ||
45 | return VibrationEffect.createOneShot( | ||
46 | 50, | ||
47 | (255.0 * intensity).toInt().coerceIn(1, 255) | ||
48 | ) | ||
49 | } | ||
50 | return null | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | |||
55 | @RequiresApi(Build.VERSION_CODES.S) | ||
56 | class YuzuVibratorManager(private val vibratorManager: VibratorManager) : YuzuVibrator { | ||
57 | override fun supportsVibration(): Boolean { | ||
58 | return vibratorManager.vibratorIds.isNotEmpty() | ||
59 | } | ||
60 | |||
61 | override fun vibrate(intensity: Float) { | ||
62 | val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return | ||
63 | vibratorManager.vibrate(CombinedVibration.createParallel(vibration)) | ||
64 | } | ||
65 | } | ||
66 | |||
67 | class YuzuVibratorManagerCompat(private val vibrator: Vibrator) : YuzuVibrator { | ||
68 | override fun supportsVibration(): Boolean { | ||
69 | return vibrator.hasVibrator() | ||
70 | } | ||
71 | |||
72 | override fun vibrate(intensity: Float) { | ||
73 | val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return | ||
74 | vibrator.vibrate(vibration) | ||
75 | } | ||
76 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt new file mode 100755 index 000000000..0a5fab2ae --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/AnalogDirection.kt | |||
@@ -0,0 +1,11 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | enum class AnalogDirection(val int: Int, val param: String) { | ||
7 | Up(0, "up"), | ||
8 | Down(1, "down"), | ||
9 | Left(2, "left"), | ||
10 | Right(3, "right") | ||
11 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt new file mode 100755 index 000000000..b8846ecad --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/ButtonName.kt | |||
@@ -0,0 +1,19 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | // Loosely matches the enum in common/input.h | ||
7 | enum class ButtonName(val int: Int) { | ||
8 | Invalid(1), | ||
9 | |||
10 | // This will display the engine name instead of the button name | ||
11 | Engine(2), | ||
12 | |||
13 | // This will display the button by value instead of the button name | ||
14 | Value(3); | ||
15 | |||
16 | companion object { | ||
17 | fun from(int: Int): ButtonName = entries.firstOrNull { it.int == int } ?: Invalid | ||
18 | } | ||
19 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt new file mode 100755 index 000000000..f725231cb --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/InputType.kt | |||
@@ -0,0 +1,13 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | // Must match the corresponding enum in input_common/main.h | ||
7 | enum class InputType(val int: Int) { | ||
8 | None(0), | ||
9 | Button(1), | ||
10 | Stick(2), | ||
11 | Motion(3), | ||
12 | Touch(4) | ||
13 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt new file mode 100755 index 000000000..c3b7a785d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeAnalog.kt | |||
@@ -0,0 +1,14 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | // Must match enum in src/common/settings_input.h | ||
7 | enum class NativeAnalog(val int: Int) { | ||
8 | LStick(0), | ||
9 | RStick(1); | ||
10 | |||
11 | companion object { | ||
12 | fun from(int: Int): NativeAnalog = entries.firstOrNull { it.int == int } ?: LStick | ||
13 | } | ||
14 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt new file mode 100755 index 000000000..c5ccd7115 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeButton.kt | |||
@@ -0,0 +1,38 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | // Must match enum in src/common/settings_input.h | ||
7 | enum class NativeButton(val int: Int) { | ||
8 | A(0), | ||
9 | B(1), | ||
10 | X(2), | ||
11 | Y(3), | ||
12 | LStick(4), | ||
13 | RStick(5), | ||
14 | L(6), | ||
15 | R(7), | ||
16 | ZL(8), | ||
17 | ZR(9), | ||
18 | Plus(10), | ||
19 | Minus(11), | ||
20 | |||
21 | DLeft(12), | ||
22 | DUp(13), | ||
23 | DRight(14), | ||
24 | DDown(15), | ||
25 | |||
26 | SLLeft(16), | ||
27 | SRLeft(17), | ||
28 | |||
29 | Home(18), | ||
30 | Capture(19), | ||
31 | |||
32 | SLRight(20), | ||
33 | SRRight(21); | ||
34 | |||
35 | companion object { | ||
36 | fun from(int: Int): NativeButton = entries.firstOrNull { it.int == int } ?: A | ||
37 | } | ||
38 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt new file mode 100755 index 000000000..625f352b4 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NativeTrigger.kt | |||
@@ -0,0 +1,10 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | // Must match enum in src/common/settings_input.h | ||
7 | enum class NativeTrigger(val int: Int) { | ||
8 | LTrigger(0), | ||
9 | RTrigger(1) | ||
10 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt new file mode 100755 index 000000000..e2a3d7aff --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/NpadStyleIndex.kt | |||
@@ -0,0 +1,30 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.R | ||
8 | |||
9 | // Must match enum in src/core/hid/hid_types.h | ||
10 | enum class NpadStyleIndex(val int: Int, @StringRes val nameId: Int = 0) { | ||
11 | None(0), | ||
12 | Fullkey(3, R.string.pro_controller), | ||
13 | Handheld(4, R.string.handheld), | ||
14 | HandheldNES(4), | ||
15 | JoyconDual(5, R.string.dual_joycons), | ||
16 | JoyconLeft(6, R.string.left_joycon), | ||
17 | JoyconRight(7, R.string.right_joycon), | ||
18 | GameCube(8, R.string.gamecube_controller), | ||
19 | Pokeball(9), | ||
20 | NES(10), | ||
21 | SNES(12), | ||
22 | N64(13), | ||
23 | SegaGenesis(14), | ||
24 | SystemExt(32), | ||
25 | System(33); | ||
26 | |||
27 | companion object { | ||
28 | fun from(int: Int): NpadStyleIndex = entries.firstOrNull { it.int == int } ?: None | ||
29 | } | ||
30 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt new file mode 100755 index 000000000..d35de80c4 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/input/model/PlayerInput.kt | |||
@@ -0,0 +1,83 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.input.model | ||
5 | |||
6 | import androidx.annotation.Keep | ||
7 | |||
8 | @Keep | ||
9 | data class PlayerInput( | ||
10 | var connected: Boolean, | ||
11 | var buttons: Array<String>, | ||
12 | var analogs: Array<String>, | ||
13 | var motions: Array<String>, | ||
14 | |||
15 | var vibrationEnabled: Boolean, | ||
16 | var vibrationStrength: Int, | ||
17 | |||
18 | var bodyColorLeft: Long, | ||
19 | var bodyColorRight: Long, | ||
20 | var buttonColorLeft: Long, | ||
21 | var buttonColorRight: Long, | ||
22 | var profileName: String, | ||
23 | |||
24 | var useSystemVibrator: Boolean | ||
25 | ) { | ||
26 | // It's recommended to use the generated equals() and hashCode() methods | ||
27 | // when using arrays in a data class | ||
28 | override fun equals(other: Any?): Boolean { | ||
29 | if (this === other) return true | ||
30 | if (javaClass != other?.javaClass) return false | ||
31 | |||
32 | other as PlayerInput | ||
33 | |||
34 | if (connected != other.connected) return false | ||
35 | if (!buttons.contentEquals(other.buttons)) return false | ||
36 | if (!analogs.contentEquals(other.analogs)) return false | ||
37 | if (!motions.contentEquals(other.motions)) return false | ||
38 | if (vibrationEnabled != other.vibrationEnabled) return false | ||
39 | if (vibrationStrength != other.vibrationStrength) return false | ||
40 | if (bodyColorLeft != other.bodyColorLeft) return false | ||
41 | if (bodyColorRight != other.bodyColorRight) return false | ||
42 | if (buttonColorLeft != other.buttonColorLeft) return false | ||
43 | if (buttonColorRight != other.buttonColorRight) return false | ||
44 | if (profileName != other.profileName) return false | ||
45 | return useSystemVibrator == other.useSystemVibrator | ||
46 | } | ||
47 | |||
48 | override fun hashCode(): Int { | ||
49 | var result = connected.hashCode() | ||
50 | result = 31 * result + buttons.contentHashCode() | ||
51 | result = 31 * result + analogs.contentHashCode() | ||
52 | result = 31 * result + motions.contentHashCode() | ||
53 | result = 31 * result + vibrationEnabled.hashCode() | ||
54 | result = 31 * result + vibrationStrength | ||
55 | result = 31 * result + bodyColorLeft.hashCode() | ||
56 | result = 31 * result + bodyColorRight.hashCode() | ||
57 | result = 31 * result + buttonColorLeft.hashCode() | ||
58 | result = 31 * result + buttonColorRight.hashCode() | ||
59 | result = 31 * result + profileName.hashCode() | ||
60 | result = 31 * result + useSystemVibrator.hashCode() | ||
61 | return result | ||
62 | } | ||
63 | |||
64 | fun hasMapping(): Boolean { | ||
65 | var hasMapping = false | ||
66 | buttons.forEach { | ||
67 | if (it != "[empty]") { | ||
68 | hasMapping = true | ||
69 | } | ||
70 | } | ||
71 | analogs.forEach { | ||
72 | if (it != "[empty]") { | ||
73 | hasMapping = true | ||
74 | } | ||
75 | } | ||
76 | motions.forEach { | ||
77 | if (it != "[empty]") { | ||
78 | hasMapping = true | ||
79 | } | ||
80 | } | ||
81 | return hasMapping | ||
82 | } | ||
83 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 862c6c483..4f6b93bd2 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt | |||
@@ -4,17 +4,30 @@ | |||
4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
5 | 5 | ||
6 | import org.yuzu.yuzu_emu.R | 6 | import org.yuzu.yuzu_emu.R |
7 | import org.yuzu.yuzu_emu.YuzuApplication | ||
7 | 8 | ||
8 | object Settings { | 9 | object Settings { |
9 | enum class MenuTag(val titleId: Int) { | 10 | enum class MenuTag(val titleId: Int = 0) { |
10 | SECTION_ROOT(R.string.advanced_settings), | 11 | SECTION_ROOT(R.string.advanced_settings), |
11 | SECTION_SYSTEM(R.string.preferences_system), | 12 | SECTION_SYSTEM(R.string.preferences_system), |
12 | SECTION_RENDERER(R.string.preferences_graphics), | 13 | SECTION_RENDERER(R.string.preferences_graphics), |
13 | SECTION_AUDIO(R.string.preferences_audio), | 14 | SECTION_AUDIO(R.string.preferences_audio), |
15 | SECTION_INPUT(R.string.preferences_controls), | ||
16 | SECTION_INPUT_PLAYER_ONE, | ||
17 | SECTION_INPUT_PLAYER_TWO, | ||
18 | SECTION_INPUT_PLAYER_THREE, | ||
19 | SECTION_INPUT_PLAYER_FOUR, | ||
20 | SECTION_INPUT_PLAYER_FIVE, | ||
21 | SECTION_INPUT_PLAYER_SIX, | ||
22 | SECTION_INPUT_PLAYER_SEVEN, | ||
23 | SECTION_INPUT_PLAYER_EIGHT, | ||
14 | SECTION_THEME(R.string.preferences_theme), | 24 | SECTION_THEME(R.string.preferences_theme), |
15 | SECTION_DEBUG(R.string.preferences_debug); | 25 | SECTION_DEBUG(R.string.preferences_debug); |
16 | } | 26 | } |
17 | 27 | ||
28 | fun getPlayerString(player: Int): String = | ||
29 | YuzuApplication.appContext.getString(R.string.preferences_player, player) | ||
30 | |||
18 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" | 31 | const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" |
19 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" | 32 | const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" |
20 | 33 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt new file mode 100755 index 000000000..a2996725e --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/AnalogInputSetting.kt | |||
@@ -0,0 +1,31 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
8 | import org.yuzu.yuzu_emu.features.input.model.AnalogDirection | ||
9 | import org.yuzu.yuzu_emu.features.input.model.InputType | ||
10 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
11 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
12 | |||
13 | class AnalogInputSetting( | ||
14 | override val playerIndex: Int, | ||
15 | val nativeAnalog: NativeAnalog, | ||
16 | val analogDirection: AnalogDirection, | ||
17 | @StringRes titleId: Int = 0, | ||
18 | titleString: String = "" | ||
19 | ) : InputSetting(titleId, titleString) { | ||
20 | override val type = TYPE_INPUT | ||
21 | override val inputType = InputType.Stick | ||
22 | |||
23 | override fun getSelectedValue(): String { | ||
24 | val params = NativeInput.getStickParam(playerIndex, nativeAnalog) | ||
25 | val analog = analogToText(params, analogDirection.param) | ||
26 | return getDisplayString(params, analog) | ||
27 | } | ||
28 | |||
29 | override fun setSelectedValue(param: ParamPackage) = | ||
30 | NativeInput.setStickParam(playerIndex, nativeAnalog, param) | ||
31 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt new file mode 100755 index 000000000..786d09a7a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ButtonInputSetting.kt | |||
@@ -0,0 +1,29 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
8 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
9 | import org.yuzu.yuzu_emu.features.input.model.InputType | ||
10 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
11 | |||
12 | class ButtonInputSetting( | ||
13 | override val playerIndex: Int, | ||
14 | val nativeButton: NativeButton, | ||
15 | @StringRes titleId: Int = 0, | ||
16 | titleString: String = "" | ||
17 | ) : InputSetting(titleId, titleString) { | ||
18 | override val type = TYPE_INPUT | ||
19 | override val inputType = InputType.Button | ||
20 | |||
21 | override fun getSelectedValue(): String { | ||
22 | val params = NativeInput.getButtonParam(playerIndex, nativeButton) | ||
23 | val button = buttonToText(params) | ||
24 | return getDisplayString(params, button) | ||
25 | } | ||
26 | |||
27 | override fun setSelectedValue(param: ParamPackage) = | ||
28 | NativeInput.setButtonParam(playerIndex, nativeButton, param) | ||
29 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index 1d81f5f2b..58febff1d 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt | |||
@@ -3,13 +3,16 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting |
7 | 8 | ||
8 | class DateTimeSetting( | 9 | class DateTimeSetting( |
9 | private val longSetting: AbstractLongSetting, | 10 | private val longSetting: AbstractLongSetting, |
10 | titleId: Int, | 11 | @StringRes titleId: Int = 0, |
11 | descriptionId: Int | 12 | titleString: String = "", |
12 | ) : SettingsItem(longSetting, titleId, descriptionId) { | 13 | @StringRes descriptionId: Int = 0, |
14 | descriptionString: String = "" | ||
15 | ) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) { | ||
13 | override val type = TYPE_DATETIME_SETTING | 16 | override val type = TYPE_DATETIME_SETTING |
14 | 17 | ||
15 | fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal) | 18 | fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt index d31ce1c31..8a6a51d5c 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt | |||
@@ -3,8 +3,11 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
7 | |||
6 | class HeaderSetting( | 8 | class HeaderSetting( |
7 | titleId: Int | 9 | @StringRes titleId: Int = 0, |
8 | ) : SettingsItem(emptySetting, titleId, 0) { | 10 | titleString: String = "" |
11 | ) : SettingsItem(emptySetting, titleId, titleString, 0, "") { | ||
9 | override val type = TYPE_HEADER | 12 | override val type = TYPE_HEADER |
10 | } | 13 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt new file mode 100755 index 000000000..c46de08c5 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputProfileSetting.kt | |||
@@ -0,0 +1,32 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import org.yuzu.yuzu_emu.R | ||
7 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
8 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
9 | |||
10 | class InputProfileSetting(private val playerIndex: Int) : | ||
11 | SettingsItem(emptySetting, R.string.profile, "", 0, "") { | ||
12 | override val type = TYPE_INPUT_PROFILE | ||
13 | |||
14 | fun getCurrentProfile(): String = | ||
15 | NativeConfig.getInputSettings(true)[playerIndex].profileName | ||
16 | |||
17 | fun getProfileNames(): Array<String> = NativeInput.getInputProfileNames() | ||
18 | |||
19 | fun isProfileNameValid(name: String): Boolean = NativeInput.isProfileNameValid(name) | ||
20 | |||
21 | fun createProfile(name: String): Boolean = NativeInput.createProfile(name, playerIndex) | ||
22 | |||
23 | fun deleteProfile(name: String): Boolean = NativeInput.deleteProfile(name, playerIndex) | ||
24 | |||
25 | fun loadProfile(name: String): Boolean { | ||
26 | val result = NativeInput.loadProfile(name, playerIndex) | ||
27 | NativeInput.reloadInputDevices() | ||
28 | return result | ||
29 | } | ||
30 | |||
31 | fun saveProfile(name: String): Boolean = NativeInput.saveProfile(name, playerIndex) | ||
32 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt new file mode 100755 index 000000000..2d118bff3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/InputSetting.kt | |||
@@ -0,0 +1,134 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.R | ||
8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
9 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
10 | import org.yuzu.yuzu_emu.features.input.model.ButtonName | ||
11 | import org.yuzu.yuzu_emu.features.input.model.InputType | ||
12 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
13 | |||
14 | sealed class InputSetting( | ||
15 | @StringRes titleId: Int, | ||
16 | titleString: String | ||
17 | ) : SettingsItem(emptySetting, titleId, titleString, 0, "") { | ||
18 | override val type = TYPE_INPUT | ||
19 | abstract val inputType: InputType | ||
20 | abstract val playerIndex: Int | ||
21 | |||
22 | protected val context get() = YuzuApplication.appContext | ||
23 | |||
24 | abstract fun getSelectedValue(): String | ||
25 | |||
26 | abstract fun setSelectedValue(param: ParamPackage) | ||
27 | |||
28 | protected fun getDisplayString(params: ParamPackage, control: String): String { | ||
29 | val deviceName = params.get("display", "") | ||
30 | deviceName.ifEmpty { | ||
31 | return context.getString(R.string.not_set) | ||
32 | } | ||
33 | return "$deviceName: $control" | ||
34 | } | ||
35 | |||
36 | private fun getDirectionName(direction: String): String = | ||
37 | when (direction) { | ||
38 | "up" -> context.getString(R.string.up) | ||
39 | "down" -> context.getString(R.string.down) | ||
40 | "left" -> context.getString(R.string.left) | ||
41 | "right" -> context.getString(R.string.right) | ||
42 | else -> direction | ||
43 | } | ||
44 | |||
45 | protected fun buttonToText(param: ParamPackage): String { | ||
46 | if (!param.has("engine")) { | ||
47 | return context.getString(R.string.not_set) | ||
48 | } | ||
49 | |||
50 | val toggle = if (param.get("toggle", false)) "~" else "" | ||
51 | val inverted = if (param.get("inverted", false)) "!" else "" | ||
52 | val invert = if (param.get("invert", "+") == "-") "-" else "" | ||
53 | val turbo = if (param.get("turbo", false)) "$" else "" | ||
54 | val commonButtonName = NativeInput.getButtonName(param) | ||
55 | |||
56 | if (commonButtonName == ButtonName.Invalid) { | ||
57 | return context.getString(R.string.invalid) | ||
58 | } | ||
59 | |||
60 | if (commonButtonName == ButtonName.Engine) { | ||
61 | return param.get("engine", "") | ||
62 | } | ||
63 | |||
64 | if (commonButtonName == ButtonName.Value) { | ||
65 | if (param.has("hat")) { | ||
66 | val hat = getDirectionName(param.get("direction", "")) | ||
67 | return context.getString(R.string.qualified_hat, turbo, toggle, inverted, hat) | ||
68 | } | ||
69 | if (param.has("axis")) { | ||
70 | val axis = param.get("axis", "") | ||
71 | return context.getString( | ||
72 | R.string.qualified_button_stick_axis, | ||
73 | toggle, | ||
74 | inverted, | ||
75 | invert, | ||
76 | axis | ||
77 | ) | ||
78 | } | ||
79 | if (param.has("button")) { | ||
80 | val button = param.get("button", "") | ||
81 | return context.getString(R.string.qualified_button, turbo, toggle, inverted, button) | ||
82 | } | ||
83 | } | ||
84 | |||
85 | return context.getString(R.string.unknown) | ||
86 | } | ||
87 | |||
88 | protected fun analogToText(param: ParamPackage, direction: String): String { | ||
89 | if (!param.has("engine")) { | ||
90 | return context.getString(R.string.not_set) | ||
91 | } | ||
92 | |||
93 | if (param.get("engine", "") == "analog_from_button") { | ||
94 | return buttonToText(ParamPackage(param.get(direction, ""))) | ||
95 | } | ||
96 | |||
97 | if (!param.has("axis_x") || !param.has("axis_y")) { | ||
98 | return context.getString(R.string.unknown) | ||
99 | } | ||
100 | |||
101 | val xAxis = param.get("axis_x", "") | ||
102 | val yAxis = param.get("axis_y", "") | ||
103 | val xInvert = param.get("invert_x", "+") == "-" | ||
104 | val yInvert = param.get("invert_y", "+") == "-" | ||
105 | |||
106 | if (direction == "modifier") { | ||
107 | return context.getString(R.string.unused) | ||
108 | } | ||
109 | |||
110 | when (direction) { | ||
111 | "up" -> { | ||
112 | val yInvertString = if (yInvert) "+" else "-" | ||
113 | return context.getString(R.string.qualified_axis, yAxis, yInvertString) | ||
114 | } | ||
115 | |||
116 | "down" -> { | ||
117 | val yInvertString = if (yInvert) "-" else "+" | ||
118 | return context.getString(R.string.qualified_axis, yAxis, yInvertString) | ||
119 | } | ||
120 | |||
121 | "left" -> { | ||
122 | val xInvertString = if (xInvert) "+" else "-" | ||
123 | return context.getString(R.string.qualified_axis, xAxis, xInvertString) | ||
124 | } | ||
125 | |||
126 | "right" -> { | ||
127 | val xInvertString = if (xInvert) "-" else "+" | ||
128 | return context.getString(R.string.qualified_axis, xAxis, xInvertString) | ||
129 | } | ||
130 | } | ||
131 | |||
132 | return context.getString(R.string.unknown) | ||
133 | } | ||
134 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt new file mode 100755 index 000000000..e024c793a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/IntSingleChoiceSetting.kt | |||
@@ -0,0 +1,38 @@ | |||
1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | ||
8 | |||
9 | class IntSingleChoiceSetting( | ||
10 | private val intSetting: AbstractIntSetting, | ||
11 | @StringRes titleId: Int = 0, | ||
12 | titleString: String = "", | ||
13 | @StringRes descriptionId: Int = 0, | ||
14 | descriptionString: String = "", | ||
15 | val choices: Array<String>, | ||
16 | val values: Array<Int> | ||
17 | ) : SettingsItem(intSetting, titleId, titleString, descriptionId, descriptionString) { | ||
18 | override val type = TYPE_INT_SINGLE_CHOICE | ||
19 | |||
20 | fun getValueAt(index: Int): Int = | ||
21 | if (values.indices.contains(index)) values[index] else -1 | ||
22 | |||
23 | fun getChoiceAt(index: Int): String = | ||
24 | if (choices.indices.contains(index)) choices[index] else "" | ||
25 | |||
26 | fun getSelectedValue(needsGlobal: Boolean = false) = intSetting.getInt(needsGlobal) | ||
27 | fun setSelectedValue(value: Int) = intSetting.setInt(value) | ||
28 | |||
29 | val selectedValueIndex: Int | ||
30 | get() { | ||
31 | for (i in values.indices) { | ||
32 | if (values[i] == getSelectedValue()) { | ||
33 | return i | ||
34 | } | ||
35 | } | ||
36 | return -1 | ||
37 | } | ||
38 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt new file mode 100755 index 000000000..a1db3cc87 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/ModifierInputSetting.kt | |||
@@ -0,0 +1,31 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | ||
5 | |||
6 | import androidx.annotation.StringRes | ||
7 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
8 | import org.yuzu.yuzu_emu.features.input.model.InputType | ||
9 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
10 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
11 | |||
12 | class ModifierInputSetting( | ||
13 | override val playerIndex: Int, | ||
14 | val nativeAnalog: NativeAnalog, | ||
15 | @StringRes titleId: Int = 0, | ||
16 | titleString: String = "" | ||
17 | ) : InputSetting(titleId, titleString) { | ||
18 | override val inputType = InputType.Button | ||
19 | |||
20 | override fun getSelectedValue(): String { | ||
21 | val analogParam = NativeInput.getStickParam(playerIndex, nativeAnalog) | ||
22 | val modifierParam = ParamPackage(analogParam.get("modifier", "")) | ||
23 | return buttonToText(modifierParam) | ||
24 | } | ||
25 | |||
26 | override fun setSelectedValue(param: ParamPackage) { | ||
27 | val newParam = NativeInput.getStickParam(playerIndex, nativeAnalog) | ||
28 | newParam.set("modifier", param.serialize()) | ||
29 | NativeInput.setStickParam(playerIndex, nativeAnalog, newParam) | ||
30 | } | ||
31 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt index 425160024..06f607424 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt | |||
@@ -4,13 +4,16 @@ | |||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.DrawableRes | 6 | import androidx.annotation.DrawableRes |
7 | import androidx.annotation.StringRes | ||
7 | 8 | ||
8 | class RunnableSetting( | 9 | class RunnableSetting( |
9 | titleId: Int, | 10 | @StringRes titleId: Int = 0, |
10 | descriptionId: Int, | 11 | titleString: String = "", |
11 | val isRuntimeRunnable: Boolean, | 12 | @StringRes descriptionId: Int = 0, |
13 | descriptionString: String = "", | ||
14 | val isRunnable: Boolean, | ||
12 | @DrawableRes val iconId: Int = 0, | 15 | @DrawableRes val iconId: Int = 0, |
13 | val runnable: () -> Unit | 16 | val runnable: () -> Unit |
14 | ) : SettingsItem(emptySetting, titleId, descriptionId) { | 17 | ) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) { |
15 | override val type = TYPE_RUNNABLE | 18 | override val type = TYPE_RUNNABLE |
16 | } | 19 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 21ca97bc1..8f724835e 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt | |||
@@ -3,8 +3,12 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.NativeLibrary | 7 | import org.yuzu.yuzu_emu.NativeLibrary |
7 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
9 | import org.yuzu.yuzu_emu.YuzuApplication | ||
10 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
11 | import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex | ||
8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
10 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 14 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
@@ -23,13 +27,34 @@ import org.yuzu.yuzu_emu.utils.NativeConfig | |||
23 | */ | 27 | */ |
24 | abstract class SettingsItem( | 28 | abstract class SettingsItem( |
25 | val setting: AbstractSetting, | 29 | val setting: AbstractSetting, |
26 | val nameId: Int, | 30 | @StringRes val titleId: Int, |
27 | val descriptionId: Int | 31 | val titleString: String, |
32 | @StringRes val descriptionId: Int, | ||
33 | val descriptionString: String | ||
28 | ) { | 34 | ) { |
29 | abstract val type: Int | 35 | abstract val type: Int |
30 | 36 | ||
37 | val title: String by lazy { | ||
38 | if (titleId != 0) { | ||
39 | return@lazy YuzuApplication.appContext.getString(titleId) | ||
40 | } | ||
41 | return@lazy titleString | ||
42 | } | ||
43 | |||
44 | val description: String by lazy { | ||
45 | if (descriptionId != 0) { | ||
46 | return@lazy YuzuApplication.appContext.getString(descriptionId) | ||
47 | } | ||
48 | return@lazy descriptionString | ||
49 | } | ||
50 | |||
31 | val isEditable: Boolean | 51 | val isEditable: Boolean |
32 | get() { | 52 | get() { |
53 | // Can't change docked mode toggle when using handheld mode | ||
54 | if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) { | ||
55 | return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld | ||
56 | } | ||
57 | |||
33 | // Can't edit settings that aren't saveable in per-game config even if they are switchable | 58 | // Can't edit settings that aren't saveable in per-game config even if they are switchable |
34 | if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) { | 59 | if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) { |
35 | return false | 60 | return false |
@@ -59,6 +84,9 @@ abstract class SettingsItem( | |||
59 | const val TYPE_STRING_SINGLE_CHOICE = 5 | 84 | const val TYPE_STRING_SINGLE_CHOICE = 5 |
60 | const val TYPE_DATETIME_SETTING = 6 | 85 | const val TYPE_DATETIME_SETTING = 6 |
61 | const val TYPE_RUNNABLE = 7 | 86 | const val TYPE_RUNNABLE = 7 |
87 | const val TYPE_INPUT = 8 | ||
88 | const val TYPE_INT_SINGLE_CHOICE = 9 | ||
89 | const val TYPE_INPUT_PROFILE = 10 | ||
62 | 90 | ||
63 | const val FASTMEM_COMBINED = "fastmem_combined" | 91 | const val FASTMEM_COMBINED = "fastmem_combined" |
64 | 92 | ||
@@ -80,237 +108,242 @@ abstract class SettingsItem( | |||
80 | put( | 108 | put( |
81 | SwitchSetting( | 109 | SwitchSetting( |
82 | BooleanSetting.RENDERER_USE_SPEED_LIMIT, | 110 | BooleanSetting.RENDERER_USE_SPEED_LIMIT, |
83 | R.string.frame_limit_enable, | 111 | titleId = R.string.frame_limit_enable, |
84 | R.string.frame_limit_enable_description | 112 | descriptionId = R.string.frame_limit_enable_description |
85 | ) | 113 | ) |
86 | ) | 114 | ) |
87 | put( | 115 | put( |
88 | SliderSetting( | 116 | SliderSetting( |
89 | ShortSetting.RENDERER_SPEED_LIMIT, | 117 | ShortSetting.RENDERER_SPEED_LIMIT, |
90 | R.string.frame_limit_slider, | 118 | titleId = R.string.frame_limit_slider, |
91 | R.string.frame_limit_slider_description, | 119 | descriptionId = R.string.frame_limit_slider_description, |
92 | 1, | 120 | min = 1, |
93 | 400, | 121 | max = 400, |
94 | "%" | 122 | units = "%" |
95 | ) | 123 | ) |
96 | ) | 124 | ) |
97 | put( | 125 | put( |
98 | SingleChoiceSetting( | 126 | SingleChoiceSetting( |
99 | IntSetting.CPU_BACKEND, | 127 | IntSetting.CPU_BACKEND, |
100 | R.string.cpu_backend, | 128 | titleId = R.string.cpu_backend, |
101 | 0, | 129 | choicesId = R.array.cpuBackendArm64Names, |
102 | R.array.cpuBackendArm64Names, | 130 | valuesId = R.array.cpuBackendArm64Values |
103 | R.array.cpuBackendArm64Values | ||
104 | ) | 131 | ) |
105 | ) | 132 | ) |
106 | put( | 133 | put( |
107 | SingleChoiceSetting( | 134 | SingleChoiceSetting( |
108 | IntSetting.CPU_ACCURACY, | 135 | IntSetting.CPU_ACCURACY, |
109 | R.string.cpu_accuracy, | 136 | titleId = R.string.cpu_accuracy, |
110 | 0, | 137 | choicesId = R.array.cpuAccuracyNames, |
111 | R.array.cpuAccuracyNames, | 138 | valuesId = R.array.cpuAccuracyValues |
112 | R.array.cpuAccuracyValues | ||
113 | ) | 139 | ) |
114 | ) | 140 | ) |
115 | put( | 141 | put( |
116 | SwitchSetting( | 142 | SwitchSetting( |
117 | BooleanSetting.PICTURE_IN_PICTURE, | 143 | BooleanSetting.PICTURE_IN_PICTURE, |
118 | R.string.picture_in_picture, | 144 | titleId = R.string.picture_in_picture, |
119 | R.string.picture_in_picture_description | 145 | descriptionId = R.string.picture_in_picture_description |
120 | ) | 146 | ) |
121 | ) | 147 | ) |
148 | |||
149 | val dockedModeSetting = object : AbstractBooleanSetting { | ||
150 | override val key = BooleanSetting.USE_DOCKED_MODE.key | ||
151 | |||
152 | override fun getBoolean(needsGlobal: Boolean): Boolean { | ||
153 | if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) { | ||
154 | return false | ||
155 | } | ||
156 | return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal) | ||
157 | } | ||
158 | |||
159 | override fun setBoolean(value: Boolean) = | ||
160 | BooleanSetting.USE_DOCKED_MODE.setBoolean(value) | ||
161 | |||
162 | override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue | ||
163 | |||
164 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
165 | BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal) | ||
166 | |||
167 | override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset() | ||
168 | } | ||
122 | put( | 169 | put( |
123 | SwitchSetting( | 170 | SwitchSetting( |
124 | BooleanSetting.USE_DOCKED_MODE, | 171 | dockedModeSetting, |
125 | R.string.use_docked_mode, | 172 | titleId = R.string.use_docked_mode, |
126 | R.string.use_docked_mode_description | 173 | descriptionId = R.string.use_docked_mode_description |
127 | ) | 174 | ) |
128 | ) | 175 | ) |
176 | |||
129 | put( | 177 | put( |
130 | SingleChoiceSetting( | 178 | SingleChoiceSetting( |
131 | IntSetting.REGION_INDEX, | 179 | IntSetting.REGION_INDEX, |
132 | R.string.emulated_region, | 180 | titleId = R.string.emulated_region, |
133 | 0, | 181 | choicesId = R.array.regionNames, |
134 | R.array.regionNames, | 182 | valuesId = R.array.regionValues |
135 | R.array.regionValues | ||
136 | ) | 183 | ) |
137 | ) | 184 | ) |
138 | put( | 185 | put( |
139 | SingleChoiceSetting( | 186 | SingleChoiceSetting( |
140 | IntSetting.LANGUAGE_INDEX, | 187 | IntSetting.LANGUAGE_INDEX, |
141 | R.string.emulated_language, | 188 | titleId = R.string.emulated_language, |
142 | 0, | 189 | choicesId = R.array.languageNames, |
143 | R.array.languageNames, | 190 | valuesId = R.array.languageValues |
144 | R.array.languageValues | ||
145 | ) | 191 | ) |
146 | ) | 192 | ) |
147 | put( | 193 | put( |
148 | SwitchSetting( | 194 | SwitchSetting( |
149 | BooleanSetting.USE_CUSTOM_RTC, | 195 | BooleanSetting.USE_CUSTOM_RTC, |
150 | R.string.use_custom_rtc, | 196 | titleId = R.string.use_custom_rtc, |
151 | R.string.use_custom_rtc_description | 197 | descriptionId = R.string.use_custom_rtc_description |
152 | ) | 198 | ) |
153 | ) | 199 | ) |
154 | put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0)) | 200 | put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc)) |
155 | put( | 201 | put( |
156 | SingleChoiceSetting( | 202 | SingleChoiceSetting( |
157 | IntSetting.RENDERER_ACCURACY, | 203 | IntSetting.RENDERER_ACCURACY, |
158 | R.string.renderer_accuracy, | 204 | titleId = R.string.renderer_accuracy, |
159 | 0, | 205 | choicesId = R.array.rendererAccuracyNames, |
160 | R.array.rendererAccuracyNames, | 206 | valuesId = R.array.rendererAccuracyValues |
161 | R.array.rendererAccuracyValues | ||
162 | ) | 207 | ) |
163 | ) | 208 | ) |
164 | put( | 209 | put( |
165 | SingleChoiceSetting( | 210 | SingleChoiceSetting( |
166 | IntSetting.RENDERER_RESOLUTION, | 211 | IntSetting.RENDERER_RESOLUTION, |
167 | R.string.renderer_resolution, | 212 | titleId = R.string.renderer_resolution, |
168 | 0, | 213 | choicesId = R.array.rendererResolutionNames, |
169 | R.array.rendererResolutionNames, | 214 | valuesId = R.array.rendererResolutionValues |
170 | R.array.rendererResolutionValues | ||
171 | ) | 215 | ) |
172 | ) | 216 | ) |
173 | put( | 217 | put( |
174 | SingleChoiceSetting( | 218 | SingleChoiceSetting( |
175 | IntSetting.RENDERER_VSYNC, | 219 | IntSetting.RENDERER_VSYNC, |
176 | R.string.renderer_vsync, | 220 | titleId = R.string.renderer_vsync, |
177 | 0, | 221 | choicesId = R.array.rendererVSyncNames, |
178 | R.array.rendererVSyncNames, | 222 | valuesId = R.array.rendererVSyncValues |
179 | R.array.rendererVSyncValues | ||
180 | ) | 223 | ) |
181 | ) | 224 | ) |
182 | put( | 225 | put( |
183 | SingleChoiceSetting( | 226 | SingleChoiceSetting( |
184 | IntSetting.RENDERER_SCALING_FILTER, | 227 | IntSetting.RENDERER_SCALING_FILTER, |
185 | R.string.renderer_scaling_filter, | 228 | titleId = R.string.renderer_scaling_filter, |
186 | 0, | 229 | choicesId = R.array.rendererScalingFilterNames, |
187 | R.array.rendererScalingFilterNames, | 230 | valuesId = R.array.rendererScalingFilterValues |
188 | R.array.rendererScalingFilterValues | ||
189 | ) | 231 | ) |
190 | ) | 232 | ) |
191 | put( | 233 | put( |
192 | SliderSetting( | 234 | SliderSetting( |
193 | IntSetting.FSR_SHARPENING_SLIDER, | 235 | IntSetting.FSR_SHARPENING_SLIDER, |
194 | R.string.fsr_sharpness, | 236 | titleId = R.string.fsr_sharpness, |
195 | R.string.fsr_sharpness_description, | 237 | descriptionId = R.string.fsr_sharpness_description, |
196 | 0, | 238 | units = "%" |
197 | 100, | ||
198 | "%" | ||
199 | ) | 239 | ) |
200 | ) | 240 | ) |
201 | put( | 241 | put( |
202 | SingleChoiceSetting( | 242 | SingleChoiceSetting( |
203 | IntSetting.RENDERER_ANTI_ALIASING, | 243 | IntSetting.RENDERER_ANTI_ALIASING, |
204 | R.string.renderer_anti_aliasing, | 244 | titleId = R.string.renderer_anti_aliasing, |
205 | 0, | 245 | choicesId = R.array.rendererAntiAliasingNames, |
206 | R.array.rendererAntiAliasingNames, | 246 | valuesId = R.array.rendererAntiAliasingValues |
207 | R.array.rendererAntiAliasingValues | ||
208 | ) | 247 | ) |
209 | ) | 248 | ) |
210 | put( | 249 | put( |
211 | SingleChoiceSetting( | 250 | SingleChoiceSetting( |
212 | IntSetting.RENDERER_SCREEN_LAYOUT, | 251 | IntSetting.RENDERER_SCREEN_LAYOUT, |
213 | R.string.renderer_screen_layout, | 252 | titleId = R.string.renderer_screen_layout, |
214 | 0, | 253 | choicesId = R.array.rendererScreenLayoutNames, |
215 | R.array.rendererScreenLayoutNames, | 254 | valuesId = R.array.rendererScreenLayoutValues |
216 | R.array.rendererScreenLayoutValues | ||
217 | ) | 255 | ) |
218 | ) | 256 | ) |
219 | put( | 257 | put( |
220 | SingleChoiceSetting( | 258 | SingleChoiceSetting( |
221 | IntSetting.RENDERER_ASPECT_RATIO, | 259 | IntSetting.RENDERER_ASPECT_RATIO, |
222 | R.string.renderer_aspect_ratio, | 260 | titleId = R.string.renderer_aspect_ratio, |
223 | 0, | 261 | choicesId = R.array.rendererAspectRatioNames, |
224 | R.array.rendererAspectRatioNames, | 262 | valuesId = R.array.rendererAspectRatioValues |
225 | R.array.rendererAspectRatioValues | ||
226 | ) | 263 | ) |
227 | ) | 264 | ) |
228 | put( | 265 | put( |
229 | SingleChoiceSetting( | 266 | SingleChoiceSetting( |
230 | IntSetting.VERTICAL_ALIGNMENT, | 267 | IntSetting.VERTICAL_ALIGNMENT, |
231 | R.string.vertical_alignment, | 268 | titleId = R.string.vertical_alignment, |
232 | 0, | 269 | descriptionId = 0, |
233 | R.array.verticalAlignmentEntries, | 270 | choicesId = R.array.verticalAlignmentEntries, |
234 | R.array.verticalAlignmentValues | 271 | valuesId = R.array.verticalAlignmentValues |
235 | ) | 272 | ) |
236 | ) | 273 | ) |
237 | put( | 274 | put( |
238 | SwitchSetting( | 275 | SwitchSetting( |
239 | BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, | 276 | BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE, |
240 | R.string.use_disk_shader_cache, | 277 | titleId = R.string.use_disk_shader_cache, |
241 | R.string.use_disk_shader_cache_description | 278 | descriptionId = R.string.use_disk_shader_cache_description |
242 | ) | 279 | ) |
243 | ) | 280 | ) |
244 | put( | 281 | put( |
245 | SwitchSetting( | 282 | SwitchSetting( |
246 | BooleanSetting.RENDERER_FORCE_MAX_CLOCK, | 283 | BooleanSetting.RENDERER_FORCE_MAX_CLOCK, |
247 | R.string.renderer_force_max_clock, | 284 | titleId = R.string.renderer_force_max_clock, |
248 | R.string.renderer_force_max_clock_description | 285 | descriptionId = R.string.renderer_force_max_clock_description |
249 | ) | 286 | ) |
250 | ) | 287 | ) |
251 | put( | 288 | put( |
252 | SwitchSetting( | 289 | SwitchSetting( |
253 | BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, | 290 | BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, |
254 | R.string.renderer_asynchronous_shaders, | 291 | titleId = R.string.renderer_asynchronous_shaders, |
255 | R.string.renderer_asynchronous_shaders_description | 292 | descriptionId = R.string.renderer_asynchronous_shaders_description |
256 | ) | 293 | ) |
257 | ) | 294 | ) |
258 | put( | 295 | put( |
259 | SwitchSetting( | 296 | SwitchSetting( |
260 | BooleanSetting.RENDERER_REACTIVE_FLUSHING, | 297 | BooleanSetting.RENDERER_REACTIVE_FLUSHING, |
261 | R.string.renderer_reactive_flushing, | 298 | titleId = R.string.renderer_reactive_flushing, |
262 | R.string.renderer_reactive_flushing_description | 299 | descriptionId = R.string.renderer_reactive_flushing_description |
263 | ) | 300 | ) |
264 | ) | 301 | ) |
265 | put( | 302 | put( |
266 | SingleChoiceSetting( | 303 | SingleChoiceSetting( |
267 | IntSetting.MAX_ANISOTROPY, | 304 | IntSetting.MAX_ANISOTROPY, |
268 | R.string.anisotropic_filtering, | 305 | titleId = R.string.anisotropic_filtering, |
269 | R.string.anisotropic_filtering_description, | 306 | descriptionId = R.string.anisotropic_filtering_description, |
270 | R.array.anisoEntries, | 307 | choicesId = R.array.anisoEntries, |
271 | R.array.anisoValues | 308 | valuesId = R.array.anisoValues |
272 | ) | 309 | ) |
273 | ) | 310 | ) |
274 | put( | 311 | put( |
275 | SingleChoiceSetting( | 312 | SingleChoiceSetting( |
276 | IntSetting.AUDIO_OUTPUT_ENGINE, | 313 | IntSetting.AUDIO_OUTPUT_ENGINE, |
277 | R.string.audio_output_engine, | 314 | titleId = R.string.audio_output_engine, |
278 | 0, | 315 | choicesId = R.array.outputEngineEntries, |
279 | R.array.outputEngineEntries, | 316 | valuesId = R.array.outputEngineValues |
280 | R.array.outputEngineValues | ||
281 | ) | 317 | ) |
282 | ) | 318 | ) |
283 | put( | 319 | put( |
284 | SliderSetting( | 320 | SliderSetting( |
285 | ByteSetting.AUDIO_VOLUME, | 321 | ByteSetting.AUDIO_VOLUME, |
286 | R.string.audio_volume, | 322 | titleId = R.string.audio_volume, |
287 | R.string.audio_volume_description, | 323 | descriptionId = R.string.audio_volume_description, |
288 | 0, | 324 | units = "%" |
289 | 100, | ||
290 | "%" | ||
291 | ) | 325 | ) |
292 | ) | 326 | ) |
293 | put( | 327 | put( |
294 | SingleChoiceSetting( | 328 | SingleChoiceSetting( |
295 | IntSetting.RENDERER_BACKEND, | 329 | IntSetting.RENDERER_BACKEND, |
296 | R.string.renderer_api, | 330 | titleId = R.string.renderer_api, |
297 | 0, | 331 | choicesId = R.array.rendererApiNames, |
298 | R.array.rendererApiNames, | 332 | valuesId = R.array.rendererApiValues |
299 | R.array.rendererApiValues | ||
300 | ) | 333 | ) |
301 | ) | 334 | ) |
302 | put( | 335 | put( |
303 | SwitchSetting( | 336 | SwitchSetting( |
304 | BooleanSetting.RENDERER_DEBUG, | 337 | BooleanSetting.RENDERER_DEBUG, |
305 | R.string.renderer_debug, | 338 | titleId = R.string.renderer_debug, |
306 | R.string.renderer_debug_description | 339 | descriptionId = R.string.renderer_debug_description |
307 | ) | 340 | ) |
308 | ) | 341 | ) |
309 | put( | 342 | put( |
310 | SwitchSetting( | 343 | SwitchSetting( |
311 | BooleanSetting.CPU_DEBUG_MODE, | 344 | BooleanSetting.CPU_DEBUG_MODE, |
312 | R.string.cpu_debug_mode, | 345 | titleId = R.string.cpu_debug_mode, |
313 | R.string.cpu_debug_mode_description | 346 | descriptionId = R.string.cpu_debug_mode_description |
314 | ) | 347 | ) |
315 | ) | 348 | ) |
316 | 349 | ||
@@ -346,7 +379,7 @@ abstract class SettingsItem( | |||
346 | 379 | ||
347 | override fun reset() = setBoolean(defaultValue) | 380 | override fun reset() = setBoolean(defaultValue) |
348 | } | 381 | } |
349 | put(SwitchSetting(fastmem, R.string.fastmem, 0)) | 382 | put(SwitchSetting(fastmem, R.string.fastmem)) |
350 | } | 383 | } |
351 | } | 384 | } |
352 | } | 385 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 97a5a9e59..ea5e099ed 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt | |||
@@ -3,16 +3,20 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.ArrayRes | ||
7 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
8 | 10 | ||
9 | class SingleChoiceSetting( | 11 | class SingleChoiceSetting( |
10 | setting: AbstractSetting, | 12 | setting: AbstractSetting, |
11 | titleId: Int, | 13 | @StringRes titleId: Int = 0, |
12 | descriptionId: Int, | 14 | titleString: String = "", |
13 | val choicesId: Int, | 15 | @StringRes descriptionId: Int = 0, |
14 | val valuesId: Int | 16 | descriptionString: String = "", |
15 | ) : SettingsItem(setting, titleId, descriptionId) { | 17 | @ArrayRes val choicesId: Int, |
18 | @ArrayRes val valuesId: Int | ||
19 | ) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { | ||
16 | override val type = TYPE_SINGLE_CHOICE | 20 | override val type = TYPE_SINGLE_CHOICE |
17 | 21 | ||
18 | fun getSelectedValue(needsGlobal: Boolean = false) = | 22 | fun getSelectedValue(needsGlobal: Boolean = false) = |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index b9b709bf7..6a5cdf48b 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting |
7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
@@ -12,12 +13,14 @@ import kotlin.math.roundToInt | |||
12 | 13 | ||
13 | class SliderSetting( | 14 | class SliderSetting( |
14 | setting: AbstractSetting, | 15 | setting: AbstractSetting, |
15 | titleId: Int, | 16 | @StringRes titleId: Int = 0, |
16 | descriptionId: Int, | 17 | titleString: String = "", |
17 | val min: Int, | 18 | @StringRes descriptionId: Int = 0, |
18 | val max: Int, | 19 | descriptionString: String = "", |
19 | val units: String | 20 | val min: Int = 0, |
20 | ) : SettingsItem(setting, titleId, descriptionId) { | 21 | val max: Int = 100, |
22 | val units: String = "" | ||
23 | ) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { | ||
21 | override val type = TYPE_SLIDER | 24 | override val type = TYPE_SLIDER |
22 | 25 | ||
23 | fun getSelectedValue(needsGlobal: Boolean = false) = | 26 | fun getSelectedValue(needsGlobal: Boolean = false) = |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index ba7920f50..5260ff4dc 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt | |||
@@ -3,15 +3,18 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
7 | 8 | ||
8 | class StringSingleChoiceSetting( | 9 | class StringSingleChoiceSetting( |
9 | private val stringSetting: AbstractStringSetting, | 10 | private val stringSetting: AbstractStringSetting, |
10 | titleId: Int, | 11 | @StringRes titleId: Int = 0, |
11 | descriptionId: Int, | 12 | titleString: String = "", |
13 | @StringRes descriptionId: Int = 0, | ||
14 | descriptionString: String = "", | ||
12 | val choices: Array<String>, | 15 | val choices: Array<String>, |
13 | val values: Array<String> | 16 | val values: Array<String> |
14 | ) : SettingsItem(stringSetting, titleId, descriptionId) { | 17 | ) : SettingsItem(stringSetting, titleId, titleString, descriptionId, descriptionString) { |
15 | override val type = TYPE_STRING_SINGLE_CHOICE | 18 | override val type = TYPE_STRING_SINGLE_CHOICE |
16 | 19 | ||
17 | fun getValueAt(index: Int): String = | 20 | fun getValueAt(index: Int): String = |
@@ -20,7 +23,7 @@ class StringSingleChoiceSetting( | |||
20 | fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal) | 23 | fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal) |
21 | fun setSelectedValue(value: String) = stringSetting.setString(value) | 24 | fun setSelectedValue(value: String) = stringSetting.setString(value) |
22 | 25 | ||
23 | val selectValueIndex: Int | 26 | val selectedValueIndex: Int |
24 | get() { | 27 | get() { |
25 | for (i in values.indices) { | 28 | for (i in values.indices) { |
26 | if (values[i] == getSelectedValue()) { | 29 | if (values[i] == getSelectedValue()) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index 94953b18a..c722393dd 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt | |||
@@ -8,10 +8,12 @@ import androidx.annotation.StringRes | |||
8 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 8 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
9 | 9 | ||
10 | class SubmenuSetting( | 10 | class SubmenuSetting( |
11 | @StringRes titleId: Int, | 11 | @StringRes titleId: Int = 0, |
12 | @StringRes descriptionId: Int, | 12 | titleString: String = "", |
13 | @DrawableRes val iconId: Int, | 13 | @StringRes descriptionId: Int = 0, |
14 | descriptionString: String = "", | ||
15 | @DrawableRes val iconId: Int = 0, | ||
14 | val menuKey: Settings.MenuTag | 16 | val menuKey: Settings.MenuTag |
15 | ) : SettingsItem(emptySetting, titleId, descriptionId) { | 17 | ) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) { |
16 | override val type = TYPE_SUBMENU | 18 | override val type = TYPE_SUBMENU |
17 | } | 19 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 44d47dd69..4984bf52e 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt | |||
@@ -3,15 +3,18 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
5 | 5 | ||
6 | import androidx.annotation.StringRes | ||
6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
9 | 10 | ||
10 | class SwitchSetting( | 11 | class SwitchSetting( |
11 | setting: AbstractSetting, | 12 | setting: AbstractSetting, |
12 | titleId: Int, | 13 | @StringRes titleId: Int = 0, |
13 | descriptionId: Int | 14 | titleString: String = "", |
14 | ) : SettingsItem(setting, titleId, descriptionId) { | 15 | @StringRes descriptionId: Int = 0, |
16 | descriptionString: String = "" | ||
17 | ) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { | ||
15 | override val type = TYPE_SWITCH | 18 | override val type = TYPE_SWITCH |
16 | 19 | ||
17 | fun getIsChecked(needsGlobal: Boolean = false): Boolean { | 20 | fun getIsChecked(needsGlobal: Boolean = false): Boolean { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt new file mode 100755 index 000000000..16a1d0504 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt | |||
@@ -0,0 +1,300 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.app.Dialog | ||
7 | import android.graphics.drawable.Animatable2 | ||
8 | import android.graphics.drawable.AnimatedVectorDrawable | ||
9 | import android.graphics.drawable.Drawable | ||
10 | import android.os.Bundle | ||
11 | import android.view.InputDevice | ||
12 | import android.view.KeyEvent | ||
13 | import android.view.LayoutInflater | ||
14 | import android.view.MotionEvent | ||
15 | import android.view.View | ||
16 | import android.view.ViewGroup | ||
17 | import androidx.fragment.app.DialogFragment | ||
18 | import androidx.fragment.app.activityViewModels | ||
19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
20 | import org.yuzu.yuzu_emu.R | ||
21 | import org.yuzu.yuzu_emu.databinding.DialogMappingBinding | ||
22 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
23 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
24 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
25 | import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting | ||
26 | import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting | ||
27 | import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting | ||
28 | import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting | ||
29 | import org.yuzu.yuzu_emu.utils.InputHandler | ||
30 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
31 | |||
32 | class InputDialogFragment : DialogFragment() { | ||
33 | private var inputAccepted = false | ||
34 | |||
35 | private var position: Int = 0 | ||
36 | |||
37 | private lateinit var inputSetting: InputSetting | ||
38 | |||
39 | private lateinit var binding: DialogMappingBinding | ||
40 | |||
41 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
42 | |||
43 | override fun onCreate(savedInstanceState: Bundle?) { | ||
44 | super.onCreate(savedInstanceState) | ||
45 | if (settingsViewModel.clickedItem == null) dismiss() | ||
46 | |||
47 | position = requireArguments().getInt(POSITION) | ||
48 | |||
49 | InputHandler.updateControllerData() | ||
50 | } | ||
51 | |||
52 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
53 | inputSetting = settingsViewModel.clickedItem as InputSetting | ||
54 | binding = DialogMappingBinding.inflate(layoutInflater) | ||
55 | |||
56 | val builder = MaterialAlertDialogBuilder(requireContext()) | ||
57 | .setPositiveButton(android.R.string.cancel) { _, _ -> | ||
58 | NativeInput.stopMapping() | ||
59 | dismiss() | ||
60 | } | ||
61 | .setView(binding.root) | ||
62 | |||
63 | val playButtonMapAnimation = { twoDirections: Boolean -> | ||
64 | val stickAnimation: AnimatedVectorDrawable | ||
65 | val buttonAnimation: AnimatedVectorDrawable | ||
66 | binding.imageStickAnimation.apply { | ||
67 | val anim = if (twoDirections) { | ||
68 | R.drawable.stick_two_direction_anim | ||
69 | } else { | ||
70 | R.drawable.stick_one_direction_anim | ||
71 | } | ||
72 | setBackgroundResource(anim) | ||
73 | stickAnimation = background as AnimatedVectorDrawable | ||
74 | } | ||
75 | binding.imageButtonAnimation.apply { | ||
76 | setBackgroundResource(R.drawable.button_anim) | ||
77 | buttonAnimation = background as AnimatedVectorDrawable | ||
78 | } | ||
79 | stickAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() { | ||
80 | override fun onAnimationEnd(drawable: Drawable?) { | ||
81 | buttonAnimation.start() | ||
82 | } | ||
83 | }) | ||
84 | buttonAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() { | ||
85 | override fun onAnimationEnd(drawable: Drawable?) { | ||
86 | stickAnimation.start() | ||
87 | } | ||
88 | }) | ||
89 | stickAnimation.start() | ||
90 | } | ||
91 | |||
92 | when (val setting = inputSetting) { | ||
93 | is AnalogInputSetting -> { | ||
94 | when (setting.nativeAnalog) { | ||
95 | NativeAnalog.LStick -> builder.setTitle( | ||
96 | getString(R.string.map_control, getString(R.string.left_stick)) | ||
97 | ) | ||
98 | |||
99 | NativeAnalog.RStick -> builder.setTitle( | ||
100 | getString(R.string.map_control, getString(R.string.right_stick)) | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | builder.setMessage(R.string.stick_map_description) | ||
105 | |||
106 | playButtonMapAnimation.invoke(true) | ||
107 | } | ||
108 | |||
109 | is ModifierInputSetting -> { | ||
110 | builder.setTitle(getString(R.string.map_control, setting.title)) | ||
111 | .setMessage(R.string.button_map_description) | ||
112 | playButtonMapAnimation.invoke(false) | ||
113 | } | ||
114 | |||
115 | is ButtonInputSetting -> { | ||
116 | if (setting.nativeButton == NativeButton.DUp || | ||
117 | setting.nativeButton == NativeButton.DDown || | ||
118 | setting.nativeButton == NativeButton.DLeft || | ||
119 | setting.nativeButton == NativeButton.DRight | ||
120 | ) { | ||
121 | builder.setTitle(getString(R.string.map_dpad_direction, setting.title)) | ||
122 | } else { | ||
123 | builder.setTitle(getString(R.string.map_control, setting.title)) | ||
124 | } | ||
125 | builder.setMessage(R.string.button_map_description) | ||
126 | playButtonMapAnimation.invoke(false) | ||
127 | } | ||
128 | } | ||
129 | |||
130 | return builder.create() | ||
131 | } | ||
132 | |||
133 | override fun onCreateView( | ||
134 | inflater: LayoutInflater, | ||
135 | container: ViewGroup?, | ||
136 | savedInstanceState: Bundle? | ||
137 | ): View { | ||
138 | return binding.root | ||
139 | } | ||
140 | |||
141 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
142 | super.onViewCreated(view, savedInstanceState) | ||
143 | view.requestFocus() | ||
144 | view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() } | ||
145 | dialog?.setOnKeyListener { _, _, keyEvent -> onKeyEvent(keyEvent) } | ||
146 | binding.root.setOnGenericMotionListener { _, motionEvent -> onMotionEvent(motionEvent) } | ||
147 | NativeInput.beginMapping(inputSetting.inputType.int) | ||
148 | } | ||
149 | |||
150 | private fun onKeyEvent(event: KeyEvent): Boolean { | ||
151 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && | ||
152 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD | ||
153 | ) { | ||
154 | return false | ||
155 | } | ||
156 | |||
157 | val action = when (event.action) { | ||
158 | KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED | ||
159 | KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED | ||
160 | else -> return false | ||
161 | } | ||
162 | val controllerData = | ||
163 | InputHandler.androidControllers[event.device.controllerNumber] ?: return false | ||
164 | NativeInput.onGamePadButtonEvent( | ||
165 | controllerData.getGUID(), | ||
166 | controllerData.getPort(), | ||
167 | event.keyCode, | ||
168 | action | ||
169 | ) | ||
170 | onInputReceived(event.device) | ||
171 | return true | ||
172 | } | ||
173 | |||
174 | private fun onMotionEvent(event: MotionEvent): Boolean { | ||
175 | if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK && | ||
176 | event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD | ||
177 | ) { | ||
178 | return false | ||
179 | } | ||
180 | |||
181 | // Temp workaround for DPads that give both axis and button input. The input system can't | ||
182 | // take in a specific axis direction for a binding so you lose half of the directions for a DPad. | ||
183 | |||
184 | val controllerData = | ||
185 | InputHandler.androidControllers[event.device.controllerNumber] ?: return false | ||
186 | event.device.motionRanges.forEach { | ||
187 | NativeInput.onGamePadAxisEvent( | ||
188 | controllerData.getGUID(), | ||
189 | controllerData.getPort(), | ||
190 | it.axis, | ||
191 | event.getAxisValue(it.axis) | ||
192 | ) | ||
193 | onInputReceived(event.device) | ||
194 | } | ||
195 | return true | ||
196 | } | ||
197 | |||
198 | private fun onInputReceived(device: InputDevice) { | ||
199 | val params = ParamPackage(NativeInput.getNextInput()) | ||
200 | if (params.has("engine") && isInputAcceptable(params) && !inputAccepted) { | ||
201 | inputAccepted = true | ||
202 | setResult(params, device) | ||
203 | } | ||
204 | } | ||
205 | |||
206 | private fun setResult(params: ParamPackage, device: InputDevice) { | ||
207 | NativeInput.stopMapping() | ||
208 | params.set("display", "${device.name} ${params.get("port", 0)}") | ||
209 | when (val item = settingsViewModel.clickedItem as InputSetting) { | ||
210 | is ModifierInputSetting, | ||
211 | is ButtonInputSetting -> { | ||
212 | // Invert DPad up and left bindings by default | ||
213 | val tempSetting = inputSetting as? ButtonInputSetting | ||
214 | if (tempSetting != null) { | ||
215 | if (tempSetting.nativeButton == NativeButton.DUp || | ||
216 | tempSetting.nativeButton == NativeButton.DLeft && | ||
217 | params.has("axis") | ||
218 | ) { | ||
219 | params.set("invert", "-") | ||
220 | } | ||
221 | } | ||
222 | |||
223 | item.setSelectedValue(params) | ||
224 | settingsViewModel.setAdapterItemChanged(position) | ||
225 | } | ||
226 | |||
227 | is AnalogInputSetting -> { | ||
228 | var analogParam = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) | ||
229 | analogParam = adjustAnalogParam(params, analogParam, item.analogDirection.param) | ||
230 | |||
231 | // Invert Y-Axis by default | ||
232 | analogParam.set("invert_y", "-") | ||
233 | |||
234 | item.setSelectedValue(analogParam) | ||
235 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
236 | } | ||
237 | } | ||
238 | dismiss() | ||
239 | } | ||
240 | |||
241 | private fun adjustAnalogParam( | ||
242 | inputParam: ParamPackage, | ||
243 | analogParam: ParamPackage, | ||
244 | buttonName: String | ||
245 | ): ParamPackage { | ||
246 | // The poller returned a complete axis, so set all the buttons | ||
247 | if (inputParam.has("axis_x") && inputParam.has("axis_y")) { | ||
248 | return inputParam | ||
249 | } | ||
250 | |||
251 | // Check if the current configuration has either no engine or an axis binding. | ||
252 | // Clears out the old binding and adds one with analog_from_button. | ||
253 | if (!analogParam.has("engine") || analogParam.has("axis_x") || analogParam.has("axis_y")) { | ||
254 | analogParam.clear() | ||
255 | analogParam.set("engine", "analog_from_button") | ||
256 | } | ||
257 | analogParam.set(buttonName, inputParam.serialize()) | ||
258 | return analogParam | ||
259 | } | ||
260 | |||
261 | private fun isInputAcceptable(params: ParamPackage): Boolean { | ||
262 | if (InputHandler.registeredControllers.size == 1) { | ||
263 | return true | ||
264 | } | ||
265 | |||
266 | if (params.has("motion")) { | ||
267 | return true | ||
268 | } | ||
269 | |||
270 | val currentDevice = settingsViewModel.getCurrentDeviceParams(params) | ||
271 | if (currentDevice.get("engine", "any") == "any") { | ||
272 | return true | ||
273 | } | ||
274 | |||
275 | val guidMatch = params.get("guid", "") == currentDevice.get("guid", "") || | ||
276 | params.get("guid", "") == currentDevice.get("guid2", "") | ||
277 | return params.get("engine", "") == currentDevice.get("engine", "") && | ||
278 | guidMatch && | ||
279 | params.get("port", 0) == currentDevice.get("port", 0) | ||
280 | } | ||
281 | |||
282 | companion object { | ||
283 | const val TAG = "InputDialogFragment" | ||
284 | |||
285 | const val POSITION = "Position" | ||
286 | |||
287 | fun newInstance( | ||
288 | inputMappingViewModel: SettingsViewModel, | ||
289 | setting: InputSetting, | ||
290 | position: Int | ||
291 | ): InputDialogFragment { | ||
292 | inputMappingViewModel.clickedItem = setting | ||
293 | val args = Bundle() | ||
294 | args.putInt(POSITION, position) | ||
295 | val fragment = InputDialogFragment() | ||
296 | fragment.arguments = args | ||
297 | return fragment | ||
298 | } | ||
299 | } | ||
300 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt new file mode 100755 index 000000000..5656e9d8d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileAdapter.kt | |||
@@ -0,0 +1,68 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.view.LayoutInflater | ||
7 | import android.view.View | ||
8 | import android.view.ViewGroup | ||
9 | import org.yuzu.yuzu_emu.YuzuApplication | ||
10 | import org.yuzu.yuzu_emu.adapters.AbstractListAdapter | ||
11 | import org.yuzu.yuzu_emu.databinding.ListItemInputProfileBinding | ||
12 | import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder | ||
13 | import org.yuzu.yuzu_emu.R | ||
14 | |||
15 | class InputProfileAdapter(options: List<ProfileItem>) : | ||
16 | AbstractListAdapter<ProfileItem, AbstractViewHolder<ProfileItem>>(options) { | ||
17 | override fun onCreateViewHolder( | ||
18 | parent: ViewGroup, | ||
19 | viewType: Int | ||
20 | ): AbstractViewHolder<ProfileItem> { | ||
21 | ListItemInputProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||
22 | .also { return InputProfileViewHolder(it) } | ||
23 | } | ||
24 | |||
25 | inner class InputProfileViewHolder(val binding: ListItemInputProfileBinding) : | ||
26 | AbstractViewHolder<ProfileItem>(binding) { | ||
27 | override fun bind(model: ProfileItem) { | ||
28 | when (model) { | ||
29 | is ExistingProfileItem -> { | ||
30 | binding.title.text = model.name | ||
31 | binding.buttonNew.visibility = View.GONE | ||
32 | binding.buttonDelete.visibility = View.VISIBLE | ||
33 | binding.buttonDelete.setOnClickListener { model.deleteProfile.invoke() } | ||
34 | binding.buttonSave.visibility = View.VISIBLE | ||
35 | binding.buttonSave.setOnClickListener { model.saveProfile.invoke() } | ||
36 | binding.buttonLoad.visibility = View.VISIBLE | ||
37 | binding.buttonLoad.setOnClickListener { model.loadProfile.invoke() } | ||
38 | } | ||
39 | |||
40 | is NewProfileItem -> { | ||
41 | binding.title.text = model.name | ||
42 | binding.buttonNew.visibility = View.VISIBLE | ||
43 | binding.buttonNew.setOnClickListener { model.createNewProfile.invoke() } | ||
44 | binding.buttonSave.visibility = View.GONE | ||
45 | binding.buttonDelete.visibility = View.GONE | ||
46 | binding.buttonLoad.visibility = View.GONE | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | |||
53 | sealed interface ProfileItem { | ||
54 | val name: String | ||
55 | } | ||
56 | |||
57 | data class NewProfileItem( | ||
58 | val createNewProfile: () -> Unit | ||
59 | ) : ProfileItem { | ||
60 | override val name: String = YuzuApplication.appContext.getString(R.string.create_new_profile) | ||
61 | } | ||
62 | |||
63 | data class ExistingProfileItem( | ||
64 | override val name: String, | ||
65 | val deleteProfile: () -> Unit, | ||
66 | val saveProfile: () -> Unit, | ||
67 | val loadProfile: () -> Unit | ||
68 | ) : ProfileItem | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt new file mode 100755 index 000000000..1bae593ae --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputProfileDialogFragment.kt | |||
@@ -0,0 +1,148 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.app.Dialog | ||
7 | import android.os.Bundle | ||
8 | import android.view.LayoutInflater | ||
9 | import android.view.View | ||
10 | import android.view.ViewGroup | ||
11 | import android.widget.Toast | ||
12 | import androidx.fragment.app.DialogFragment | ||
13 | import androidx.fragment.app.activityViewModels | ||
14 | import androidx.recyclerview.widget.LinearLayoutManager | ||
15 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
16 | import org.yuzu.yuzu_emu.R | ||
17 | import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding | ||
18 | import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting | ||
19 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | ||
20 | import org.yuzu.yuzu_emu.utils.collect | ||
21 | |||
22 | class InputProfileDialogFragment : DialogFragment() { | ||
23 | private var position = 0 | ||
24 | |||
25 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
26 | |||
27 | private lateinit var binding: DialogInputProfilesBinding | ||
28 | |||
29 | private lateinit var setting: InputProfileSetting | ||
30 | |||
31 | override fun onCreate(savedInstanceState: Bundle?) { | ||
32 | super.onCreate(savedInstanceState) | ||
33 | position = requireArguments().getInt(POSITION) | ||
34 | } | ||
35 | |||
36 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
37 | binding = DialogInputProfilesBinding.inflate(layoutInflater) | ||
38 | |||
39 | setting = settingsViewModel.clickedItem as InputProfileSetting | ||
40 | val options = mutableListOf<ProfileItem>().apply { | ||
41 | add( | ||
42 | NewProfileItem( | ||
43 | createNewProfile = { | ||
44 | NewInputProfileDialogFragment.newInstance( | ||
45 | settingsViewModel, | ||
46 | setting, | ||
47 | position | ||
48 | ).show(parentFragmentManager, NewInputProfileDialogFragment.TAG) | ||
49 | dismiss() | ||
50 | } | ||
51 | ) | ||
52 | ) | ||
53 | |||
54 | val onActionDismiss = { | ||
55 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
56 | dismiss() | ||
57 | } | ||
58 | setting.getProfileNames().forEach { | ||
59 | add( | ||
60 | ExistingProfileItem( | ||
61 | it, | ||
62 | deleteProfile = { | ||
63 | settingsViewModel.setShouldShowDeleteProfileDialog(it) | ||
64 | }, | ||
65 | saveProfile = { | ||
66 | if (!setting.saveProfile(it)) { | ||
67 | Toast.makeText( | ||
68 | requireContext(), | ||
69 | R.string.failed_to_save_profile, | ||
70 | Toast.LENGTH_SHORT | ||
71 | ).show() | ||
72 | } | ||
73 | onActionDismiss.invoke() | ||
74 | }, | ||
75 | loadProfile = { | ||
76 | if (!setting.loadProfile(it)) { | ||
77 | Toast.makeText( | ||
78 | requireContext(), | ||
79 | R.string.failed_to_load_profile, | ||
80 | Toast.LENGTH_SHORT | ||
81 | ).show() | ||
82 | } | ||
83 | onActionDismiss.invoke() | ||
84 | } | ||
85 | ) | ||
86 | ) | ||
87 | } | ||
88 | } | ||
89 | binding.listProfiles.apply { | ||
90 | layoutManager = LinearLayoutManager(requireContext()) | ||
91 | adapter = InputProfileAdapter(options) | ||
92 | } | ||
93 | |||
94 | return MaterialAlertDialogBuilder(requireContext()) | ||
95 | .setView(binding.root) | ||
96 | .create() | ||
97 | } | ||
98 | |||
99 | override fun onCreateView( | ||
100 | inflater: LayoutInflater, | ||
101 | container: ViewGroup?, | ||
102 | savedInstanceState: Bundle? | ||
103 | ): View { | ||
104 | return binding.root | ||
105 | } | ||
106 | |||
107 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
108 | super.onViewCreated(view, savedInstanceState) | ||
109 | |||
110 | settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) { | ||
111 | if (it.isNotEmpty()) { | ||
112 | MessageDialogFragment.newInstance( | ||
113 | activity = requireActivity(), | ||
114 | titleId = R.string.delete_input_profile, | ||
115 | descriptionId = R.string.delete_input_profile_description, | ||
116 | positiveAction = { | ||
117 | setting.deleteProfile(it) | ||
118 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
119 | }, | ||
120 | negativeAction = {}, | ||
121 | negativeButtonTitleId = android.R.string.cancel | ||
122 | ).show(parentFragmentManager, MessageDialogFragment.TAG) | ||
123 | settingsViewModel.setShouldShowDeleteProfileDialog("") | ||
124 | dismiss() | ||
125 | } | ||
126 | } | ||
127 | } | ||
128 | |||
129 | companion object { | ||
130 | const val TAG = "InputProfileDialogFragment" | ||
131 | |||
132 | const val POSITION = "Position" | ||
133 | |||
134 | fun newInstance( | ||
135 | settingsViewModel: SettingsViewModel, | ||
136 | profileSetting: InputProfileSetting, | ||
137 | position: Int | ||
138 | ): InputProfileDialogFragment { | ||
139 | settingsViewModel.clickedItem = profileSetting | ||
140 | |||
141 | val args = Bundle() | ||
142 | args.putInt(POSITION, position) | ||
143 | val fragment = InputProfileDialogFragment() | ||
144 | fragment.arguments = args | ||
145 | return fragment | ||
146 | } | ||
147 | } | ||
148 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt new file mode 100755 index 000000000..6e52bea80 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/NewInputProfileDialogFragment.kt | |||
@@ -0,0 +1,79 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.app.Dialog | ||
7 | import android.os.Bundle | ||
8 | import android.widget.Toast | ||
9 | import androidx.fragment.app.DialogFragment | ||
10 | import androidx.fragment.app.activityViewModels | ||
11 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
12 | import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding | ||
13 | import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting | ||
14 | import org.yuzu.yuzu_emu.R | ||
15 | |||
16 | class NewInputProfileDialogFragment : DialogFragment() { | ||
17 | private var position = 0 | ||
18 | |||
19 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
20 | |||
21 | private lateinit var binding: DialogEditTextBinding | ||
22 | |||
23 | override fun onCreate(savedInstanceState: Bundle?) { | ||
24 | super.onCreate(savedInstanceState) | ||
25 | position = requireArguments().getInt(POSITION) | ||
26 | } | ||
27 | |||
28 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
29 | binding = DialogEditTextBinding.inflate(layoutInflater) | ||
30 | |||
31 | val setting = settingsViewModel.clickedItem as InputProfileSetting | ||
32 | return MaterialAlertDialogBuilder(requireContext()) | ||
33 | .setTitle(R.string.enter_profile_name) | ||
34 | .setPositiveButton(android.R.string.ok) { _, _ -> | ||
35 | val profileName = binding.editText.text.toString() | ||
36 | if (!setting.isProfileNameValid(profileName)) { | ||
37 | Toast.makeText( | ||
38 | requireContext(), | ||
39 | R.string.invalid_profile_name, | ||
40 | Toast.LENGTH_SHORT | ||
41 | ).show() | ||
42 | return@setPositiveButton | ||
43 | } | ||
44 | |||
45 | if (!setting.createProfile(profileName)) { | ||
46 | Toast.makeText( | ||
47 | requireContext(), | ||
48 | R.string.profile_name_already_exists, | ||
49 | Toast.LENGTH_SHORT | ||
50 | ).show() | ||
51 | } else { | ||
52 | settingsViewModel.setAdapterItemChanged(position) | ||
53 | } | ||
54 | } | ||
55 | .setNegativeButton(android.R.string.cancel, null) | ||
56 | .setView(binding.root) | ||
57 | .show() | ||
58 | } | ||
59 | |||
60 | companion object { | ||
61 | const val TAG = "NewInputProfileDialogFragment" | ||
62 | |||
63 | const val POSITION = "Position" | ||
64 | |||
65 | fun newInstance( | ||
66 | settingsViewModel: SettingsViewModel, | ||
67 | profileSetting: InputProfileSetting, | ||
68 | position: Int | ||
69 | ): NewInputProfileDialogFragment { | ||
70 | settingsViewModel.clickedItem = profileSetting | ||
71 | |||
72 | val args = Bundle() | ||
73 | args.putInt(POSITION, position) | ||
74 | val fragment = NewInputProfileDialogFragment() | ||
75 | fragment.arguments = args | ||
76 | return fragment | ||
77 | } | ||
78 | } | ||
79 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index 6f072241a..455b3b5ff 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt | |||
@@ -13,21 +13,16 @@ import androidx.appcompat.app.AppCompatActivity | |||
13 | import androidx.core.view.ViewCompat | 13 | import androidx.core.view.ViewCompat |
14 | import androidx.core.view.WindowCompat | 14 | import androidx.core.view.WindowCompat |
15 | import androidx.core.view.WindowInsetsCompat | 15 | import androidx.core.view.WindowInsetsCompat |
16 | import androidx.lifecycle.Lifecycle | ||
17 | import androidx.lifecycle.lifecycleScope | ||
18 | import androidx.lifecycle.repeatOnLifecycle | ||
19 | import androidx.navigation.fragment.NavHostFragment | 16 | import androidx.navigation.fragment.NavHostFragment |
20 | import androidx.navigation.navArgs | 17 | import androidx.navigation.navArgs |
21 | import com.google.android.material.color.MaterialColors | 18 | import com.google.android.material.color.MaterialColors |
22 | import kotlinx.coroutines.flow.collectLatest | ||
23 | import kotlinx.coroutines.launch | ||
24 | import org.yuzu.yuzu_emu.NativeLibrary | 19 | import org.yuzu.yuzu_emu.NativeLibrary |
25 | import java.io.IOException | 20 | import java.io.IOException |
26 | import org.yuzu.yuzu_emu.R | 21 | import org.yuzu.yuzu_emu.R |
27 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 22 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
23 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
28 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 24 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
29 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment | 25 | import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment |
30 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
31 | import org.yuzu.yuzu_emu.utils.* | 26 | import org.yuzu.yuzu_emu.utils.* |
32 | 27 | ||
33 | class SettingsActivity : AppCompatActivity() { | 28 | class SettingsActivity : AppCompatActivity() { |
@@ -70,39 +65,23 @@ class SettingsActivity : AppCompatActivity() { | |||
70 | ) | 65 | ) |
71 | } | 66 | } |
72 | 67 | ||
73 | lifecycleScope.apply { | 68 | settingsViewModel.shouldRecreate.collect( |
74 | launch { | 69 | this, |
75 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 70 | resetState = { settingsViewModel.setShouldRecreate(false) } |
76 | settingsViewModel.shouldRecreate.collectLatest { | 71 | ) { if (it) recreate() } |
77 | if (it) { | 72 | settingsViewModel.shouldNavigateBack.collect( |
78 | settingsViewModel.setShouldRecreate(false) | 73 | this, |
79 | recreate() | 74 | resetState = { settingsViewModel.setShouldNavigateBack(false) } |
80 | } | 75 | ) { if (it) navigateBack() } |
81 | } | 76 | settingsViewModel.shouldShowResetSettingsDialog.collect( |
82 | } | 77 | this, |
83 | } | 78 | resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) } |
84 | launch { | 79 | ) { |
85 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 80 | if (it) { |
86 | settingsViewModel.shouldNavigateBack.collectLatest { | 81 | ResetSettingsDialogFragment().show( |
87 | if (it) { | 82 | supportFragmentManager, |
88 | settingsViewModel.setShouldNavigateBack(false) | 83 | ResetSettingsDialogFragment.TAG |
89 | navigateBack() | 84 | ) |
90 | } | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | launch { | ||
95 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
96 | settingsViewModel.shouldShowResetSettingsDialog.collectLatest { | ||
97 | if (it) { | ||
98 | settingsViewModel.setShouldShowResetSettingsDialog(false) | ||
99 | ResetSettingsDialogFragment().show( | ||
100 | supportFragmentManager, | ||
101 | ResetSettingsDialogFragment.TAG | ||
102 | ) | ||
103 | } | ||
104 | } | ||
105 | } | ||
106 | } | 85 | } |
107 | } | 86 | } |
108 | 87 | ||
@@ -137,6 +116,7 @@ class SettingsActivity : AppCompatActivity() { | |||
137 | super.onStop() | 116 | super.onStop() |
138 | Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...") | 117 | Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...") |
139 | if (isFinishing) { | 118 | if (isFinishing) { |
119 | NativeInput.reloadInputDevices() | ||
140 | NativeLibrary.applySettings() | 120 | NativeLibrary.applySettings() |
141 | if (args.game == null) { | 121 | if (args.game == null) { |
142 | NativeConfig.saveGlobalConfig() | 122 | NativeConfig.saveGlobalConfig() |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index be9b3031b..45c8faa10 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt | |||
@@ -8,12 +8,11 @@ import android.icu.util.Calendar | |||
8 | import android.icu.util.TimeZone | 8 | import android.icu.util.TimeZone |
9 | import android.text.format.DateFormat | 9 | import android.text.format.DateFormat |
10 | import android.view.LayoutInflater | 10 | import android.view.LayoutInflater |
11 | import android.view.View | ||
11 | import android.view.ViewGroup | 12 | import android.view.ViewGroup |
13 | import android.widget.PopupMenu | ||
12 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
13 | import androidx.lifecycle.Lifecycle | ||
14 | import androidx.lifecycle.ViewModelProvider | 15 | import androidx.lifecycle.ViewModelProvider |
15 | import androidx.lifecycle.lifecycleScope | ||
16 | import androidx.lifecycle.repeatOnLifecycle | ||
17 | import androidx.navigation.findNavController | 16 | import androidx.navigation.findNavController |
18 | import androidx.recyclerview.widget.AsyncDifferConfig | 17 | import androidx.recyclerview.widget.AsyncDifferConfig |
19 | import androidx.recyclerview.widget.DiffUtil | 18 | import androidx.recyclerview.widget.DiffUtil |
@@ -21,16 +20,18 @@ import androidx.recyclerview.widget.ListAdapter | |||
21 | import com.google.android.material.datepicker.MaterialDatePicker | 20 | import com.google.android.material.datepicker.MaterialDatePicker |
22 | import com.google.android.material.timepicker.MaterialTimePicker | 21 | import com.google.android.material.timepicker.MaterialTimePicker |
23 | import com.google.android.material.timepicker.TimeFormat | 22 | import com.google.android.material.timepicker.TimeFormat |
24 | import kotlinx.coroutines.launch | ||
25 | import org.yuzu.yuzu_emu.R | 23 | import org.yuzu.yuzu_emu.R |
26 | import org.yuzu.yuzu_emu.SettingsNavigationDirections | 24 | import org.yuzu.yuzu_emu.SettingsNavigationDirections |
27 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 25 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
26 | import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding | ||
28 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 27 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
29 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding | 28 | import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding |
29 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
30 | import org.yuzu.yuzu_emu.features.input.model.AnalogDirection | ||
31 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | ||
30 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 32 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
31 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* | 33 | import org.yuzu.yuzu_emu.features.settings.ui.viewholder.* |
32 | import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment | 34 | import org.yuzu.yuzu_emu.utils.ParamPackage |
33 | import org.yuzu.yuzu_emu.model.SettingsViewModel | ||
34 | 35 | ||
35 | class SettingsAdapter( | 36 | class SettingsAdapter( |
36 | private val fragment: Fragment, | 37 | private val fragment: Fragment, |
@@ -41,19 +42,6 @@ class SettingsAdapter( | |||
41 | private val settingsViewModel: SettingsViewModel | 42 | private val settingsViewModel: SettingsViewModel |
42 | get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] | 43 | get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java] |
43 | 44 | ||
44 | init { | ||
45 | fragment.viewLifecycleOwner.lifecycleScope.launch { | ||
46 | fragment.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
47 | settingsViewModel.adapterItemChanged.collect { | ||
48 | if (it != -1) { | ||
49 | notifyItemChanged(it) | ||
50 | settingsViewModel.setAdapterItemChanged(-1) | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { | 45 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder { |
58 | val inflater = LayoutInflater.from(parent.context) | 46 | val inflater = LayoutInflater.from(parent.context) |
59 | return when (viewType) { | 47 | return when (viewType) { |
@@ -85,8 +73,19 @@ class SettingsAdapter( | |||
85 | RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this) | 73 | RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this) |
86 | } | 74 | } |
87 | 75 | ||
76 | SettingsItem.TYPE_INPUT -> { | ||
77 | InputViewHolder(ListItemSettingInputBinding.inflate(inflater), this) | ||
78 | } | ||
79 | |||
80 | SettingsItem.TYPE_INT_SINGLE_CHOICE -> { | ||
81 | SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this) | ||
82 | } | ||
83 | |||
84 | SettingsItem.TYPE_INPUT_PROFILE -> { | ||
85 | InputProfileViewHolder(ListItemSettingBinding.inflate(inflater), this) | ||
86 | } | ||
87 | |||
88 | else -> { | 88 | else -> { |
89 | // TODO: Create an error view since we can't return null now | ||
90 | HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this) | 89 | HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this) |
91 | } | 90 | } |
92 | } | 91 | } |
@@ -126,6 +125,15 @@ class SettingsAdapter( | |||
126 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) | 125 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) |
127 | } | 126 | } |
128 | 127 | ||
128 | fun onIntSingleChoiceClick(item: IntSingleChoiceSetting, position: Int) { | ||
129 | SettingsDialogFragment.newInstance( | ||
130 | settingsViewModel, | ||
131 | item, | ||
132 | SettingsItem.TYPE_INT_SINGLE_CHOICE, | ||
133 | position | ||
134 | ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) | ||
135 | } | ||
136 | |||
129 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { | 137 | fun onDateTimeClick(item: DateTimeSetting, position: Int) { |
130 | val storedTime = item.getValue() * 1000 | 138 | val storedTime = item.getValue() * 1000 |
131 | 139 | ||
@@ -185,6 +193,205 @@ class SettingsAdapter( | |||
185 | fragment.view?.findNavController()?.navigate(action) | 193 | fragment.view?.findNavController()?.navigate(action) |
186 | } | 194 | } |
187 | 195 | ||
196 | fun onInputProfileClick(item: InputProfileSetting, position: Int) { | ||
197 | InputProfileDialogFragment.newInstance( | ||
198 | settingsViewModel, | ||
199 | item, | ||
200 | position | ||
201 | ).show(fragment.childFragmentManager, InputProfileDialogFragment.TAG) | ||
202 | } | ||
203 | |||
204 | fun onInputClick(item: InputSetting, position: Int) { | ||
205 | InputDialogFragment.newInstance( | ||
206 | settingsViewModel, | ||
207 | item, | ||
208 | position | ||
209 | ).show(fragment.childFragmentManager, InputDialogFragment.TAG) | ||
210 | } | ||
211 | |||
212 | fun onInputOptionsClick(anchor: View, item: InputSetting, position: Int) { | ||
213 | val popup = PopupMenu(context, anchor) | ||
214 | popup.menuInflater.inflate(R.menu.menu_input_options, popup.menu) | ||
215 | |||
216 | popup.menu.apply { | ||
217 | val invertAxis = findItem(R.id.invert_axis) | ||
218 | val invertButton = findItem(R.id.invert_button) | ||
219 | val toggleButton = findItem(R.id.toggle_button) | ||
220 | val turboButton = findItem(R.id.turbo_button) | ||
221 | val setThreshold = findItem(R.id.set_threshold) | ||
222 | val toggleAxis = findItem(R.id.toggle_axis) | ||
223 | when (item) { | ||
224 | is AnalogInputSetting -> { | ||
225 | val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) | ||
226 | |||
227 | invertAxis.isVisible = true | ||
228 | invertAxis.isCheckable = true | ||
229 | invertAxis.isChecked = when (item.analogDirection) { | ||
230 | AnalogDirection.Left, AnalogDirection.Right -> { | ||
231 | params.get("invert_x", "+") == "-" | ||
232 | } | ||
233 | |||
234 | AnalogDirection.Up, AnalogDirection.Down -> { | ||
235 | params.get("invert_y", "+") == "-" | ||
236 | } | ||
237 | } | ||
238 | invertAxis.setOnMenuItemClickListener { | ||
239 | if (item.analogDirection == AnalogDirection.Left || | ||
240 | item.analogDirection == AnalogDirection.Right | ||
241 | ) { | ||
242 | val invertValue = params.get("invert_x", "+") == "-" | ||
243 | val invertString = if (invertValue) "+" else "-" | ||
244 | params.set("invert_x", invertString) | ||
245 | } else if ( | ||
246 | item.analogDirection == AnalogDirection.Up || | ||
247 | item.analogDirection == AnalogDirection.Down | ||
248 | ) { | ||
249 | val invertValue = params.get("invert_y", "+") == "-" | ||
250 | val invertString = if (invertValue) "+" else "-" | ||
251 | params.set("invert_y", invertString) | ||
252 | } | ||
253 | true | ||
254 | } | ||
255 | |||
256 | popup.setOnDismissListener { | ||
257 | NativeInput.setStickParam(item.playerIndex, item.nativeAnalog, params) | ||
258 | settingsViewModel.setDatasetChanged(true) | ||
259 | } | ||
260 | } | ||
261 | |||
262 | is ButtonInputSetting -> { | ||
263 | val params = NativeInput.getButtonParam(item.playerIndex, item.nativeButton) | ||
264 | if (params.has("code") || params.has("button") || params.has("hat")) { | ||
265 | val buttonInvert = params.get("inverted", false) | ||
266 | invertButton.isVisible = true | ||
267 | invertButton.isCheckable = true | ||
268 | invertButton.isChecked = buttonInvert | ||
269 | invertButton.setOnMenuItemClickListener { | ||
270 | params.set("inverted", !buttonInvert) | ||
271 | true | ||
272 | } | ||
273 | |||
274 | val toggle = params.get("toggle", false) | ||
275 | toggleButton.isVisible = true | ||
276 | toggleButton.isCheckable = true | ||
277 | toggleButton.isChecked = toggle | ||
278 | toggleButton.setOnMenuItemClickListener { | ||
279 | params.set("toggle", !toggle) | ||
280 | true | ||
281 | } | ||
282 | |||
283 | val turbo = params.get("turbo", false) | ||
284 | turboButton.isVisible = true | ||
285 | turboButton.isCheckable = true | ||
286 | turboButton.isChecked = turbo | ||
287 | turboButton.setOnMenuItemClickListener { | ||
288 | params.set("turbo", !turbo) | ||
289 | true | ||
290 | } | ||
291 | } else if (params.has("axis")) { | ||
292 | val axisInvert = params.get("invert", "+") == "-" | ||
293 | invertAxis.isVisible = true | ||
294 | invertAxis.isCheckable = true | ||
295 | invertAxis.isChecked = axisInvert | ||
296 | invertAxis.setOnMenuItemClickListener { | ||
297 | params.set("invert", if (!axisInvert) "-" else "+") | ||
298 | true | ||
299 | } | ||
300 | |||
301 | val buttonInvert = params.get("inverted", false) | ||
302 | invertButton.isVisible = true | ||
303 | invertButton.isCheckable = true | ||
304 | invertButton.isChecked = buttonInvert | ||
305 | invertButton.setOnMenuItemClickListener { | ||
306 | params.set("inverted", !buttonInvert) | ||
307 | true | ||
308 | } | ||
309 | |||
310 | setThreshold.isVisible = true | ||
311 | val thresholdSetting = object : AbstractIntSetting { | ||
312 | override val key = "" | ||
313 | |||
314 | override fun getInt(needsGlobal: Boolean): Int = | ||
315 | (params.get("threshold", 0.5f) * 100).toInt() | ||
316 | |||
317 | override fun setInt(value: Int) { | ||
318 | params.set("threshold", value.toFloat() / 100) | ||
319 | NativeInput.setButtonParam( | ||
320 | item.playerIndex, | ||
321 | item.nativeButton, | ||
322 | params | ||
323 | ) | ||
324 | } | ||
325 | |||
326 | override val defaultValue = 50 | ||
327 | |||
328 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
329 | getInt(needsGlobal).toString() | ||
330 | |||
331 | override fun reset() = setInt(defaultValue) | ||
332 | } | ||
333 | setThreshold.setOnMenuItemClickListener { | ||
334 | onSliderClick( | ||
335 | SliderSetting(thresholdSetting, R.string.set_threshold), | ||
336 | position | ||
337 | ) | ||
338 | true | ||
339 | } | ||
340 | |||
341 | val axisToggle = params.get("toggle", false) | ||
342 | toggleAxis.isVisible = true | ||
343 | toggleAxis.isCheckable = true | ||
344 | toggleAxis.isChecked = axisToggle | ||
345 | toggleAxis.setOnMenuItemClickListener { | ||
346 | params.set("toggle", !axisToggle) | ||
347 | true | ||
348 | } | ||
349 | } | ||
350 | |||
351 | popup.setOnDismissListener { | ||
352 | NativeInput.setButtonParam(item.playerIndex, item.nativeButton, params) | ||
353 | settingsViewModel.setAdapterItemChanged(position) | ||
354 | } | ||
355 | } | ||
356 | |||
357 | is ModifierInputSetting -> { | ||
358 | val stickParams = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) | ||
359 | val modifierParams = ParamPackage(stickParams.get("modifier", "")) | ||
360 | |||
361 | val invert = modifierParams.get("inverted", false) | ||
362 | invertButton.isVisible = true | ||
363 | invertButton.isCheckable = true | ||
364 | invertButton.isChecked = invert | ||
365 | invertButton.setOnMenuItemClickListener { | ||
366 | modifierParams.set("inverted", !invert) | ||
367 | stickParams.set("modifier", modifierParams.serialize()) | ||
368 | true | ||
369 | } | ||
370 | |||
371 | val toggle = modifierParams.get("toggle", false) | ||
372 | toggleButton.isVisible = true | ||
373 | toggleButton.isCheckable = true | ||
374 | toggleButton.isChecked = toggle | ||
375 | toggleButton.setOnMenuItemClickListener { | ||
376 | modifierParams.set("toggle", !toggle) | ||
377 | stickParams.set("modifier", modifierParams.serialize()) | ||
378 | true | ||
379 | } | ||
380 | |||
381 | popup.setOnDismissListener { | ||
382 | NativeInput.setStickParam( | ||
383 | item.playerIndex, | ||
384 | item.nativeAnalog, | ||
385 | stickParams | ||
386 | ) | ||
387 | settingsViewModel.setAdapterItemChanged(position) | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | } | ||
392 | popup.show() | ||
393 | } | ||
394 | |||
188 | fun onLongClick(item: SettingsItem, position: Int): Boolean { | 395 | fun onLongClick(item: SettingsItem, position: Int): Boolean { |
189 | SettingsDialogFragment.newInstance( | 396 | SettingsDialogFragment.newInstance( |
190 | settingsViewModel, | 397 | settingsViewModel, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt new file mode 100755 index 000000000..a81ff6b1a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt | |||
@@ -0,0 +1,278 @@ | |||
1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.app.Dialog | ||
7 | import android.content.DialogInterface | ||
8 | import android.os.Bundle | ||
9 | import android.view.LayoutInflater | ||
10 | import android.view.View | ||
11 | import android.view.ViewGroup | ||
12 | import androidx.fragment.app.DialogFragment | ||
13 | import androidx.fragment.app.activityViewModels | ||
14 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
15 | import com.google.android.material.slider.Slider | ||
16 | import org.yuzu.yuzu_emu.R | ||
17 | import org.yuzu.yuzu_emu.databinding.DialogSliderBinding | ||
18 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
19 | import org.yuzu.yuzu_emu.features.input.model.AnalogDirection | ||
20 | import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting | ||
21 | import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting | ||
22 | import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting | ||
23 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
24 | import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting | ||
25 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting | ||
26 | import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting | ||
27 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
28 | import org.yuzu.yuzu_emu.utils.collect | ||
29 | |||
30 | class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener { | ||
31 | private var type = 0 | ||
32 | private var position = 0 | ||
33 | |||
34 | private var defaultCancelListener = | ||
35 | DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() } | ||
36 | |||
37 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
38 | |||
39 | private lateinit var sliderBinding: DialogSliderBinding | ||
40 | |||
41 | override fun onCreate(savedInstanceState: Bundle?) { | ||
42 | super.onCreate(savedInstanceState) | ||
43 | type = requireArguments().getInt(TYPE) | ||
44 | position = requireArguments().getInt(POSITION) | ||
45 | |||
46 | if (settingsViewModel.clickedItem == null) dismiss() | ||
47 | } | ||
48 | |||
49 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
50 | return when (type) { | ||
51 | TYPE_RESET_SETTING -> { | ||
52 | MaterialAlertDialogBuilder(requireContext()) | ||
53 | .setMessage(R.string.reset_setting_confirmation) | ||
54 | .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> | ||
55 | when (val item = settingsViewModel.clickedItem) { | ||
56 | is AnalogInputSetting -> { | ||
57 | val stickParam = NativeInput.getStickParam( | ||
58 | item.playerIndex, | ||
59 | item.nativeAnalog | ||
60 | ) | ||
61 | if (stickParam.get("engine", "") == "analog_from_button") { | ||
62 | when (item.analogDirection) { | ||
63 | AnalogDirection.Up -> stickParam.erase("up") | ||
64 | AnalogDirection.Down -> stickParam.erase("down") | ||
65 | AnalogDirection.Left -> stickParam.erase("left") | ||
66 | AnalogDirection.Right -> stickParam.erase("right") | ||
67 | } | ||
68 | NativeInput.setStickParam( | ||
69 | item.playerIndex, | ||
70 | item.nativeAnalog, | ||
71 | stickParam | ||
72 | ) | ||
73 | settingsViewModel.setAdapterItemChanged(position) | ||
74 | } else { | ||
75 | NativeInput.setStickParam( | ||
76 | item.playerIndex, | ||
77 | item.nativeAnalog, | ||
78 | ParamPackage() | ||
79 | ) | ||
80 | settingsViewModel.setDatasetChanged(true) | ||
81 | } | ||
82 | } | ||
83 | |||
84 | is ButtonInputSetting -> { | ||
85 | NativeInput.setButtonParam( | ||
86 | item.playerIndex, | ||
87 | item.nativeButton, | ||
88 | ParamPackage() | ||
89 | ) | ||
90 | settingsViewModel.setAdapterItemChanged(position) | ||
91 | } | ||
92 | |||
93 | else -> { | ||
94 | settingsViewModel.clickedItem!!.setting.reset() | ||
95 | settingsViewModel.setAdapterItemChanged(position) | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | .setNegativeButton(android.R.string.cancel, null) | ||
100 | .create() | ||
101 | } | ||
102 | |||
103 | SettingsItem.TYPE_SINGLE_CHOICE -> { | ||
104 | val item = settingsViewModel.clickedItem as SingleChoiceSetting | ||
105 | val value = getSelectionForSingleChoiceValue(item) | ||
106 | MaterialAlertDialogBuilder(requireContext()) | ||
107 | .setTitle(item.title) | ||
108 | .setSingleChoiceItems(item.choicesId, value, this) | ||
109 | .create() | ||
110 | } | ||
111 | |||
112 | SettingsItem.TYPE_SLIDER -> { | ||
113 | sliderBinding = DialogSliderBinding.inflate(layoutInflater) | ||
114 | val item = settingsViewModel.clickedItem as SliderSetting | ||
115 | |||
116 | settingsViewModel.setSliderTextValue(item.getSelectedValue().toFloat(), item.units) | ||
117 | sliderBinding.slider.apply { | ||
118 | valueFrom = item.min.toFloat() | ||
119 | valueTo = item.max.toFloat() | ||
120 | value = settingsViewModel.sliderProgress.value.toFloat() | ||
121 | addOnChangeListener { _: Slider, value: Float, _: Boolean -> | ||
122 | settingsViewModel.setSliderTextValue(value, item.units) | ||
123 | } | ||
124 | } | ||
125 | |||
126 | MaterialAlertDialogBuilder(requireContext()) | ||
127 | .setTitle(item.title) | ||
128 | .setView(sliderBinding.root) | ||
129 | .setPositiveButton(android.R.string.ok, this) | ||
130 | .setNegativeButton(android.R.string.cancel, defaultCancelListener) | ||
131 | .create() | ||
132 | } | ||
133 | |||
134 | SettingsItem.TYPE_STRING_SINGLE_CHOICE -> { | ||
135 | val item = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
136 | MaterialAlertDialogBuilder(requireContext()) | ||
137 | .setTitle(item.title) | ||
138 | .setSingleChoiceItems(item.choices, item.selectedValueIndex, this) | ||
139 | .create() | ||
140 | } | ||
141 | |||
142 | SettingsItem.TYPE_INT_SINGLE_CHOICE -> { | ||
143 | val item = settingsViewModel.clickedItem as IntSingleChoiceSetting | ||
144 | MaterialAlertDialogBuilder(requireContext()) | ||
145 | .setTitle(item.title) | ||
146 | .setSingleChoiceItems(item.choices, item.selectedValueIndex, this) | ||
147 | .create() | ||
148 | } | ||
149 | |||
150 | else -> super.onCreateDialog(savedInstanceState) | ||
151 | } | ||
152 | } | ||
153 | |||
154 | override fun onCreateView( | ||
155 | inflater: LayoutInflater, | ||
156 | container: ViewGroup?, | ||
157 | savedInstanceState: Bundle? | ||
158 | ): View? { | ||
159 | return when (type) { | ||
160 | SettingsItem.TYPE_SLIDER -> sliderBinding.root | ||
161 | else -> super.onCreateView(inflater, container, savedInstanceState) | ||
162 | } | ||
163 | } | ||
164 | |||
165 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
166 | super.onViewCreated(view, savedInstanceState) | ||
167 | when (type) { | ||
168 | SettingsItem.TYPE_SLIDER -> { | ||
169 | settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) { | ||
170 | sliderBinding.textValue.text = it | ||
171 | } | ||
172 | settingsViewModel.sliderProgress.collect(viewLifecycleOwner) { | ||
173 | sliderBinding.slider.value = it.toFloat() | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | override fun onClick(dialog: DialogInterface, which: Int) { | ||
180 | when (settingsViewModel.clickedItem) { | ||
181 | is SingleChoiceSetting -> { | ||
182 | val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting | ||
183 | val value = getValueForSingleChoiceSelection(scSetting, which) | ||
184 | scSetting.setSelectedValue(value) | ||
185 | } | ||
186 | |||
187 | is StringSingleChoiceSetting -> { | ||
188 | val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting | ||
189 | val value = scSetting.getValueAt(which) | ||
190 | scSetting.setSelectedValue(value) | ||
191 | } | ||
192 | |||
193 | is IntSingleChoiceSetting -> { | ||
194 | val scSetting = settingsViewModel.clickedItem as IntSingleChoiceSetting | ||
195 | val value = scSetting.getValueAt(which) | ||
196 | scSetting.setSelectedValue(value) | ||
197 | } | ||
198 | |||
199 | is SliderSetting -> { | ||
200 | val sliderSetting = settingsViewModel.clickedItem as SliderSetting | ||
201 | sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value) | ||
202 | } | ||
203 | } | ||
204 | closeDialog() | ||
205 | } | ||
206 | |||
207 | private fun closeDialog() { | ||
208 | settingsViewModel.setAdapterItemChanged(position) | ||
209 | settingsViewModel.clickedItem = null | ||
210 | settingsViewModel.setSliderProgress(-1f) | ||
211 | dismiss() | ||
212 | } | ||
213 | |||
214 | private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { | ||
215 | val valuesId = item.valuesId | ||
216 | return if (valuesId > 0) { | ||
217 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
218 | valuesArray[which] | ||
219 | } else { | ||
220 | which | ||
221 | } | ||
222 | } | ||
223 | |||
224 | private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { | ||
225 | val value = item.getSelectedValue() | ||
226 | val valuesId = item.valuesId | ||
227 | if (valuesId > 0) { | ||
228 | val valuesArray = requireContext().resources.getIntArray(valuesId) | ||
229 | for (index in valuesArray.indices) { | ||
230 | val current = valuesArray[index] | ||
231 | if (current == value) { | ||
232 | return index | ||
233 | } | ||
234 | } | ||
235 | } else { | ||
236 | return value | ||
237 | } | ||
238 | return -1 | ||
239 | } | ||
240 | |||
241 | companion object { | ||
242 | const val TAG = "SettingsDialogFragment" | ||
243 | |||
244 | const val TYPE_RESET_SETTING = -1 | ||
245 | |||
246 | const val TITLE = "Title" | ||
247 | const val TYPE = "Type" | ||
248 | const val POSITION = "Position" | ||
249 | |||
250 | fun newInstance( | ||
251 | settingsViewModel: SettingsViewModel, | ||
252 | clickedItem: SettingsItem, | ||
253 | type: Int, | ||
254 | position: Int | ||
255 | ): SettingsDialogFragment { | ||
256 | when (type) { | ||
257 | SettingsItem.TYPE_HEADER, | ||
258 | SettingsItem.TYPE_SWITCH, | ||
259 | SettingsItem.TYPE_SUBMENU, | ||
260 | SettingsItem.TYPE_DATETIME_SETTING, | ||
261 | SettingsItem.TYPE_RUNNABLE -> | ||
262 | throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!") | ||
263 | |||
264 | SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress( | ||
265 | (clickedItem as SliderSetting).getSelectedValue().toFloat() | ||
266 | ) | ||
267 | } | ||
268 | settingsViewModel.clickedItem = clickedItem | ||
269 | |||
270 | val args = Bundle() | ||
271 | args.putInt(TYPE, type) | ||
272 | args.putInt(POSITION, position) | ||
273 | val fragment = SettingsDialogFragment() | ||
274 | fragment.arguments = args | ||
275 | return fragment | ||
276 | } | ||
277 | } | ||
278 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 6f6e7be10..ec16f16c4 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt | |||
@@ -13,20 +13,17 @@ import androidx.core.view.WindowInsetsCompat | |||
13 | import androidx.core.view.updatePadding | 13 | import androidx.core.view.updatePadding |
14 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
15 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
16 | import androidx.lifecycle.Lifecycle | ||
17 | import androidx.lifecycle.lifecycleScope | ||
18 | import androidx.lifecycle.repeatOnLifecycle | ||
19 | import androidx.navigation.findNavController | 16 | import androidx.navigation.findNavController |
20 | import androidx.navigation.fragment.navArgs | 17 | import androidx.navigation.fragment.navArgs |
21 | import androidx.recyclerview.widget.LinearLayoutManager | 18 | import androidx.recyclerview.widget.LinearLayoutManager |
22 | import com.google.android.material.transition.MaterialSharedAxis | 19 | import com.google.android.material.transition.MaterialSharedAxis |
23 | import kotlinx.coroutines.flow.collectLatest | ||
24 | import kotlinx.coroutines.launch | ||
25 | import org.yuzu.yuzu_emu.R | 20 | import org.yuzu.yuzu_emu.R |
26 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding | 21 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding |
22 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
27 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 23 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
28 | import org.yuzu.yuzu_emu.model.SettingsViewModel | 24 | import org.yuzu.yuzu_emu.fragments.MessageDialogFragment |
29 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 25 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
26 | import org.yuzu.yuzu_emu.utils.collect | ||
30 | 27 | ||
31 | class SettingsFragment : Fragment() { | 28 | class SettingsFragment : Fragment() { |
32 | private lateinit var presenter: SettingsFragmentPresenter | 29 | private lateinit var presenter: SettingsFragmentPresenter |
@@ -45,6 +42,12 @@ class SettingsFragment : Fragment() { | |||
45 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | 42 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
46 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | 43 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
47 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | 44 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
45 | |||
46 | val playerIndex = getPlayerIndex() | ||
47 | if (playerIndex != -1) { | ||
48 | NativeInput.loadInputProfiles() | ||
49 | NativeInput.reloadInputDevices() | ||
50 | } | ||
48 | } | 51 | } |
49 | 52 | ||
50 | override fun onCreateView( | 53 | override fun onCreateView( |
@@ -56,9 +59,9 @@ class SettingsFragment : Fragment() { | |||
56 | return binding.root | 59 | return binding.root |
57 | } | 60 | } |
58 | 61 | ||
59 | // This is using the correct scope, lint is just acting up | 62 | @SuppressLint("NotifyDataSetChanged") |
60 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
61 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 63 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
64 | super.onViewCreated(view, savedInstanceState) | ||
62 | settingsAdapter = SettingsAdapter(this, requireContext()) | 65 | settingsAdapter = SettingsAdapter(this, requireContext()) |
63 | presenter = SettingsFragmentPresenter( | 66 | presenter = SettingsFragmentPresenter( |
64 | settingsViewModel, | 67 | settingsViewModel, |
@@ -71,7 +74,17 @@ class SettingsFragment : Fragment() { | |||
71 | ) { | 74 | ) { |
72 | args.game!!.title | 75 | args.game!!.title |
73 | } else { | 76 | } else { |
74 | getString(args.menuTag.titleId) | 77 | when (args.menuTag) { |
78 | Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> Settings.getPlayerString(1) | ||
79 | Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> Settings.getPlayerString(2) | ||
80 | Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> Settings.getPlayerString(3) | ||
81 | Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> Settings.getPlayerString(4) | ||
82 | Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> Settings.getPlayerString(5) | ||
83 | Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> Settings.getPlayerString(6) | ||
84 | Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> Settings.getPlayerString(7) | ||
85 | Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> Settings.getPlayerString(8) | ||
86 | else -> getString(args.menuTag.titleId) | ||
87 | } | ||
75 | } | 88 | } |
76 | binding.listSettings.apply { | 89 | binding.listSettings.apply { |
77 | adapter = settingsAdapter | 90 | adapter = settingsAdapter |
@@ -82,16 +95,37 @@ class SettingsFragment : Fragment() { | |||
82 | settingsViewModel.setShouldNavigateBack(true) | 95 | settingsViewModel.setShouldNavigateBack(true) |
83 | } | 96 | } |
84 | 97 | ||
85 | viewLifecycleOwner.lifecycleScope.apply { | 98 | settingsViewModel.shouldReloadSettingsList.collect( |
86 | launch { | 99 | viewLifecycleOwner, |
87 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 100 | resetState = { settingsViewModel.setShouldReloadSettingsList(false) } |
88 | settingsViewModel.shouldReloadSettingsList.collectLatest { | 101 | ) { if (it) presenter.loadSettingsList() } |
89 | if (it) { | 102 | settingsViewModel.adapterItemChanged.collect( |
90 | settingsViewModel.setShouldReloadSettingsList(false) | 103 | viewLifecycleOwner, |
91 | presenter.loadSettingsList() | 104 | resetState = { settingsViewModel.setAdapterItemChanged(-1) } |
92 | } | 105 | ) { if (it != -1) settingsAdapter?.notifyItemChanged(it) } |
93 | } | 106 | settingsViewModel.datasetChanged.collect( |
94 | } | 107 | viewLifecycleOwner, |
108 | resetState = { settingsViewModel.setDatasetChanged(false) } | ||
109 | ) { if (it) settingsAdapter?.notifyDataSetChanged() } | ||
110 | settingsViewModel.reloadListAndNotifyDataset.collect( | ||
111 | viewLifecycleOwner, | ||
112 | resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) } | ||
113 | ) { if (it) presenter.loadSettingsList(true) } | ||
114 | settingsViewModel.shouldShowResetInputDialog.collect( | ||
115 | viewLifecycleOwner, | ||
116 | resetState = { settingsViewModel.setShouldShowResetInputDialog(false) } | ||
117 | ) { | ||
118 | if (it) { | ||
119 | MessageDialogFragment.newInstance( | ||
120 | activity = requireActivity(), | ||
121 | titleId = R.string.reset_mapping, | ||
122 | descriptionId = R.string.reset_mapping_description, | ||
123 | positiveAction = { | ||
124 | NativeInput.resetControllerMappings(getPlayerIndex()) | ||
125 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
126 | }, | ||
127 | negativeAction = {} | ||
128 | ).show(parentFragmentManager, MessageDialogFragment.TAG) | ||
95 | } | 129 | } |
96 | } | 130 | } |
97 | 131 | ||
@@ -115,6 +149,19 @@ class SettingsFragment : Fragment() { | |||
115 | setInsets() | 149 | setInsets() |
116 | } | 150 | } |
117 | 151 | ||
152 | private fun getPlayerIndex(): Int = | ||
153 | when (args.menuTag) { | ||
154 | Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> 0 | ||
155 | Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> 1 | ||
156 | Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> 2 | ||
157 | Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> 3 | ||
158 | Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> 4 | ||
159 | Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> 5 | ||
160 | Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> 6 | ||
161 | Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> 7 | ||
162 | else -> -1 | ||
163 | } | ||
164 | |||
118 | private fun setInsets() { | 165 | private fun setInsets() { |
119 | ViewCompat.setOnApplyWindowInsetsListener( | 166 | ViewCompat.setOnApplyWindowInsetsListener( |
120 | binding.root | 167 | binding.root |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index db1a58147..e491c29a2 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt | |||
@@ -3,11 +3,17 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.features.settings.ui | 4 | package org.yuzu.yuzu_emu.features.settings.ui |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
6 | import android.os.Build | 7 | import android.os.Build |
7 | import android.widget.Toast | 8 | import android.widget.Toast |
8 | import org.yuzu.yuzu_emu.NativeLibrary | 9 | import org.yuzu.yuzu_emu.NativeLibrary |
9 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
10 | import org.yuzu.yuzu_emu.YuzuApplication | 11 | import org.yuzu.yuzu_emu.YuzuApplication |
12 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
13 | import org.yuzu.yuzu_emu.features.input.model.AnalogDirection | ||
14 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
15 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
16 | import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex | ||
11 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 17 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 18 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
13 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 19 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
@@ -15,18 +21,21 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting | |||
15 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 21 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
16 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting | 22 | import org.yuzu.yuzu_emu.features.settings.model.LongSetting |
17 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 23 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
24 | import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag | ||
18 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting | 25 | import org.yuzu.yuzu_emu.features.settings.model.ShortSetting |
19 | import org.yuzu.yuzu_emu.features.settings.model.view.* | 26 | import org.yuzu.yuzu_emu.features.settings.model.view.* |
20 | import org.yuzu.yuzu_emu.model.SettingsViewModel | 27 | import org.yuzu.yuzu_emu.utils.InputHandler |
21 | import org.yuzu.yuzu_emu.utils.NativeConfig | 28 | import org.yuzu.yuzu_emu.utils.NativeConfig |
22 | 29 | ||
23 | class SettingsFragmentPresenter( | 30 | class SettingsFragmentPresenter( |
24 | private val settingsViewModel: SettingsViewModel, | 31 | private val settingsViewModel: SettingsViewModel, |
25 | private val adapter: SettingsAdapter, | 32 | private val adapter: SettingsAdapter, |
26 | private var menuTag: Settings.MenuTag | 33 | private var menuTag: MenuTag |
27 | ) { | 34 | ) { |
28 | private var settingsList = ArrayList<SettingsItem>() | 35 | private var settingsList = ArrayList<SettingsItem>() |
29 | 36 | ||
37 | private val context get() = YuzuApplication.appContext | ||
38 | |||
30 | // Extension for altering settings list based on each setting's properties | 39 | // Extension for altering settings list based on each setting's properties |
31 | fun ArrayList<SettingsItem>.add(key: String) { | 40 | fun ArrayList<SettingsItem>.add(key: String) { |
32 | val item = SettingsItem.settingsItems[key]!! | 41 | val item = SettingsItem.settingsItems[key]!! |
@@ -53,73 +62,90 @@ class SettingsFragmentPresenter( | |||
53 | add(item) | 62 | add(item) |
54 | } | 63 | } |
55 | 64 | ||
65 | // Allows you to show/hide abstract settings based on the paired setting key | ||
66 | fun ArrayList<SettingsItem>.addAbstract(item: SettingsItem) { | ||
67 | val pairedSettingKey = item.setting.pairedSettingKey | ||
68 | if (pairedSettingKey.isNotEmpty()) { | ||
69 | val pairedSettingsItem = | ||
70 | this.firstOrNull { it.setting.key == pairedSettingKey } ?: return | ||
71 | val pairedSetting = pairedSettingsItem.setting as AbstractBooleanSetting | ||
72 | if (!pairedSetting.getBoolean(!NativeConfig.isPerGameConfigLoaded())) return | ||
73 | } | ||
74 | add(item) | ||
75 | } | ||
76 | |||
56 | fun onViewCreated() { | 77 | fun onViewCreated() { |
57 | loadSettingsList() | 78 | loadSettingsList() |
58 | } | 79 | } |
59 | 80 | ||
60 | fun loadSettingsList() { | 81 | @SuppressLint("NotifyDataSetChanged") |
82 | fun loadSettingsList(notifyDataSetChanged: Boolean = false) { | ||
61 | val sl = ArrayList<SettingsItem>() | 83 | val sl = ArrayList<SettingsItem>() |
62 | when (menuTag) { | 84 | when (menuTag) { |
63 | Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl) | 85 | MenuTag.SECTION_ROOT -> addConfigSettings(sl) |
64 | Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) | 86 | MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) |
65 | Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) | 87 | MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) |
66 | Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl) | 88 | MenuTag.SECTION_AUDIO -> addAudioSettings(sl) |
67 | Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl) | 89 | MenuTag.SECTION_INPUT -> addInputSettings(sl) |
68 | Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl) | 90 | MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0) |
69 | else -> { | 91 | MenuTag.SECTION_INPUT_PLAYER_TWO -> addInputPlayer(sl, 1) |
70 | val context = YuzuApplication.appContext | 92 | MenuTag.SECTION_INPUT_PLAYER_THREE -> addInputPlayer(sl, 2) |
71 | Toast.makeText( | 93 | MenuTag.SECTION_INPUT_PLAYER_FOUR -> addInputPlayer(sl, 3) |
72 | context, | 94 | MenuTag.SECTION_INPUT_PLAYER_FIVE -> addInputPlayer(sl, 4) |
73 | context.getString(R.string.unimplemented_menu), | 95 | MenuTag.SECTION_INPUT_PLAYER_SIX -> addInputPlayer(sl, 5) |
74 | Toast.LENGTH_SHORT | 96 | MenuTag.SECTION_INPUT_PLAYER_SEVEN -> addInputPlayer(sl, 6) |
75 | ).show() | 97 | MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7) |
76 | return | 98 | MenuTag.SECTION_THEME -> addThemeSettings(sl) |
77 | } | 99 | MenuTag.SECTION_DEBUG -> addDebugSettings(sl) |
78 | } | 100 | } |
79 | settingsList = sl | 101 | settingsList = sl |
80 | adapter.submitList(settingsList) | 102 | adapter.submitList(settingsList) { |
103 | if (notifyDataSetChanged) { | ||
104 | adapter.notifyDataSetChanged() | ||
105 | } | ||
106 | } | ||
81 | } | 107 | } |
82 | 108 | ||
83 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { | 109 | private fun addConfigSettings(sl: ArrayList<SettingsItem>) { |
84 | sl.apply { | 110 | sl.apply { |
85 | add( | 111 | add( |
86 | SubmenuSetting( | 112 | SubmenuSetting( |
87 | R.string.preferences_system, | 113 | titleId = R.string.preferences_system, |
88 | R.string.preferences_system_description, | 114 | descriptionId = R.string.preferences_system_description, |
89 | R.drawable.ic_system_settings, | 115 | iconId = R.drawable.ic_system_settings, |
90 | Settings.MenuTag.SECTION_SYSTEM | 116 | menuKey = MenuTag.SECTION_SYSTEM |
91 | ) | 117 | ) |
92 | ) | 118 | ) |
93 | add( | 119 | add( |
94 | SubmenuSetting( | 120 | SubmenuSetting( |
95 | R.string.preferences_graphics, | 121 | titleId = R.string.preferences_graphics, |
96 | R.string.preferences_graphics_description, | 122 | descriptionId = R.string.preferences_graphics_description, |
97 | R.drawable.ic_graphics, | 123 | iconId = R.drawable.ic_graphics, |
98 | Settings.MenuTag.SECTION_RENDERER | 124 | menuKey = MenuTag.SECTION_RENDERER |
99 | ) | 125 | ) |
100 | ) | 126 | ) |
101 | add( | 127 | add( |
102 | SubmenuSetting( | 128 | SubmenuSetting( |
103 | R.string.preferences_audio, | 129 | titleId = R.string.preferences_audio, |
104 | R.string.preferences_audio_description, | 130 | descriptionId = R.string.preferences_audio_description, |
105 | R.drawable.ic_audio, | 131 | iconId = R.drawable.ic_audio, |
106 | Settings.MenuTag.SECTION_AUDIO | 132 | menuKey = MenuTag.SECTION_AUDIO |
107 | ) | 133 | ) |
108 | ) | 134 | ) |
109 | add( | 135 | add( |
110 | SubmenuSetting( | 136 | SubmenuSetting( |
111 | R.string.preferences_debug, | 137 | titleId = R.string.preferences_debug, |
112 | R.string.preferences_debug_description, | 138 | descriptionId = R.string.preferences_debug_description, |
113 | R.drawable.ic_code, | 139 | iconId = R.drawable.ic_code, |
114 | Settings.MenuTag.SECTION_DEBUG | 140 | menuKey = MenuTag.SECTION_DEBUG |
115 | ) | 141 | ) |
116 | ) | 142 | ) |
117 | add( | 143 | add( |
118 | RunnableSetting( | 144 | RunnableSetting( |
119 | R.string.reset_to_default, | 145 | titleId = R.string.reset_to_default, |
120 | R.string.reset_to_default_description, | 146 | descriptionId = R.string.reset_to_default_description, |
121 | false, | 147 | isRunnable = !NativeLibrary.isRunning(), |
122 | R.drawable.ic_restore | 148 | iconId = R.drawable.ic_restore |
123 | ) { settingsViewModel.setShouldShowResetSettingsDialog(true) } | 149 | ) { settingsViewModel.setShouldShowResetSettingsDialog(true) } |
124 | ) | 150 | ) |
125 | } | 151 | } |
@@ -164,6 +190,671 @@ class SettingsFragmentPresenter( | |||
164 | } | 190 | } |
165 | } | 191 | } |
166 | 192 | ||
193 | private fun addInputSettings(sl: ArrayList<SettingsItem>) { | ||
194 | settingsViewModel.currentDevice = 0 | ||
195 | |||
196 | if (NativeConfig.isPerGameConfigLoaded()) { | ||
197 | NativeInput.loadInputProfiles() | ||
198 | val profiles = NativeInput.getInputProfileNames().toMutableList() | ||
199 | profiles.add(0, "") | ||
200 | val prettyProfiles = profiles.toTypedArray() | ||
201 | prettyProfiles[0] = | ||
202 | context.getString(R.string.use_global_input_configuration) | ||
203 | sl.apply { | ||
204 | for (i in 0 until 8) { | ||
205 | add( | ||
206 | IntSingleChoiceSetting( | ||
207 | getPerGameProfileSetting(profiles, i), | ||
208 | titleString = getPlayerProfileString(i + 1), | ||
209 | choices = prettyProfiles, | ||
210 | values = IntArray(profiles.size) { it }.toTypedArray() | ||
211 | ) | ||
212 | ) | ||
213 | } | ||
214 | } | ||
215 | return | ||
216 | } | ||
217 | |||
218 | val getConnectedIcon: (Int) -> Int = { playerIndex: Int -> | ||
219 | if (NativeInput.getIsConnected(playerIndex)) { | ||
220 | R.drawable.ic_controller | ||
221 | } else { | ||
222 | R.drawable.ic_controller_disconnected | ||
223 | } | ||
224 | } | ||
225 | |||
226 | val inputSettings = NativeConfig.getInputSettings(true) | ||
227 | sl.apply { | ||
228 | add( | ||
229 | SubmenuSetting( | ||
230 | titleString = Settings.getPlayerString(1), | ||
231 | descriptionString = inputSettings[0].profileName, | ||
232 | menuKey = MenuTag.SECTION_INPUT_PLAYER_ONE, | ||
233 | iconId = getConnectedIcon(0) | ||
234 | ) | ||
235 | ) | ||
236 | add( | ||
237 | SubmenuSetting( | ||
238 | titleString = Settings.getPlayerString(2), | ||
239 | descriptionString = inputSettings[1].profileName, | ||
240 | menuKey = MenuTag.SECTION_INPUT_PLAYER_TWO, | ||
241 | iconId = getConnectedIcon(1) | ||
242 | ) | ||
243 | ) | ||
244 | add( | ||
245 | SubmenuSetting( | ||
246 | titleString = Settings.getPlayerString(3), | ||
247 | descriptionString = inputSettings[2].profileName, | ||
248 | menuKey = MenuTag.SECTION_INPUT_PLAYER_THREE, | ||
249 | iconId = getConnectedIcon(2) | ||
250 | ) | ||
251 | ) | ||
252 | add( | ||
253 | SubmenuSetting( | ||
254 | titleString = Settings.getPlayerString(4), | ||
255 | descriptionString = inputSettings[3].profileName, | ||
256 | menuKey = MenuTag.SECTION_INPUT_PLAYER_FOUR, | ||
257 | iconId = getConnectedIcon(3) | ||
258 | ) | ||
259 | ) | ||
260 | add( | ||
261 | SubmenuSetting( | ||
262 | titleString = Settings.getPlayerString(5), | ||
263 | descriptionString = inputSettings[4].profileName, | ||
264 | menuKey = MenuTag.SECTION_INPUT_PLAYER_FIVE, | ||
265 | iconId = getConnectedIcon(4) | ||
266 | ) | ||
267 | ) | ||
268 | add( | ||
269 | SubmenuSetting( | ||
270 | titleString = Settings.getPlayerString(6), | ||
271 | descriptionString = inputSettings[5].profileName, | ||
272 | menuKey = MenuTag.SECTION_INPUT_PLAYER_SIX, | ||
273 | iconId = getConnectedIcon(5) | ||
274 | ) | ||
275 | ) | ||
276 | add( | ||
277 | SubmenuSetting( | ||
278 | titleString = Settings.getPlayerString(7), | ||
279 | descriptionString = inputSettings[6].profileName, | ||
280 | menuKey = MenuTag.SECTION_INPUT_PLAYER_SEVEN, | ||
281 | iconId = getConnectedIcon(6) | ||
282 | ) | ||
283 | ) | ||
284 | add( | ||
285 | SubmenuSetting( | ||
286 | titleString = Settings.getPlayerString(8), | ||
287 | descriptionString = inputSettings[7].profileName, | ||
288 | menuKey = MenuTag.SECTION_INPUT_PLAYER_EIGHT, | ||
289 | iconId = getConnectedIcon(7) | ||
290 | ) | ||
291 | ) | ||
292 | } | ||
293 | } | ||
294 | |||
295 | private fun getPlayerProfileString(player: Int): String = | ||
296 | context.getString(R.string.player_num_profile, player) | ||
297 | |||
298 | private fun getPerGameProfileSetting( | ||
299 | profiles: List<String>, | ||
300 | playerIndex: Int | ||
301 | ): AbstractIntSetting { | ||
302 | return object : AbstractIntSetting { | ||
303 | private val players | ||
304 | get() = NativeConfig.getInputSettings(false) | ||
305 | |||
306 | override val key = "" | ||
307 | |||
308 | override fun getInt(needsGlobal: Boolean): Int { | ||
309 | val currentProfile = players[playerIndex].profileName | ||
310 | profiles.forEachIndexed { i, profile -> | ||
311 | if (profile == currentProfile) { | ||
312 | return i | ||
313 | } | ||
314 | } | ||
315 | return 0 | ||
316 | } | ||
317 | |||
318 | override fun setInt(value: Int) { | ||
319 | NativeInput.loadPerGameConfiguration(playerIndex, value, profiles[value]) | ||
320 | NativeInput.connectControllers(playerIndex) | ||
321 | NativeConfig.saveControlPlayerValues() | ||
322 | } | ||
323 | |||
324 | override val defaultValue = 0 | ||
325 | |||
326 | override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() | ||
327 | |||
328 | override fun reset() = setInt(defaultValue) | ||
329 | |||
330 | override var global = true | ||
331 | |||
332 | override val isRuntimeModifiable = true | ||
333 | |||
334 | override val isSaveable = true | ||
335 | } | ||
336 | } | ||
337 | |||
338 | private fun addInputPlayer(sl: ArrayList<SettingsItem>, playerIndex: Int) { | ||
339 | sl.apply { | ||
340 | val connectedSetting = object : AbstractBooleanSetting { | ||
341 | override val key = "connected" | ||
342 | |||
343 | override fun getBoolean(needsGlobal: Boolean): Boolean = | ||
344 | NativeInput.getIsConnected(playerIndex) | ||
345 | |||
346 | override fun setBoolean(value: Boolean) = | ||
347 | NativeInput.connectControllers(playerIndex, value) | ||
348 | |||
349 | override val defaultValue = playerIndex == 0 | ||
350 | |||
351 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
352 | getBoolean(needsGlobal).toString() | ||
353 | |||
354 | override fun reset() = setBoolean(defaultValue) | ||
355 | } | ||
356 | add(SwitchSetting(connectedSetting, R.string.connected)) | ||
357 | |||
358 | val styleTags = NativeInput.getSupportedStyleTags(playerIndex) | ||
359 | val npadType = object : AbstractIntSetting { | ||
360 | override val key = "npad_type" | ||
361 | override fun getInt(needsGlobal: Boolean): Int { | ||
362 | val styleIndex = NativeInput.getStyleIndex(playerIndex) | ||
363 | return styleTags.indexOfFirst { it == styleIndex } | ||
364 | } | ||
365 | |||
366 | override fun setInt(value: Int) { | ||
367 | NativeInput.setStyleIndex(playerIndex, styleTags[value]) | ||
368 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
369 | } | ||
370 | |||
371 | override val defaultValue = NpadStyleIndex.Fullkey.int | ||
372 | override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() | ||
373 | override fun reset() = setInt(defaultValue) | ||
374 | override val pairedSettingKey: String = "connected" | ||
375 | } | ||
376 | addAbstract( | ||
377 | IntSingleChoiceSetting( | ||
378 | npadType, | ||
379 | titleId = R.string.controller_type, | ||
380 | choices = styleTags.map { context.getString(it.nameId) } | ||
381 | .toTypedArray(), | ||
382 | values = IntArray(styleTags.size) { it }.toTypedArray() | ||
383 | ) | ||
384 | ) | ||
385 | |||
386 | InputHandler.updateControllerData() | ||
387 | |||
388 | val autoMappingSetting = object : AbstractIntSetting { | ||
389 | override val key = "auto_mapping_device" | ||
390 | |||
391 | override fun getInt(needsGlobal: Boolean): Int = -1 | ||
392 | |||
393 | override fun setInt(value: Int) { | ||
394 | val registeredController = InputHandler.registeredControllers[value + 1] | ||
395 | val displayName = registeredController.get( | ||
396 | "display", | ||
397 | context.getString(R.string.unknown) | ||
398 | ) | ||
399 | NativeInput.updateMappingsWithDefault( | ||
400 | playerIndex, | ||
401 | registeredController, | ||
402 | displayName | ||
403 | ) | ||
404 | Toast.makeText( | ||
405 | context, | ||
406 | context.getString(R.string.attempted_auto_map, displayName), | ||
407 | Toast.LENGTH_SHORT | ||
408 | ).show() | ||
409 | settingsViewModel.setReloadListAndNotifyDataset(true) | ||
410 | } | ||
411 | |||
412 | override val defaultValue = -1 | ||
413 | |||
414 | override fun getValueAsString(needsGlobal: Boolean) = getInt().toString() | ||
415 | |||
416 | override fun reset() = setInt(defaultValue) | ||
417 | |||
418 | override val isRuntimeModifiable: Boolean = true | ||
419 | } | ||
420 | |||
421 | val unknownString = context.getString(R.string.unknown) | ||
422 | val prettyAutoMappingControllerList = InputHandler.registeredControllers.mapNotNull { | ||
423 | val port = it.get("port", -1) | ||
424 | return@mapNotNull if (port == 100 || port == -1) { | ||
425 | null | ||
426 | } else { | ||
427 | it.get("display", unknownString) | ||
428 | } | ||
429 | }.toTypedArray() | ||
430 | add( | ||
431 | IntSingleChoiceSetting( | ||
432 | autoMappingSetting, | ||
433 | titleId = R.string.auto_map, | ||
434 | descriptionId = R.string.auto_map_description, | ||
435 | choices = prettyAutoMappingControllerList, | ||
436 | values = IntArray(prettyAutoMappingControllerList.size) { it }.toTypedArray() | ||
437 | ) | ||
438 | ) | ||
439 | |||
440 | val mappingFilterSetting = object : AbstractIntSetting { | ||
441 | override val key = "mapping_filter" | ||
442 | |||
443 | override fun getInt(needsGlobal: Boolean): Int = settingsViewModel.currentDevice | ||
444 | |||
445 | override fun setInt(value: Int) { | ||
446 | settingsViewModel.currentDevice = value | ||
447 | } | ||
448 | |||
449 | override val defaultValue = 0 | ||
450 | |||
451 | override fun getValueAsString(needsGlobal: Boolean) = getInt().toString() | ||
452 | |||
453 | override fun reset() = setInt(defaultValue) | ||
454 | |||
455 | override val isRuntimeModifiable: Boolean = true | ||
456 | } | ||
457 | |||
458 | val prettyControllerList = InputHandler.registeredControllers.mapNotNull { | ||
459 | return@mapNotNull if (it.get("port", 0) == 100) { | ||
460 | null | ||
461 | } else { | ||
462 | it.get("display", unknownString) | ||
463 | } | ||
464 | }.toTypedArray() | ||
465 | add( | ||
466 | IntSingleChoiceSetting( | ||
467 | mappingFilterSetting, | ||
468 | titleId = R.string.input_mapping_filter, | ||
469 | descriptionId = R.string.input_mapping_filter_description, | ||
470 | choices = prettyControllerList, | ||
471 | values = IntArray(prettyControllerList.size) { it }.toTypedArray() | ||
472 | ) | ||
473 | ) | ||
474 | |||
475 | add(InputProfileSetting(playerIndex)) | ||
476 | add( | ||
477 | RunnableSetting(titleId = R.string.reset_to_default, isRunnable = true) { | ||
478 | settingsViewModel.setShouldShowResetInputDialog(true) | ||
479 | } | ||
480 | ) | ||
481 | |||
482 | val styleIndex = NativeInput.getStyleIndex(playerIndex) | ||
483 | |||
484 | // Buttons | ||
485 | when (styleIndex) { | ||
486 | NpadStyleIndex.Fullkey, | ||
487 | NpadStyleIndex.Handheld, | ||
488 | NpadStyleIndex.JoyconDual -> { | ||
489 | add(HeaderSetting(R.string.buttons)) | ||
490 | add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) | ||
491 | add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) | ||
492 | add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) | ||
493 | add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) | ||
494 | add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus)) | ||
495 | add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus)) | ||
496 | add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home)) | ||
497 | add( | ||
498 | ButtonInputSetting( | ||
499 | playerIndex, | ||
500 | NativeButton.Capture, | ||
501 | R.string.button_capture | ||
502 | ) | ||
503 | ) | ||
504 | } | ||
505 | |||
506 | NpadStyleIndex.JoyconLeft -> { | ||
507 | add(HeaderSetting(R.string.buttons)) | ||
508 | add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus)) | ||
509 | add( | ||
510 | ButtonInputSetting( | ||
511 | playerIndex, | ||
512 | NativeButton.Capture, | ||
513 | R.string.button_capture | ||
514 | ) | ||
515 | ) | ||
516 | } | ||
517 | |||
518 | NpadStyleIndex.JoyconRight -> { | ||
519 | add(HeaderSetting(R.string.buttons)) | ||
520 | add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) | ||
521 | add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) | ||
522 | add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) | ||
523 | add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) | ||
524 | add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus)) | ||
525 | add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home)) | ||
526 | } | ||
527 | |||
528 | NpadStyleIndex.GameCube -> { | ||
529 | add(HeaderSetting(R.string.buttons)) | ||
530 | add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a)) | ||
531 | add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b)) | ||
532 | add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x)) | ||
533 | add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y)) | ||
534 | add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.start_pause)) | ||
535 | } | ||
536 | |||
537 | else -> { | ||
538 | // No-op | ||
539 | } | ||
540 | } | ||
541 | |||
542 | when (styleIndex) { | ||
543 | NpadStyleIndex.Fullkey, | ||
544 | NpadStyleIndex.Handheld, | ||
545 | NpadStyleIndex.JoyconDual, | ||
546 | NpadStyleIndex.JoyconLeft -> { | ||
547 | add(HeaderSetting(R.string.dpad)) | ||
548 | add(ButtonInputSetting(playerIndex, NativeButton.DUp, R.string.up)) | ||
549 | add(ButtonInputSetting(playerIndex, NativeButton.DDown, R.string.down)) | ||
550 | add(ButtonInputSetting(playerIndex, NativeButton.DLeft, R.string.left)) | ||
551 | add(ButtonInputSetting(playerIndex, NativeButton.DRight, R.string.right)) | ||
552 | } | ||
553 | |||
554 | else -> { | ||
555 | // No-op | ||
556 | } | ||
557 | } | ||
558 | |||
559 | // Left stick | ||
560 | when (styleIndex) { | ||
561 | NpadStyleIndex.Fullkey, | ||
562 | NpadStyleIndex.Handheld, | ||
563 | NpadStyleIndex.JoyconDual, | ||
564 | NpadStyleIndex.JoyconLeft -> { | ||
565 | add(HeaderSetting(R.string.left_stick)) | ||
566 | addAll(getStickDirections(playerIndex, NativeAnalog.LStick)) | ||
567 | add(ButtonInputSetting(playerIndex, NativeButton.LStick, R.string.pressed)) | ||
568 | addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick)) | ||
569 | } | ||
570 | |||
571 | NpadStyleIndex.GameCube -> { | ||
572 | add(HeaderSetting(R.string.control_stick)) | ||
573 | addAll(getStickDirections(playerIndex, NativeAnalog.LStick)) | ||
574 | addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick)) | ||
575 | } | ||
576 | |||
577 | else -> { | ||
578 | // No-op | ||
579 | } | ||
580 | } | ||
581 | |||
582 | // Right stick | ||
583 | when (styleIndex) { | ||
584 | NpadStyleIndex.Fullkey, | ||
585 | NpadStyleIndex.Handheld, | ||
586 | NpadStyleIndex.JoyconDual, | ||
587 | NpadStyleIndex.JoyconRight -> { | ||
588 | add(HeaderSetting(R.string.right_stick)) | ||
589 | addAll(getStickDirections(playerIndex, NativeAnalog.RStick)) | ||
590 | add(ButtonInputSetting(playerIndex, NativeButton.RStick, R.string.pressed)) | ||
591 | addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick)) | ||
592 | } | ||
593 | |||
594 | NpadStyleIndex.GameCube -> { | ||
595 | add(HeaderSetting(R.string.c_stick)) | ||
596 | addAll(getStickDirections(playerIndex, NativeAnalog.RStick)) | ||
597 | addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick)) | ||
598 | } | ||
599 | |||
600 | else -> { | ||
601 | // No-op | ||
602 | } | ||
603 | } | ||
604 | |||
605 | // L/R, ZL/ZR, and SL/SR | ||
606 | when (styleIndex) { | ||
607 | NpadStyleIndex.Fullkey, | ||
608 | NpadStyleIndex.Handheld -> { | ||
609 | add(HeaderSetting(R.string.triggers)) | ||
610 | add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) | ||
611 | add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) | ||
612 | add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) | ||
613 | add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) | ||
614 | } | ||
615 | |||
616 | NpadStyleIndex.JoyconDual -> { | ||
617 | add(HeaderSetting(R.string.triggers)) | ||
618 | add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) | ||
619 | add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) | ||
620 | add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) | ||
621 | add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) | ||
622 | add( | ||
623 | ButtonInputSetting( | ||
624 | playerIndex, | ||
625 | NativeButton.SLLeft, | ||
626 | R.string.button_sl_left | ||
627 | ) | ||
628 | ) | ||
629 | add( | ||
630 | ButtonInputSetting( | ||
631 | playerIndex, | ||
632 | NativeButton.SRLeft, | ||
633 | R.string.button_sr_left | ||
634 | ) | ||
635 | ) | ||
636 | add( | ||
637 | ButtonInputSetting( | ||
638 | playerIndex, | ||
639 | NativeButton.SLRight, | ||
640 | R.string.button_sl_right | ||
641 | ) | ||
642 | ) | ||
643 | add( | ||
644 | ButtonInputSetting( | ||
645 | playerIndex, | ||
646 | NativeButton.SRRight, | ||
647 | R.string.button_sr_right | ||
648 | ) | ||
649 | ) | ||
650 | } | ||
651 | |||
652 | NpadStyleIndex.JoyconLeft -> { | ||
653 | add(HeaderSetting(R.string.triggers)) | ||
654 | add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l)) | ||
655 | add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl)) | ||
656 | add( | ||
657 | ButtonInputSetting( | ||
658 | playerIndex, | ||
659 | NativeButton.SLLeft, | ||
660 | R.string.button_sl_left | ||
661 | ) | ||
662 | ) | ||
663 | add( | ||
664 | ButtonInputSetting( | ||
665 | playerIndex, | ||
666 | NativeButton.SRLeft, | ||
667 | R.string.button_sr_left | ||
668 | ) | ||
669 | ) | ||
670 | } | ||
671 | |||
672 | NpadStyleIndex.JoyconRight -> { | ||
673 | add(HeaderSetting(R.string.triggers)) | ||
674 | add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r)) | ||
675 | add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr)) | ||
676 | add( | ||
677 | ButtonInputSetting( | ||
678 | playerIndex, | ||
679 | NativeButton.SLRight, | ||
680 | R.string.button_sl_right | ||
681 | ) | ||
682 | ) | ||
683 | add( | ||
684 | ButtonInputSetting( | ||
685 | playerIndex, | ||
686 | NativeButton.SRRight, | ||
687 | R.string.button_sr_right | ||
688 | ) | ||
689 | ) | ||
690 | } | ||
691 | |||
692 | NpadStyleIndex.GameCube -> { | ||
693 | add(HeaderSetting(R.string.triggers)) | ||
694 | add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_z)) | ||
695 | add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_l)) | ||
696 | add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_r)) | ||
697 | } | ||
698 | |||
699 | else -> { | ||
700 | // No-op | ||
701 | } | ||
702 | } | ||
703 | |||
704 | add(HeaderSetting(R.string.vibration)) | ||
705 | val vibrationEnabledSetting = object : AbstractBooleanSetting { | ||
706 | override val key = "vibration" | ||
707 | |||
708 | override fun getBoolean(needsGlobal: Boolean): Boolean = | ||
709 | NativeConfig.getInputSettings(true)[playerIndex].vibrationEnabled | ||
710 | |||
711 | override fun setBoolean(value: Boolean) { | ||
712 | val settings = NativeConfig.getInputSettings(true) | ||
713 | settings[playerIndex].vibrationEnabled = value | ||
714 | NativeConfig.setInputSettings(settings, true) | ||
715 | } | ||
716 | |||
717 | override val defaultValue = true | ||
718 | |||
719 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
720 | getBoolean(needsGlobal).toString() | ||
721 | |||
722 | override fun reset() = setBoolean(defaultValue) | ||
723 | } | ||
724 | add(SwitchSetting(vibrationEnabledSetting, R.string.vibration)) | ||
725 | |||
726 | val useSystemVibratorSetting = object : AbstractBooleanSetting { | ||
727 | override val key = "" | ||
728 | |||
729 | override fun getBoolean(needsGlobal: Boolean): Boolean = | ||
730 | NativeConfig.getInputSettings(true)[playerIndex].useSystemVibrator | ||
731 | |||
732 | override fun setBoolean(value: Boolean) { | ||
733 | val settings = NativeConfig.getInputSettings(true) | ||
734 | settings[playerIndex].useSystemVibrator = value | ||
735 | NativeConfig.setInputSettings(settings, true) | ||
736 | } | ||
737 | |||
738 | override val defaultValue = playerIndex == 0 | ||
739 | |||
740 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
741 | getBoolean(needsGlobal).toString() | ||
742 | |||
743 | override fun reset() = setBoolean(defaultValue) | ||
744 | |||
745 | override val pairedSettingKey: String = "vibration" | ||
746 | } | ||
747 | addAbstract(SwitchSetting(useSystemVibratorSetting, R.string.use_system_vibrator)) | ||
748 | |||
749 | val vibrationStrengthSetting = object : AbstractIntSetting { | ||
750 | override val key = "" | ||
751 | |||
752 | override fun getInt(needsGlobal: Boolean): Int = | ||
753 | NativeConfig.getInputSettings(true)[playerIndex].vibrationStrength | ||
754 | |||
755 | override fun setInt(value: Int) { | ||
756 | val settings = NativeConfig.getInputSettings(true) | ||
757 | settings[playerIndex].vibrationStrength = value | ||
758 | NativeConfig.setInputSettings(settings, true) | ||
759 | } | ||
760 | |||
761 | override val defaultValue = 100 | ||
762 | |||
763 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
764 | getInt(needsGlobal).toString() | ||
765 | |||
766 | override fun reset() = setInt(defaultValue) | ||
767 | |||
768 | override val pairedSettingKey: String = "vibration" | ||
769 | } | ||
770 | addAbstract( | ||
771 | SliderSetting(vibrationStrengthSetting, R.string.vibration_strength, units = "%") | ||
772 | ) | ||
773 | } | ||
774 | } | ||
775 | |||
776 | // Convenience function for creating AbstractIntSettings for modifier range/stick range/stick deadzones | ||
777 | private fun getStickIntSettingFromParam( | ||
778 | playerIndex: Int, | ||
779 | paramName: String, | ||
780 | stick: NativeAnalog, | ||
781 | defaultValue: Int | ||
782 | ): AbstractIntSetting = | ||
783 | object : AbstractIntSetting { | ||
784 | val params get() = NativeInput.getStickParam(playerIndex, stick) | ||
785 | |||
786 | override val key = "" | ||
787 | |||
788 | override fun getInt(needsGlobal: Boolean): Int = | ||
789 | (params.get(paramName, 0.15f) * 100).toInt() | ||
790 | |||
791 | override fun setInt(value: Int) { | ||
792 | val tempParams = params | ||
793 | tempParams.set(paramName, value.toFloat() / 100) | ||
794 | NativeInput.setStickParam(playerIndex, stick, tempParams) | ||
795 | } | ||
796 | |||
797 | override val defaultValue = defaultValue | ||
798 | |||
799 | override fun getValueAsString(needsGlobal: Boolean): String = | ||
800 | getInt(needsGlobal).toString() | ||
801 | |||
802 | override fun reset() = setInt(defaultValue) | ||
803 | } | ||
804 | |||
805 | private fun getExtraStickSettings( | ||
806 | playerIndex: Int, | ||
807 | nativeAnalog: NativeAnalog | ||
808 | ): List<SettingsItem> { | ||
809 | val stickIsController = | ||
810 | NativeInput.isController(NativeInput.getStickParam(playerIndex, nativeAnalog)) | ||
811 | val modifierRangeSetting = | ||
812 | getStickIntSettingFromParam(playerIndex, "modifier_scale", nativeAnalog, 50) | ||
813 | val stickRangeSetting = | ||
814 | getStickIntSettingFromParam(playerIndex, "range", nativeAnalog, 95) | ||
815 | val stickDeadzoneSetting = | ||
816 | getStickIntSettingFromParam(playerIndex, "deadzone", nativeAnalog, 15) | ||
817 | |||
818 | val out = mutableListOf<SettingsItem>().apply { | ||
819 | if (stickIsController) { | ||
820 | add(SliderSetting(stickRangeSetting, titleId = R.string.range, min = 25, max = 150)) | ||
821 | add(SliderSetting(stickDeadzoneSetting, R.string.deadzone)) | ||
822 | } else { | ||
823 | add(ModifierInputSetting(playerIndex, NativeAnalog.LStick, R.string.modifier)) | ||
824 | add(SliderSetting(modifierRangeSetting, R.string.modifier_range)) | ||
825 | } | ||
826 | } | ||
827 | return out | ||
828 | } | ||
829 | |||
830 | private fun getStickDirections(player: Int, stick: NativeAnalog): List<AnalogInputSetting> = | ||
831 | listOf( | ||
832 | AnalogInputSetting( | ||
833 | player, | ||
834 | stick, | ||
835 | AnalogDirection.Up, | ||
836 | R.string.up | ||
837 | ), | ||
838 | AnalogInputSetting( | ||
839 | player, | ||
840 | stick, | ||
841 | AnalogDirection.Down, | ||
842 | R.string.down | ||
843 | ), | ||
844 | AnalogInputSetting( | ||
845 | player, | ||
846 | stick, | ||
847 | AnalogDirection.Left, | ||
848 | R.string.left | ||
849 | ), | ||
850 | AnalogInputSetting( | ||
851 | player, | ||
852 | stick, | ||
853 | AnalogDirection.Right, | ||
854 | R.string.right | ||
855 | ) | ||
856 | ) | ||
857 | |||
167 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { | 858 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { |
168 | sl.apply { | 859 | sl.apply { |
169 | val theme: AbstractIntSetting = object : AbstractIntSetting { | 860 | val theme: AbstractIntSetting = object : AbstractIntSetting { |
@@ -186,20 +877,18 @@ class SettingsFragmentPresenter( | |||
186 | add( | 877 | add( |
187 | SingleChoiceSetting( | 878 | SingleChoiceSetting( |
188 | theme, | 879 | theme, |
189 | R.string.change_app_theme, | 880 | titleId = R.string.change_app_theme, |
190 | 0, | 881 | choicesId = R.array.themeEntriesA12, |
191 | R.array.themeEntriesA12, | 882 | valuesId = R.array.themeValuesA12 |
192 | R.array.themeValuesA12 | ||
193 | ) | 883 | ) |
194 | ) | 884 | ) |
195 | } else { | 885 | } else { |
196 | add( | 886 | add( |
197 | SingleChoiceSetting( | 887 | SingleChoiceSetting( |
198 | theme, | 888 | theme, |
199 | R.string.change_app_theme, | 889 | titleId = R.string.change_app_theme, |
200 | 0, | 890 | choicesId = R.array.themeEntries, |
201 | R.array.themeEntries, | 891 | valuesId = R.array.themeValues |
202 | R.array.themeValues | ||
203 | ) | 892 | ) |
204 | ) | 893 | ) |
205 | } | 894 | } |
@@ -228,10 +917,9 @@ class SettingsFragmentPresenter( | |||
228 | add( | 917 | add( |
229 | SingleChoiceSetting( | 918 | SingleChoiceSetting( |
230 | themeMode, | 919 | themeMode, |
231 | R.string.change_theme_mode, | 920 | titleId = R.string.change_theme_mode, |
232 | 0, | 921 | choicesId = R.array.themeModeEntries, |
233 | R.array.themeModeEntries, | 922 | valuesId = R.array.themeModeValues |
234 | R.array.themeModeValues | ||
235 | ) | 923 | ) |
236 | ) | 924 | ) |
237 | 925 | ||
@@ -262,8 +950,8 @@ class SettingsFragmentPresenter( | |||
262 | add( | 950 | add( |
263 | SwitchSetting( | 951 | SwitchSetting( |
264 | blackBackgrounds, | 952 | blackBackgrounds, |
265 | R.string.use_black_backgrounds, | 953 | titleId = R.string.use_black_backgrounds, |
266 | R.string.use_black_backgrounds_description | 954 | descriptionId = R.string.use_black_backgrounds_description |
267 | ) | 955 | ) |
268 | ) | 956 | ) |
269 | } | 957 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt new file mode 100755 index 000000000..ed60cf34f --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSearchFragment.kt | |||
@@ -0,0 +1,183 @@ | |||
1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import android.content.Context | ||
7 | import android.os.Bundle | ||
8 | import android.view.LayoutInflater | ||
9 | import android.view.View | ||
10 | import android.view.ViewGroup | ||
11 | import android.view.inputmethod.InputMethodManager | ||
12 | import androidx.core.view.ViewCompat | ||
13 | import androidx.core.view.WindowInsetsCompat | ||
14 | import androidx.core.view.updatePadding | ||
15 | import androidx.core.widget.doOnTextChanged | ||
16 | import androidx.fragment.app.Fragment | ||
17 | import androidx.fragment.app.activityViewModels | ||
18 | import androidx.recyclerview.widget.LinearLayoutManager | ||
19 | import com.google.android.material.divider.MaterialDividerItemDecoration | ||
20 | import com.google.android.material.transition.MaterialSharedAxis | ||
21 | import info.debatty.java.stringsimilarity.Cosine | ||
22 | import org.yuzu.yuzu_emu.R | ||
23 | import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding | ||
24 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
25 | import org.yuzu.yuzu_emu.utils.NativeConfig | ||
26 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
27 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | ||
28 | import org.yuzu.yuzu_emu.utils.collect | ||
29 | |||
30 | class SettingsSearchFragment : Fragment() { | ||
31 | private var _binding: FragmentSettingsSearchBinding? = null | ||
32 | private val binding get() = _binding!! | ||
33 | |||
34 | private var settingsAdapter: SettingsAdapter? = null | ||
35 | |||
36 | private val settingsViewModel: SettingsViewModel by activityViewModels() | ||
37 | |||
38 | override fun onCreate(savedInstanceState: Bundle?) { | ||
39 | super.onCreate(savedInstanceState) | ||
40 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
41 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
42 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) | ||
43 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | ||
44 | } | ||
45 | |||
46 | override fun onCreateView( | ||
47 | inflater: LayoutInflater, | ||
48 | container: ViewGroup?, | ||
49 | savedInstanceState: Bundle? | ||
50 | ): View { | ||
51 | _binding = FragmentSettingsSearchBinding.inflate(layoutInflater) | ||
52 | return binding.root | ||
53 | } | ||
54 | |||
55 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
56 | super.onViewCreated(view, savedInstanceState) | ||
57 | |||
58 | if (savedInstanceState != null) { | ||
59 | binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT)) | ||
60 | } | ||
61 | |||
62 | settingsAdapter = SettingsAdapter(this, requireContext()) | ||
63 | |||
64 | val dividerDecoration = MaterialDividerItemDecoration( | ||
65 | requireContext(), | ||
66 | LinearLayoutManager.VERTICAL | ||
67 | ) | ||
68 | dividerDecoration.isLastItemDecorated = false | ||
69 | binding.settingsList.apply { | ||
70 | adapter = settingsAdapter | ||
71 | layoutManager = LinearLayoutManager(requireContext()) | ||
72 | addItemDecoration(dividerDecoration) | ||
73 | } | ||
74 | |||
75 | focusSearch() | ||
76 | |||
77 | binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) } | ||
78 | binding.searchBackground.setOnClickListener { focusSearch() } | ||
79 | binding.clearButton.setOnClickListener { binding.searchText.setText("") } | ||
80 | binding.searchText.doOnTextChanged { _, _, _, _ -> | ||
81 | search() | ||
82 | binding.settingsList.smoothScrollToPosition(0) | ||
83 | } | ||
84 | settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) { | ||
85 | if (it) { | ||
86 | settingsViewModel.setShouldReloadSettingsList(false) | ||
87 | search() | ||
88 | } | ||
89 | } | ||
90 | |||
91 | search() | ||
92 | |||
93 | setInsets() | ||
94 | } | ||
95 | |||
96 | override fun onSaveInstanceState(outState: Bundle) { | ||
97 | super.onSaveInstanceState(outState) | ||
98 | outState.putString(SEARCH_TEXT, binding.searchText.text.toString()) | ||
99 | } | ||
100 | |||
101 | private fun search() { | ||
102 | val searchTerm = binding.searchText.text.toString().lowercase() | ||
103 | binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false) | ||
104 | if (searchTerm.isEmpty()) { | ||
105 | binding.noResultsView.setVisible(visible = false, gone = false) | ||
106 | settingsAdapter?.submitList(emptyList()) | ||
107 | return | ||
108 | } | ||
109 | |||
110 | val baseList = SettingsItem.settingsItems | ||
111 | val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1) | ||
112 | val sortedList: List<SettingsItem> = baseList.mapNotNull { item -> | ||
113 | val title = item.value.title.lowercase() | ||
114 | val similarity = similarityAlgorithm.similarity(searchTerm, title) | ||
115 | if (similarity > 0.08) { | ||
116 | Pair(similarity, item) | ||
117 | } else { | ||
118 | null | ||
119 | } | ||
120 | }.sortedByDescending { it.first }.mapNotNull { | ||
121 | val item = it.second.value | ||
122 | val pairedSettingKey = item.setting.pairedSettingKey | ||
123 | val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) { | ||
124 | val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false) | ||
125 | if (pairedSettingValue) it.second.value else null | ||
126 | } else { | ||
127 | it.second.value | ||
128 | } | ||
129 | optionalSetting | ||
130 | } | ||
131 | settingsAdapter?.submitList(sortedList) | ||
132 | binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false) | ||
133 | } | ||
134 | |||
135 | private fun focusSearch() { | ||
136 | binding.searchText.requestFocus() | ||
137 | val imm = requireActivity() | ||
138 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | ||
139 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | ||
140 | } | ||
141 | |||
142 | private fun setInsets() = | ||
143 | ViewCompat.setOnApplyWindowInsetsListener( | ||
144 | binding.root | ||
145 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
146 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | ||
147 | val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge) | ||
148 | val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip) | ||
149 | |||
150 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
151 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | ||
152 | |||
153 | val leftInsets = barInsets.left + cutoutInsets.left | ||
154 | val rightInsets = barInsets.right + cutoutInsets.right | ||
155 | |||
156 | binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing) | ||
157 | binding.frameSearch.updatePadding( | ||
158 | left = leftInsets + sideMargin, | ||
159 | top = barInsets.top + topMargin, | ||
160 | right = rightInsets + sideMargin | ||
161 | ) | ||
162 | binding.noResultsView.updatePadding( | ||
163 | left = leftInsets, | ||
164 | right = rightInsets, | ||
165 | bottom = barInsets.bottom | ||
166 | ) | ||
167 | |||
168 | binding.settingsList.updateMargins( | ||
169 | left = leftInsets + sideMargin, | ||
170 | right = rightInsets + sideMargin | ||
171 | ) | ||
172 | binding.divider.updateMargins( | ||
173 | left = leftInsets + sideMargin, | ||
174 | right = rightInsets + sideMargin | ||
175 | ) | ||
176 | |||
177 | windowInsets | ||
178 | } | ||
179 | |||
180 | companion object { | ||
181 | const val SEARCH_TEXT = "SearchText" | ||
182 | } | ||
183 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt new file mode 100755 index 000000000..fbdca04e9 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsViewModel.kt | |||
@@ -0,0 +1,112 @@ | |||
1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui | ||
5 | |||
6 | import androidx.lifecycle.ViewModel | ||
7 | import kotlinx.coroutines.flow.MutableStateFlow | ||
8 | import kotlinx.coroutines.flow.StateFlow | ||
9 | import kotlinx.coroutines.flow.asStateFlow | ||
10 | import org.yuzu.yuzu_emu.R | ||
11 | import org.yuzu.yuzu_emu.YuzuApplication | ||
12 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
13 | import org.yuzu.yuzu_emu.model.Game | ||
14 | import org.yuzu.yuzu_emu.utils.InputHandler | ||
15 | import org.yuzu.yuzu_emu.utils.ParamPackage | ||
16 | |||
17 | class SettingsViewModel : ViewModel() { | ||
18 | var game: Game? = null | ||
19 | |||
20 | var clickedItem: SettingsItem? = null | ||
21 | |||
22 | var currentDevice = 0 | ||
23 | |||
24 | val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate | ||
25 | private val _shouldRecreate = MutableStateFlow(false) | ||
26 | |||
27 | val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack | ||
28 | private val _shouldNavigateBack = MutableStateFlow(false) | ||
29 | |||
30 | val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog | ||
31 | private val _shouldShowResetSettingsDialog = MutableStateFlow(false) | ||
32 | |||
33 | val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList | ||
34 | private val _shouldReloadSettingsList = MutableStateFlow(false) | ||
35 | |||
36 | val sliderProgress: StateFlow<Int> get() = _sliderProgress | ||
37 | private val _sliderProgress = MutableStateFlow(-1) | ||
38 | |||
39 | val sliderTextValue: StateFlow<String> get() = _sliderTextValue | ||
40 | private val _sliderTextValue = MutableStateFlow("") | ||
41 | |||
42 | val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged | ||
43 | private val _adapterItemChanged = MutableStateFlow(-1) | ||
44 | |||
45 | private val _datasetChanged = MutableStateFlow(false) | ||
46 | val datasetChanged = _datasetChanged.asStateFlow() | ||
47 | |||
48 | private val _reloadListAndNotifyDataset = MutableStateFlow(false) | ||
49 | val reloadListAndNotifyDataset = _reloadListAndNotifyDataset.asStateFlow() | ||
50 | |||
51 | private val _shouldShowDeleteProfileDialog = MutableStateFlow("") | ||
52 | val shouldShowDeleteProfileDialog = _shouldShowDeleteProfileDialog.asStateFlow() | ||
53 | |||
54 | private val _shouldShowResetInputDialog = MutableStateFlow(false) | ||
55 | val shouldShowResetInputDialog = _shouldShowResetInputDialog.asStateFlow() | ||
56 | |||
57 | fun setShouldRecreate(value: Boolean) { | ||
58 | _shouldRecreate.value = value | ||
59 | } | ||
60 | |||
61 | fun setShouldNavigateBack(value: Boolean) { | ||
62 | _shouldNavigateBack.value = value | ||
63 | } | ||
64 | |||
65 | fun setShouldShowResetSettingsDialog(value: Boolean) { | ||
66 | _shouldShowResetSettingsDialog.value = value | ||
67 | } | ||
68 | |||
69 | fun setShouldReloadSettingsList(value: Boolean) { | ||
70 | _shouldReloadSettingsList.value = value | ||
71 | } | ||
72 | |||
73 | fun setSliderTextValue(value: Float, units: String) { | ||
74 | _sliderProgress.value = value.toInt() | ||
75 | _sliderTextValue.value = String.format( | ||
76 | YuzuApplication.appContext.getString(R.string.value_with_units), | ||
77 | value.toInt().toString(), | ||
78 | units | ||
79 | ) | ||
80 | } | ||
81 | |||
82 | fun setSliderProgress(value: Float) { | ||
83 | _sliderProgress.value = value.toInt() | ||
84 | } | ||
85 | |||
86 | fun setAdapterItemChanged(value: Int) { | ||
87 | _adapterItemChanged.value = value | ||
88 | } | ||
89 | |||
90 | fun setDatasetChanged(value: Boolean) { | ||
91 | _datasetChanged.value = value | ||
92 | } | ||
93 | |||
94 | fun setReloadListAndNotifyDataset(value: Boolean) { | ||
95 | _reloadListAndNotifyDataset.value = value | ||
96 | } | ||
97 | |||
98 | fun setShouldShowDeleteProfileDialog(profile: String) { | ||
99 | _shouldShowDeleteProfileDialog.value = profile | ||
100 | } | ||
101 | |||
102 | fun setShouldShowResetInputDialog(value: Boolean) { | ||
103 | _shouldShowResetInputDialog.value = value | ||
104 | } | ||
105 | |||
106 | fun getCurrentDeviceParams(defaultParams: ParamPackage): ParamPackage = | ||
107 | try { | ||
108 | InputHandler.registeredControllers[currentDevice] | ||
109 | } catch (e: IndexOutOfBoundsException) { | ||
110 | defaultParams | ||
111 | } | ||
112 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 5ad0899dd..367db7fd2 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt | |||
@@ -14,6 +14,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | |||
14 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 14 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
15 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 15 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
16 | import org.yuzu.yuzu_emu.utils.NativeConfig | 16 | import org.yuzu.yuzu_emu.utils.NativeConfig |
17 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
17 | 18 | ||
18 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 19 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
19 | SettingViewHolder(binding.root, adapter) { | 20 | SettingViewHolder(binding.root, adapter) { |
@@ -21,28 +22,19 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
21 | 22 | ||
22 | override fun bind(item: SettingsItem) { | 23 | override fun bind(item: SettingsItem) { |
23 | setting = item as DateTimeSetting | 24 | setting = item as DateTimeSetting |
24 | binding.textSettingName.setText(item.nameId) | 25 | binding.textSettingName.text = item.title |
25 | if (item.descriptionId != 0) { | 26 | binding.textSettingDescription.setVisible(item.description.isNotEmpty()) |
26 | binding.textSettingDescription.setText(item.descriptionId) | 27 | binding.textSettingDescription.text = item.description |
27 | binding.textSettingDescription.visibility = View.VISIBLE | 28 | binding.textSettingValue.setVisible(true) |
28 | } else { | ||
29 | binding.textSettingDescription.visibility = View.GONE | ||
30 | } | ||
31 | |||
32 | binding.textSettingValue.visibility = View.VISIBLE | ||
33 | val epochTime = setting.getValue() | 29 | val epochTime = setting.getValue() |
34 | val instant = Instant.ofEpochMilli(epochTime * 1000) | 30 | val instant = Instant.ofEpochMilli(epochTime * 1000) |
35 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) | 31 | val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) |
36 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) | 32 | val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) |
37 | binding.textSettingValue.text = dateFormatter.format(zonedTime) | 33 | binding.textSettingValue.text = dateFormatter.format(zonedTime) |
38 | 34 | ||
39 | binding.buttonClear.visibility = if (setting.setting.global || | 35 | binding.buttonClear.setVisible( |
40 | !NativeConfig.isPerGameConfigLoaded() | 36 | !setting.setting.global || NativeConfig.isPerGameConfigLoaded() |
41 | ) { | 37 | ) |
42 | View.GONE | ||
43 | } else { | ||
44 | View.VISIBLE | ||
45 | } | ||
46 | binding.buttonClear.setOnClickListener { | 38 | binding.buttonClear.setOnClickListener { |
47 | adapter.onClearClick(setting, bindingAdapterPosition) | 39 | adapter.onClearClick(setting, bindingAdapterPosition) |
48 | } | 40 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt index f5bcf705c..0815c36e2 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/HeaderViewHolder.kt | |||
@@ -16,7 +16,7 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett | |||
16 | } | 16 | } |
17 | 17 | ||
18 | override fun bind(item: SettingsItem) { | 18 | override fun bind(item: SettingsItem) { |
19 | binding.textHeaderName.setText(item.nameId) | 19 | binding.textHeaderName.text = item.title |
20 | } | 20 | } |
21 | 21 | ||
22 | override fun onClick(clicked: View) { | 22 | override fun onClick(clicked: View) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt new file mode 100755 index 000000000..86af3a226 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputProfileViewHolder.kt | |||
@@ -0,0 +1,34 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder | ||
5 | |||
6 | import android.view.View | ||
7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
8 | import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting | ||
9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
11 | import org.yuzu.yuzu_emu.R | ||
12 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | |||
14 | class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | ||
15 | SettingViewHolder(binding.root, adapter) { | ||
16 | private lateinit var setting: InputProfileSetting | ||
17 | |||
18 | override fun bind(item: SettingsItem) { | ||
19 | setting = item as InputProfileSetting | ||
20 | binding.textSettingName.text = setting.title | ||
21 | binding.textSettingValue.text = | ||
22 | setting.getCurrentProfile().ifEmpty { binding.root.context.getString(R.string.not_set) } | ||
23 | |||
24 | binding.textSettingDescription.setVisible(false) | ||
25 | binding.buttonClear.setVisible(false) | ||
26 | binding.icon.setVisible(false) | ||
27 | binding.buttonClear.setVisible(false) | ||
28 | } | ||
29 | |||
30 | override fun onClick(clicked: View) = | ||
31 | adapter.onInputProfileClick(setting, bindingAdapterPosition) | ||
32 | |||
33 | override fun onLongClick(clicked: View): Boolean = false | ||
34 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt new file mode 100755 index 000000000..9d9047804 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/InputViewHolder.kt | |||
@@ -0,0 +1,60 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder | ||
5 | |||
6 | import android.view.View | ||
7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding | ||
8 | import org.yuzu.yuzu_emu.features.input.NativeInput | ||
9 | import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting | ||
10 | import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting | ||
11 | import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting | ||
12 | import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting | ||
13 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
14 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
15 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
16 | |||
17 | class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: SettingsAdapter) : | ||
18 | SettingViewHolder(binding.root, adapter) { | ||
19 | private lateinit var setting: InputSetting | ||
20 | |||
21 | override fun bind(item: SettingsItem) { | ||
22 | setting = item as InputSetting | ||
23 | binding.textSettingName.text = setting.title | ||
24 | binding.textSettingValue.text = setting.getSelectedValue() | ||
25 | |||
26 | when (item) { | ||
27 | is AnalogInputSetting -> { | ||
28 | val param = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) | ||
29 | binding.buttonOptions.setVisible( | ||
30 | param.get("engine", "") == "analog_from_button" || | ||
31 | param.has("axis_x") || param.has("axis_y") | ||
32 | ) | ||
33 | } | ||
34 | |||
35 | is ButtonInputSetting -> { | ||
36 | val param = NativeInput.getButtonParam(item.playerIndex, item.nativeButton) | ||
37 | binding.buttonOptions.setVisible( | ||
38 | param.has("code") || param.has("button") || param.has("hat") || | ||
39 | param.has("axis") | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | is ModifierInputSetting -> { | ||
44 | val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog) | ||
45 | binding.buttonOptions.setVisible(params.has("modifier")) | ||
46 | } | ||
47 | } | ||
48 | |||
49 | binding.buttonOptions.setOnClickListener(null) | ||
50 | binding.buttonOptions.setOnClickListener { | ||
51 | adapter.onInputOptionsClick(binding.buttonOptions, setting, bindingAdapterPosition) | ||
52 | } | ||
53 | } | ||
54 | |||
55 | override fun onClick(clicked: View) = | ||
56 | adapter.onInputClick(setting, bindingAdapterPosition) | ||
57 | |||
58 | override fun onLongClick(clicked: View): Boolean = | ||
59 | adapter.onLongClick(setting, bindingAdapterPosition) | ||
60 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt index 507184238..fc2ffb515 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt | |||
@@ -5,11 +5,11 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
5 | 5 | ||
6 | import android.view.View | 6 | import android.view.View |
7 | import androidx.core.content.res.ResourcesCompat | 7 | import androidx.core.content.res.ResourcesCompat |
8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
9 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
10 | import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting |
11 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
12 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
12 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | 13 | ||
14 | class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 14 | class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
15 | SettingViewHolder(binding.root, adapter) { | 15 | SettingViewHolder(binding.root, adapter) { |
@@ -17,34 +17,28 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA | |||
17 | 17 | ||
18 | override fun bind(item: SettingsItem) { | 18 | override fun bind(item: SettingsItem) { |
19 | setting = item as RunnableSetting | 19 | setting = item as RunnableSetting |
20 | if (item.iconId != 0) { | 20 | binding.icon.setVisible(setting.iconId != 0) |
21 | binding.icon.visibility = View.VISIBLE | 21 | if (setting.iconId != 0) { |
22 | binding.icon.setImageDrawable( | 22 | binding.icon.setImageDrawable( |
23 | ResourcesCompat.getDrawable( | 23 | ResourcesCompat.getDrawable( |
24 | binding.icon.resources, | 24 | binding.icon.resources, |
25 | item.iconId, | 25 | setting.iconId, |
26 | binding.icon.context.theme | 26 | binding.icon.context.theme |
27 | ) | 27 | ) |
28 | ) | 28 | ) |
29 | } else { | ||
30 | binding.icon.visibility = View.GONE | ||
31 | } | 29 | } |
32 | 30 | ||
33 | binding.textSettingName.setText(item.nameId) | 31 | binding.textSettingName.text = setting.title |
34 | if (item.descriptionId != 0) { | 32 | binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) |
35 | binding.textSettingDescription.setText(item.descriptionId) | 33 | binding.textSettingDescription.text = item.description |
36 | binding.textSettingDescription.visibility = View.VISIBLE | 34 | binding.textSettingValue.setVisible(false) |
37 | } else { | 35 | binding.buttonClear.setVisible(false) |
38 | binding.textSettingDescription.visibility = View.GONE | ||
39 | } | ||
40 | binding.textSettingValue.visibility = View.GONE | ||
41 | binding.buttonClear.visibility = View.GONE | ||
42 | 36 | ||
43 | setStyle(setting.isEditable, binding) | 37 | setStyle(setting.isEditable, binding) |
44 | } | 38 | } |
45 | 39 | ||
46 | override fun onClick(clicked: View) { | 40 | override fun onClick(clicked: View) { |
47 | if (!setting.isRuntimeRunnable && !NativeLibrary.isRunning()) { | 41 | if (setting.isRunnable) { |
48 | setting.runnable.invoke() | 42 | setting.runnable.invoke() |
49 | } | 43 | } |
50 | } | 44 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index 02dab3785..e2fe0b072 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt | |||
@@ -5,11 +5,13 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
5 | 5 | ||
6 | import android.view.View | 6 | import android.view.View |
7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | 7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding |
8 | import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting | ||
8 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
9 | import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting |
10 | import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting | 11 | import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting |
11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 12 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
12 | import org.yuzu.yuzu_emu.utils.NativeConfig | 13 | import org.yuzu.yuzu_emu.utils.NativeConfig |
14 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | 15 | ||
14 | class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 16 | class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
15 | SettingViewHolder(binding.root, adapter) { | 17 | SettingViewHolder(binding.root, adapter) { |
@@ -17,40 +19,38 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
17 | 19 | ||
18 | override fun bind(item: SettingsItem) { | 20 | override fun bind(item: SettingsItem) { |
19 | setting = item | 21 | setting = item |
20 | binding.textSettingName.setText(item.nameId) | 22 | binding.textSettingName.text = setting.title |
21 | if (item.descriptionId != 0) { | 23 | binding.textSettingDescription.setVisible(item.description.isNotEmpty()) |
22 | binding.textSettingDescription.setText(item.descriptionId) | 24 | binding.textSettingDescription.text = item.description |
23 | binding.textSettingDescription.visibility = View.VISIBLE | ||
24 | } else { | ||
25 | binding.textSettingDescription.visibility = View.GONE | ||
26 | } | ||
27 | 25 | ||
28 | binding.textSettingValue.visibility = View.VISIBLE | 26 | binding.textSettingValue.setVisible(true) |
29 | if (item is SingleChoiceSetting) { | 27 | when (item) { |
30 | val resMgr = binding.textSettingValue.context.resources | 28 | is SingleChoiceSetting -> { |
31 | val values = resMgr.getIntArray(item.valuesId) | 29 | val resMgr = binding.textSettingValue.context.resources |
32 | for (i in values.indices) { | 30 | val values = resMgr.getIntArray(item.valuesId) |
33 | if (values[i] == item.getSelectedValue()) { | 31 | for (i in values.indices) { |
34 | binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] | 32 | if (values[i] == item.getSelectedValue()) { |
35 | break | 33 | binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] |
34 | break | ||
35 | } | ||
36 | } | 36 | } |
37 | } | 37 | } |
38 | } else if (item is StringSingleChoiceSetting) { | 38 | |
39 | for (i in item.values.indices) { | 39 | is StringSingleChoiceSetting -> { |
40 | if (item.values[i] == item.getSelectedValue()) { | 40 | binding.textSettingValue.text = item.getSelectedValue() |
41 | binding.textSettingValue.text = item.choices[i] | ||
42 | break | ||
43 | } | ||
44 | } | 41 | } |
45 | } | ||
46 | 42 | ||
47 | binding.buttonClear.visibility = if (setting.setting.global || | 43 | is IntSingleChoiceSetting -> { |
48 | !NativeConfig.isPerGameConfigLoaded() | 44 | binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue()) |
49 | ) { | 45 | } |
50 | View.GONE | 46 | } |
51 | } else { | 47 | if (binding.textSettingValue.text.isEmpty()) { |
52 | View.VISIBLE | 48 | binding.textSettingValue.setVisible(false) |
53 | } | 49 | } |
50 | |||
51 | binding.buttonClear.setVisible( | ||
52 | !setting.setting.global || NativeConfig.isPerGameConfigLoaded() | ||
53 | ) | ||
54 | binding.buttonClear.setOnClickListener { | 54 | binding.buttonClear.setOnClickListener { |
55 | adapter.onClearClick(setting, bindingAdapterPosition) | 55 | adapter.onClearClick(setting, bindingAdapterPosition) |
56 | } | 56 | } |
@@ -63,16 +63,25 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
63 | return | 63 | return |
64 | } | 64 | } |
65 | 65 | ||
66 | if (setting is SingleChoiceSetting) { | 66 | when (setting) { |
67 | adapter.onSingleChoiceClick( | 67 | is SingleChoiceSetting -> adapter.onSingleChoiceClick( |
68 | (setting as SingleChoiceSetting), | 68 | setting as SingleChoiceSetting, |
69 | bindingAdapterPosition | ||
70 | ) | ||
71 | } else if (setting is StringSingleChoiceSetting) { | ||
72 | adapter.onStringSingleChoiceClick( | ||
73 | (setting as StringSingleChoiceSetting), | ||
74 | bindingAdapterPosition | 69 | bindingAdapterPosition |
75 | ) | 70 | ) |
71 | |||
72 | is StringSingleChoiceSetting -> { | ||
73 | adapter.onStringSingleChoiceClick( | ||
74 | setting as StringSingleChoiceSetting, | ||
75 | bindingAdapterPosition | ||
76 | ) | ||
77 | } | ||
78 | |||
79 | is IntSingleChoiceSetting -> { | ||
80 | adapter.onIntSingleChoiceClick( | ||
81 | setting as IntSingleChoiceSetting, | ||
82 | bindingAdapterPosition | ||
83 | ) | ||
84 | } | ||
76 | } | 85 | } |
77 | } | 86 | } |
78 | 87 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt index 596c18012..a37b59b44 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt | |||
@@ -10,6 +10,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | |||
10 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting |
11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
12 | import org.yuzu.yuzu_emu.utils.NativeConfig | 12 | import org.yuzu.yuzu_emu.utils.NativeConfig |
13 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | 14 | ||
14 | class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 15 | class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
15 | SettingViewHolder(binding.root, adapter) { | 16 | SettingViewHolder(binding.root, adapter) { |
@@ -17,27 +18,19 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda | |||
17 | 18 | ||
18 | override fun bind(item: SettingsItem) { | 19 | override fun bind(item: SettingsItem) { |
19 | setting = item as SliderSetting | 20 | setting = item as SliderSetting |
20 | binding.textSettingName.setText(item.nameId) | 21 | binding.textSettingName.text = setting.title |
21 | if (item.descriptionId != 0) { | 22 | binding.textSettingDescription.setVisible(item.description.isNotEmpty()) |
22 | binding.textSettingDescription.setText(item.descriptionId) | 23 | binding.textSettingDescription.text = setting.description |
23 | binding.textSettingDescription.visibility = View.VISIBLE | 24 | binding.textSettingValue.setVisible(true) |
24 | } else { | ||
25 | binding.textSettingDescription.visibility = View.GONE | ||
26 | } | ||
27 | binding.textSettingValue.visibility = View.VISIBLE | ||
28 | binding.textSettingValue.text = String.format( | 25 | binding.textSettingValue.text = String.format( |
29 | binding.textSettingValue.context.getString(R.string.value_with_units), | 26 | binding.textSettingValue.context.getString(R.string.value_with_units), |
30 | setting.getSelectedValue(), | 27 | setting.getSelectedValue(), |
31 | setting.units | 28 | setting.units |
32 | ) | 29 | ) |
33 | 30 | ||
34 | binding.buttonClear.visibility = if (setting.setting.global || | 31 | binding.buttonClear.setVisible( |
35 | !NativeConfig.isPerGameConfigLoaded() | 32 | !setting.setting.global || NativeConfig.isPerGameConfigLoaded() |
36 | ) { | 33 | ) |
37 | View.GONE | ||
38 | } else { | ||
39 | View.VISIBLE | ||
40 | } | ||
41 | binding.buttonClear.setOnClickListener { | 34 | binding.buttonClear.setOnClickListener { |
42 | adapter.onClearClick(setting, bindingAdapterPosition) | 35 | adapter.onClearClick(setting, bindingAdapterPosition) |
43 | } | 36 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt index 20d35a17d..f7a9c08c3 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt | |||
@@ -9,39 +9,34 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | |||
9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
10 | import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting |
11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
12 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
12 | 13 | ||
13 | class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 14 | class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
14 | SettingViewHolder(binding.root, adapter) { | 15 | SettingViewHolder(binding.root, adapter) { |
15 | private lateinit var item: SubmenuSetting | 16 | private lateinit var setting: SubmenuSetting |
16 | 17 | ||
17 | override fun bind(item: SettingsItem) { | 18 | override fun bind(item: SettingsItem) { |
18 | this.item = item as SubmenuSetting | 19 | setting = item as SubmenuSetting |
19 | if (item.iconId != 0) { | 20 | binding.icon.setVisible(setting.iconId != 0) |
20 | binding.icon.visibility = View.VISIBLE | 21 | if (setting.iconId != 0) { |
21 | binding.icon.setImageDrawable( | 22 | binding.icon.setImageDrawable( |
22 | ResourcesCompat.getDrawable( | 23 | ResourcesCompat.getDrawable( |
23 | binding.icon.resources, | 24 | binding.icon.resources, |
24 | item.iconId, | 25 | setting.iconId, |
25 | binding.icon.context.theme | 26 | binding.icon.context.theme |
26 | ) | 27 | ) |
27 | ) | 28 | ) |
28 | } else { | ||
29 | binding.icon.visibility = View.GONE | ||
30 | } | 29 | } |
31 | 30 | ||
32 | binding.textSettingName.setText(item.nameId) | 31 | binding.textSettingName.text = setting.title |
33 | if (item.descriptionId != 0) { | 32 | binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) |
34 | binding.textSettingDescription.setText(item.descriptionId) | 33 | binding.textSettingDescription.text = setting.description |
35 | binding.textSettingDescription.visibility = View.VISIBLE | 34 | binding.textSettingValue.setVisible(false) |
36 | } else { | 35 | binding.buttonClear.setVisible(false) |
37 | binding.textSettingDescription.visibility = View.GONE | ||
38 | } | ||
39 | binding.textSettingValue.visibility = View.GONE | ||
40 | binding.buttonClear.visibility = View.GONE | ||
41 | } | 36 | } |
42 | 37 | ||
43 | override fun onClick(clicked: View) { | 38 | override fun onClick(clicked: View) { |
44 | adapter.onSubmenuClick(item) | 39 | adapter.onSubmenuClick(setting) |
45 | } | 40 | } |
46 | 41 | ||
47 | override fun onLongClick(clicked: View): Boolean { | 42 | override fun onLongClick(clicked: View): Boolean { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index d26bf9374..53f7b301f 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt | |||
@@ -10,6 +10,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | |||
10 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting |
11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
12 | import org.yuzu.yuzu_emu.utils.NativeConfig | 12 | import org.yuzu.yuzu_emu.utils.NativeConfig |
13 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
13 | 14 | ||
14 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : | 15 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : |
15 | SettingViewHolder(binding.root, adapter) { | 16 | SettingViewHolder(binding.root, adapter) { |
@@ -18,28 +19,19 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter | |||
18 | 19 | ||
19 | override fun bind(item: SettingsItem) { | 20 | override fun bind(item: SettingsItem) { |
20 | setting = item as SwitchSetting | 21 | setting = item as SwitchSetting |
21 | binding.textSettingName.setText(item.nameId) | 22 | binding.textSettingName.text = setting.title |
22 | if (item.descriptionId != 0) { | 23 | binding.textSettingDescription.setVisible(setting.description.isNotEmpty()) |
23 | binding.textSettingDescription.setText(item.descriptionId) | 24 | binding.textSettingDescription.text = setting.description |
24 | binding.textSettingDescription.visibility = View.VISIBLE | ||
25 | } else { | ||
26 | binding.textSettingDescription.text = "" | ||
27 | binding.textSettingDescription.visibility = View.GONE | ||
28 | } | ||
29 | 25 | ||
30 | binding.switchWidget.setOnCheckedChangeListener(null) | 26 | binding.switchWidget.setOnCheckedChangeListener(null) |
31 | binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal) | 27 | binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal) |
32 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> | 28 | binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> |
33 | adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition) | 29 | adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition) |
34 | } | 30 | } |
35 | 31 | ||
36 | binding.buttonClear.visibility = if (setting.setting.global || | 32 | binding.buttonClear.setVisible( |
37 | !NativeConfig.isPerGameConfigLoaded() | 33 | !setting.setting.global || NativeConfig.isPerGameConfigLoaded() |
38 | ) { | 34 | ) |
39 | View.GONE | ||
40 | } else { | ||
41 | View.VISIBLE | ||
42 | } | ||
43 | binding.buttonClear.setOnClickListener { | 35 | binding.buttonClear.setOnClickListener { |
44 | adapter.onClearClick(setting, bindingAdapterPosition) | 36 | adapter.onClearClick(setting, bindingAdapterPosition) |
45 | } | 37 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 872553ac4..110aa2960 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
7 | import android.content.Intent | 6 | import android.content.Intent |
8 | import android.os.Bundle | 7 | import android.os.Bundle |
9 | import android.view.LayoutInflater | 8 | import android.view.LayoutInflater |
@@ -16,9 +15,6 @@ import androidx.core.view.updatePadding | |||
16 | import androidx.documentfile.provider.DocumentFile | 15 | import androidx.documentfile.provider.DocumentFile |
17 | import androidx.fragment.app.Fragment | 16 | import androidx.fragment.app.Fragment |
18 | import androidx.fragment.app.activityViewModels | 17 | import androidx.fragment.app.activityViewModels |
19 | import androidx.lifecycle.Lifecycle | ||
20 | import androidx.lifecycle.lifecycleScope | ||
21 | import androidx.lifecycle.repeatOnLifecycle | ||
22 | import androidx.navigation.findNavController | 18 | import androidx.navigation.findNavController |
23 | import androidx.navigation.fragment.navArgs | 19 | import androidx.navigation.fragment.navArgs |
24 | import androidx.recyclerview.widget.LinearLayoutManager | 20 | import androidx.recyclerview.widget.LinearLayoutManager |
@@ -32,6 +28,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel | |||
32 | import org.yuzu.yuzu_emu.utils.AddonUtil | 28 | import org.yuzu.yuzu_emu.utils.AddonUtil |
33 | import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo | 29 | import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo |
34 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 30 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
31 | import org.yuzu.yuzu_emu.utils.collect | ||
35 | import java.io.File | 32 | import java.io.File |
36 | 33 | ||
37 | class AddonsFragment : Fragment() { | 34 | class AddonsFragment : Fragment() { |
@@ -60,8 +57,6 @@ class AddonsFragment : Fragment() { | |||
60 | return binding.root | 57 | return binding.root |
61 | } | 58 | } |
62 | 59 | ||
63 | // This is using the correct scope, lint is just acting up | ||
64 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
65 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 60 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
66 | super.onViewCreated(view, savedInstanceState) | 61 | super.onViewCreated(view, savedInstanceState) |
67 | homeViewModel.setNavigationVisibility(visible = false, animated = false) | 62 | homeViewModel.setNavigationVisibility(visible = false, animated = false) |
@@ -78,57 +73,41 @@ class AddonsFragment : Fragment() { | |||
78 | adapter = AddonAdapter(addonViewModel) | 73 | adapter = AddonAdapter(addonViewModel) |
79 | } | 74 | } |
80 | 75 | ||
81 | viewLifecycleOwner.lifecycleScope.apply { | 76 | addonViewModel.addonList.collect(viewLifecycleOwner) { |
82 | launch { | 77 | (binding.listAddons.adapter as AddonAdapter).submitList(it) |
83 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 78 | } |
84 | addonViewModel.addonList.collect { | 79 | addonViewModel.showModInstallPicker.collect( |
85 | (binding.listAddons.adapter as AddonAdapter).submitList(it) | 80 | viewLifecycleOwner, |
86 | } | 81 | resetState = { addonViewModel.showModInstallPicker(false) } |
87 | } | 82 | ) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) } |
88 | } | 83 | addonViewModel.showModNoticeDialog.collect( |
89 | launch { | 84 | viewLifecycleOwner, |
90 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 85 | resetState = { addonViewModel.showModNoticeDialog(false) } |
91 | addonViewModel.showModInstallPicker.collect { | 86 | ) { |
92 | if (it) { | 87 | if (it) { |
93 | installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) | 88 | MessageDialogFragment.newInstance( |
94 | addonViewModel.showModInstallPicker(false) | 89 | requireActivity(), |
95 | } | 90 | titleId = R.string.addon_notice, |
96 | } | 91 | descriptionId = R.string.addon_notice_description, |
97 | } | 92 | dismissible = false, |
98 | } | 93 | positiveAction = { addonViewModel.showModInstallPicker(true) }, |
99 | launch { | 94 | negativeAction = {}, |
100 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 95 | negativeButtonTitleId = R.string.close |
101 | addonViewModel.showModNoticeDialog.collect { | 96 | ).show(parentFragmentManager, MessageDialogFragment.TAG) |
102 | if (it) { | ||
103 | MessageDialogFragment.newInstance( | ||
104 | requireActivity(), | ||
105 | titleId = R.string.addon_notice, | ||
106 | descriptionId = R.string.addon_notice_description, | ||
107 | dismissible = false, | ||
108 | positiveAction = { addonViewModel.showModInstallPicker(true) }, | ||
109 | negativeAction = {}, | ||
110 | negativeButtonTitleId = R.string.close | ||
111 | ).show(parentFragmentManager, MessageDialogFragment.TAG) | ||
112 | addonViewModel.showModNoticeDialog(false) | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | 97 | } |
117 | launch { | 98 | } |
118 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 99 | addonViewModel.addonToDelete.collect( |
119 | addonViewModel.addonToDelete.collect { | 100 | viewLifecycleOwner, |
120 | if (it != null) { | 101 | resetState = { addonViewModel.setAddonToDelete(null) } |
121 | MessageDialogFragment.newInstance( | 102 | ) { |
122 | requireActivity(), | 103 | if (it != null) { |
123 | titleId = R.string.confirm_uninstall, | 104 | MessageDialogFragment.newInstance( |
124 | descriptionId = R.string.confirm_uninstall_description, | 105 | requireActivity(), |
125 | positiveAction = { addonViewModel.onDeleteAddon(it) }, | 106 | titleId = R.string.confirm_uninstall, |
126 | negativeAction = {} | 107 | descriptionId = R.string.confirm_uninstall_description, |
127 | ).show(parentFragmentManager, MessageDialogFragment.TAG) | 108 | positiveAction = { addonViewModel.onDeleteAddon(it) }, |
128 | addonViewModel.setAddonToDelete(null) | 109 | negativeAction = {} |
129 | } | 110 | ).show(parentFragmentManager, MessageDialogFragment.TAG) |
130 | } | ||
131 | } | ||
132 | } | 111 | } |
133 | } | 112 | } |
134 | 113 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt new file mode 100755 index 000000000..299f8da71 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/CoreErrorDialogFragment.kt | |||
@@ -0,0 +1,47 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.fragments | ||
5 | |||
6 | import android.app.Dialog | ||
7 | import android.content.DialogInterface | ||
8 | import android.os.Bundle | ||
9 | import androidx.fragment.app.DialogFragment | ||
10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
11 | import org.yuzu.yuzu_emu.NativeLibrary | ||
12 | import org.yuzu.yuzu_emu.R | ||
13 | |||
14 | class CoreErrorDialogFragment : DialogFragment() { | ||
15 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = | ||
16 | MaterialAlertDialogBuilder(requireActivity()) | ||
17 | .setTitle(requireArguments().getString(TITLE)) | ||
18 | .setMessage(requireArguments().getString(MESSAGE)) | ||
19 | .setPositiveButton(R.string.continue_button, null) | ||
20 | .setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> | ||
21 | NativeLibrary.coreErrorAlertResult = false | ||
22 | synchronized(NativeLibrary.coreErrorAlertLock) { | ||
23 | NativeLibrary.coreErrorAlertLock.notify() | ||
24 | } | ||
25 | } | ||
26 | .create() | ||
27 | |||
28 | override fun onDismiss(dialog: DialogInterface) { | ||
29 | super.onDismiss(dialog) | ||
30 | NativeLibrary.coreErrorAlertResult = true | ||
31 | synchronized(NativeLibrary.coreErrorAlertLock) { NativeLibrary.coreErrorAlertLock.notify() } | ||
32 | } | ||
33 | |||
34 | companion object { | ||
35 | const val TITLE = "Title" | ||
36 | const val MESSAGE = "Message" | ||
37 | |||
38 | fun newInstance(title: String, message: String): CoreErrorDialogFragment { | ||
39 | val frag = CoreErrorDialogFragment() | ||
40 | val args = Bundle() | ||
41 | args.putString(TITLE, title) | ||
42 | args.putString(MESSAGE, message) | ||
43 | frag.arguments = args | ||
44 | return frag | ||
45 | } | ||
46 | } | ||
47 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index 41cff46c1..8b23a1021 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
7 | import android.os.Bundle | 6 | import android.os.Bundle |
8 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
9 | import android.view.View | 8 | import android.view.View |
@@ -14,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat | |||
14 | import androidx.core.view.updatePadding | 13 | import androidx.core.view.updatePadding |
15 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
16 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
17 | import androidx.lifecycle.Lifecycle | ||
18 | import androidx.lifecycle.lifecycleScope | ||
19 | import androidx.lifecycle.repeatOnLifecycle | ||
20 | import androidx.navigation.findNavController | 16 | import androidx.navigation.findNavController |
21 | import androidx.navigation.fragment.navArgs | 17 | import androidx.navigation.fragment.navArgs |
22 | import androidx.recyclerview.widget.GridLayoutManager | 18 | import androidx.recyclerview.widget.GridLayoutManager |
@@ -35,6 +31,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil | |||
35 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 31 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
36 | import org.yuzu.yuzu_emu.utils.NativeConfig | 32 | import org.yuzu.yuzu_emu.utils.NativeConfig |
37 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 33 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
34 | import org.yuzu.yuzu_emu.utils.collect | ||
38 | import java.io.File | 35 | import java.io.File |
39 | import java.io.IOException | 36 | import java.io.IOException |
40 | 37 | ||
@@ -63,8 +60,6 @@ class DriverManagerFragment : Fragment() { | |||
63 | return binding.root | 60 | return binding.root |
64 | } | 61 | } |
65 | 62 | ||
66 | // This is using the correct scope, lint is just acting up | ||
67 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
68 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 63 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
69 | super.onViewCreated(view, savedInstanceState) | 64 | super.onViewCreated(view, savedInstanceState) |
70 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | 65 | homeViewModel.setNavigationVisibility(visible = false, animated = true) |
@@ -89,15 +84,8 @@ class DriverManagerFragment : Fragment() { | |||
89 | } | 84 | } |
90 | } | 85 | } |
91 | 86 | ||
92 | viewLifecycleOwner.lifecycleScope.apply { | 87 | driverViewModel.showClearButton.collect(viewLifecycleOwner) { |
93 | launch { | 88 | binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it |
94 | repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
95 | driverViewModel.showClearButton.collect { | ||
96 | binding.toolbarDrivers.menu | ||
97 | .findItem(R.id.menu_driver_use_global).isVisible = it | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | } | 89 | } |
102 | } | 90 | } |
103 | 91 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt index 6a47b29f0..bad56e434 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt | |||
@@ -10,14 +10,11 @@ import android.view.View | |||
10 | import android.view.ViewGroup | 10 | import android.view.ViewGroup |
11 | import androidx.fragment.app.DialogFragment | 11 | import androidx.fragment.app.DialogFragment |
12 | import androidx.fragment.app.activityViewModels | 12 | import androidx.fragment.app.activityViewModels |
13 | import androidx.lifecycle.Lifecycle | ||
14 | import androidx.lifecycle.lifecycleScope | ||
15 | import androidx.lifecycle.repeatOnLifecycle | ||
16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 13 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
17 | import kotlinx.coroutines.launch | ||
18 | import org.yuzu.yuzu_emu.R | 14 | import org.yuzu.yuzu_emu.R |
19 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 15 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
20 | import org.yuzu.yuzu_emu.model.DriverViewModel | 16 | import org.yuzu.yuzu_emu.model.DriverViewModel |
17 | import org.yuzu.yuzu_emu.utils.collect | ||
21 | 18 | ||
22 | class DriversLoadingDialogFragment : DialogFragment() { | 19 | class DriversLoadingDialogFragment : DialogFragment() { |
23 | private val driverViewModel: DriverViewModel by activityViewModels() | 20 | private val driverViewModel: DriverViewModel by activityViewModels() |
@@ -44,13 +41,7 @@ class DriversLoadingDialogFragment : DialogFragment() { | |||
44 | 41 | ||
45 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
46 | super.onViewCreated(view, savedInstanceState) | 43 | super.onViewCreated(view, savedInstanceState) |
47 | viewLifecycleOwner.lifecycleScope.apply { | 44 | driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() } |
48 | launch { | ||
49 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | ||
50 | driverViewModel.isInteractionAllowed.collect { if (it) dismiss() } | ||
51 | } | ||
52 | } | ||
53 | } | ||
54 | } | 45 | } |
55 | 46 | ||
56 | companion object { | 47 | companion object { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 6b25cc525..c3b2b11f8 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt | |||
@@ -32,9 +32,6 @@ import androidx.drawerlayout.widget.DrawerLayout | |||
32 | import androidx.drawerlayout.widget.DrawerLayout.DrawerListener | 32 | import androidx.drawerlayout.widget.DrawerLayout.DrawerListener |
33 | import androidx.fragment.app.Fragment | 33 | import androidx.fragment.app.Fragment |
34 | import androidx.fragment.app.activityViewModels | 34 | import androidx.fragment.app.activityViewModels |
35 | import androidx.lifecycle.Lifecycle | ||
36 | import androidx.lifecycle.lifecycleScope | ||
37 | import androidx.lifecycle.repeatOnLifecycle | ||
38 | import androidx.navigation.findNavController | 35 | import androidx.navigation.findNavController |
39 | import androidx.navigation.fragment.navArgs | 36 | import androidx.navigation.fragment.navArgs |
40 | import androidx.window.layout.FoldingFeature | 37 | import androidx.window.layout.FoldingFeature |
@@ -42,9 +39,6 @@ import androidx.window.layout.WindowInfoTracker | |||
42 | import androidx.window.layout.WindowLayoutInfo | 39 | import androidx.window.layout.WindowLayoutInfo |
43 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 40 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
44 | import com.google.android.material.slider.Slider | 41 | import com.google.android.material.slider.Slider |
45 | import kotlinx.coroutines.Dispatchers | ||
46 | import kotlinx.coroutines.flow.collectLatest | ||
47 | import kotlinx.coroutines.launch | ||
48 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 42 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
49 | import org.yuzu.yuzu_emu.NativeLibrary | 43 | import org.yuzu.yuzu_emu.NativeLibrary |
50 | import org.yuzu.yuzu_emu.R | 44 | import org.yuzu.yuzu_emu.R |
@@ -63,6 +57,7 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel | |||
63 | import org.yuzu.yuzu_emu.overlay.model.OverlayControl | 57 | import org.yuzu.yuzu_emu.overlay.model.OverlayControl |
64 | import org.yuzu.yuzu_emu.overlay.model.OverlayLayout | 58 | import org.yuzu.yuzu_emu.overlay.model.OverlayLayout |
65 | import org.yuzu.yuzu_emu.utils.* | 59 | import org.yuzu.yuzu_emu.utils.* |
60 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
66 | import java.lang.NullPointerException | 61 | import java.lang.NullPointerException |
67 | 62 | ||
68 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { | 63 | class EmulationFragment : Fragment(), SurfaceHolder.Callback { |
@@ -90,14 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
90 | if (context is EmulationActivity) { | 85 | if (context is EmulationActivity) { |
91 | emulationActivity = context | 86 | emulationActivity = context |
92 | NativeLibrary.setEmulationActivity(context) | 87 | NativeLibrary.setEmulationActivity(context) |
93 | |||
94 | lifecycleScope.launch(Dispatchers.Main) { | ||
95 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
96 | WindowInfoTracker.getOrCreate(context) | ||
97 | .windowLayoutInfo(context) | ||
98 | .collect { updateFoldableLayout(context, it) } | ||
99 | } | ||
100 | } | ||
101 | } else { | 88 | } else { |
102 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 89 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
103 | } | 90 | } |
@@ -168,8 +155,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
168 | return binding.root | 155 | return binding.root |
169 | } | 156 | } |
170 | 157 | ||
171 | // This is using the correct scope, lint is just acting up | ||
172 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
173 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 158 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
174 | super.onViewCreated(view, savedInstanceState) | 159 | super.onViewCreated(view, savedInstanceState) |
175 | if (requireActivity().isFinishing) { | 160 | if (requireActivity().isFinishing) { |
@@ -277,6 +262,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
277 | true | 262 | true |
278 | } | 263 | } |
279 | 264 | ||
265 | R.id.menu_controls -> { | ||
266 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
267 | null, | ||
268 | Settings.MenuTag.SECTION_INPUT | ||
269 | ) | ||
270 | binding.root.findNavController().navigate(action) | ||
271 | true | ||
272 | } | ||
273 | |||
280 | R.id.menu_overlay_controls -> { | 274 | R.id.menu_overlay_controls -> { |
281 | showOverlayOptions() | 275 | showOverlayOptions() |
282 | true | 276 | true |
@@ -341,129 +335,86 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
341 | binding.loadingTitle.isSelected = true | 335 | binding.loadingTitle.isSelected = true |
342 | binding.loadingText.isSelected = true | 336 | binding.loadingText.isSelected = true |
343 | 337 | ||
344 | viewLifecycleOwner.lifecycleScope.apply { | 338 | WindowInfoTracker.getOrCreate(requireContext()) |
345 | launch { | 339 | .windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) { |
346 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 340 | updateFoldableLayout(requireActivity() as EmulationActivity, it) |
347 | WindowInfoTracker.getOrCreate(requireContext()) | ||
348 | .windowLayoutInfo(requireActivity()) | ||
349 | .collect { | ||
350 | updateFoldableLayout(requireActivity() as EmulationActivity, it) | ||
351 | } | ||
352 | } | ||
353 | } | 341 | } |
354 | launch { | 342 | emulationViewModel.shaderProgress.collect(viewLifecycleOwner) { |
355 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 343 | if (it > 0 && it != emulationViewModel.totalShaders.value) { |
356 | emulationViewModel.shaderProgress.collectLatest { | 344 | binding.loadingProgressIndicator.isIndeterminate = false |
357 | if (it > 0 && it != emulationViewModel.totalShaders.value) { | ||
358 | binding.loadingProgressIndicator.isIndeterminate = false | ||
359 | |||
360 | if (it < binding.loadingProgressIndicator.max) { | ||
361 | binding.loadingProgressIndicator.progress = it | ||
362 | } | ||
363 | } | ||
364 | 345 | ||
365 | if (it == emulationViewModel.totalShaders.value) { | 346 | if (it < binding.loadingProgressIndicator.max) { |
366 | binding.loadingText.setText(R.string.loading) | 347 | binding.loadingProgressIndicator.progress = it |
367 | binding.loadingProgressIndicator.isIndeterminate = true | ||
368 | } | ||
369 | } | ||
370 | } | 348 | } |
371 | } | 349 | } |
372 | launch { | 350 | |
373 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 351 | if (it == emulationViewModel.totalShaders.value) { |
374 | emulationViewModel.totalShaders.collectLatest { | 352 | binding.loadingText.setText(R.string.loading) |
375 | binding.loadingProgressIndicator.max = it | 353 | binding.loadingProgressIndicator.isIndeterminate = true |
376 | } | ||
377 | } | ||
378 | } | ||
379 | launch { | ||
380 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
381 | emulationViewModel.shaderMessage.collectLatest { | ||
382 | if (it.isNotEmpty()) { | ||
383 | binding.loadingText.text = it | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | } | 354 | } |
388 | launch { | 355 | } |
389 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | 356 | emulationViewModel.totalShaders.collect(viewLifecycleOwner) { |
390 | driverViewModel.isInteractionAllowed.collect { | 357 | binding.loadingProgressIndicator.max = it |
391 | if (it) { | 358 | } |
392 | startEmulation() | 359 | emulationViewModel.shaderMessage.collect(viewLifecycleOwner) { |
393 | } | 360 | if (it.isNotEmpty()) { |
394 | } | 361 | binding.loadingText.text = it |
395 | } | ||
396 | } | 362 | } |
397 | launch { | 363 | } |
398 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 364 | |
399 | emulationViewModel.emulationStarted.collectLatest { | 365 | emulationViewModel.emulationStarted.collect(viewLifecycleOwner) { |
400 | if (it) { | 366 | if (it) { |
401 | binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) | 367 | binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt()) |
402 | ViewUtils.showView(binding.surfaceInputOverlay) | 368 | ViewUtils.showView(binding.surfaceInputOverlay) |
403 | ViewUtils.hideView(binding.loadingIndicator) | 369 | ViewUtils.hideView(binding.loadingIndicator) |
404 | 370 | ||
405 | emulationState.updateSurface() | 371 | emulationState.updateSurface() |
406 | 372 | ||
407 | // Setup overlays | 373 | // Setup overlays |
408 | updateShowFpsOverlay() | 374 | updateShowFpsOverlay() |
409 | updateThermalOverlay() | 375 | updateThermalOverlay() |
410 | } | ||
411 | } | ||
412 | } | ||
413 | } | 376 | } |
414 | launch { | 377 | } |
415 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 378 | emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) { |
416 | emulationViewModel.isEmulationStopping.collectLatest { | 379 | if (it) { |
417 | if (it) { | 380 | binding.loadingText.setText(R.string.shutting_down) |
418 | binding.loadingText.setText(R.string.shutting_down) | 381 | ViewUtils.showView(binding.loadingIndicator) |
419 | ViewUtils.showView(binding.loadingIndicator) | 382 | ViewUtils.hideView(binding.inputContainer) |
420 | ViewUtils.hideView(binding.inputContainer) | 383 | ViewUtils.hideView(binding.showFpsText) |
421 | ViewUtils.hideView(binding.showFpsText) | ||
422 | } | ||
423 | } | ||
424 | } | ||
425 | } | 384 | } |
426 | launch { | 385 | } |
427 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 386 | emulationViewModel.drawerOpen.collect(viewLifecycleOwner) { |
428 | emulationViewModel.drawerOpen.collect { | 387 | if (it) { |
429 | if (it) { | 388 | binding.drawerLayout.open() |
430 | binding.drawerLayout.open() | 389 | binding.inGameMenu.requestFocus() |
431 | binding.inGameMenu.requestFocus() | 390 | } else { |
432 | } else { | 391 | binding.drawerLayout.close() |
433 | binding.drawerLayout.close() | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | } | 392 | } |
438 | launch { | 393 | } |
439 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 394 | emulationViewModel.programChanged.collect(viewLifecycleOwner) { |
440 | emulationViewModel.programChanged.collect { | 395 | if (it != 0) { |
441 | if (it != 0) { | 396 | emulationViewModel.setEmulationStarted(false) |
442 | emulationViewModel.setEmulationStarted(false) | 397 | binding.drawerLayout.close() |
443 | binding.drawerLayout.close() | 398 | binding.drawerLayout |
444 | binding.drawerLayout | 399 | .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) |
445 | .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) | 400 | ViewUtils.hideView(binding.surfaceInputOverlay) |
446 | ViewUtils.hideView(binding.surfaceInputOverlay) | 401 | ViewUtils.showView(binding.loadingIndicator) |
447 | ViewUtils.showView(binding.loadingIndicator) | ||
448 | } | ||
449 | } | ||
450 | } | ||
451 | } | 402 | } |
452 | launch { | 403 | } |
453 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 404 | emulationViewModel.emulationStopped.collect(viewLifecycleOwner) { |
454 | emulationViewModel.emulationStopped.collect { | 405 | if (it && emulationViewModel.programChanged.value != -1) { |
455 | if (it && emulationViewModel.programChanged.value != -1) { | 406 | if (perfStatsUpdater != null) { |
456 | if (perfStatsUpdater != null) { | 407 | perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) |
457 | perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) | ||
458 | } | ||
459 | emulationState.changeProgram(emulationViewModel.programChanged.value) | ||
460 | emulationViewModel.setProgramChanged(-1) | ||
461 | emulationViewModel.setEmulationStopped(false) | ||
462 | } | ||
463 | } | ||
464 | } | 408 | } |
409 | emulationState.changeProgram(emulationViewModel.programChanged.value) | ||
410 | emulationViewModel.setProgramChanged(-1) | ||
411 | emulationViewModel.setEmulationStopped(false) | ||
465 | } | 412 | } |
466 | } | 413 | } |
414 | |||
415 | driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { | ||
416 | if (it) startEmulation() | ||
417 | } | ||
467 | } | 418 | } |
468 | 419 | ||
469 | private fun startEmulation(programIndex: Int = 0) { | 420 | private fun startEmulation(programIndex: Int = 0) { |
@@ -491,14 +442,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
491 | binding.drawerLayout.close() | 442 | binding.drawerLayout.close() |
492 | } | 443 | } |
493 | if (showInputOverlay) { | 444 | if (showInputOverlay) { |
494 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | 445 | binding.surfaceInputOverlay.setVisible(visible = false, gone = false) |
495 | } | 446 | } |
496 | } else { | 447 | } else { |
497 | if (showInputOverlay && emulationViewModel.emulationStarted.value) { | 448 | binding.surfaceInputOverlay.setVisible( |
498 | binding.surfaceInputOverlay.visibility = View.VISIBLE | 449 | showInputOverlay && emulationViewModel.emulationStarted.value |
499 | } else { | 450 | ) |
500 | binding.surfaceInputOverlay.visibility = View.INVISIBLE | ||
501 | } | ||
502 | if (!isInFoldableLayout) { | 451 | if (!isInFoldableLayout) { |
503 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { | 452 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { |
504 | binding.surfaceInputOverlay.layout = OverlayLayout.Portrait | 453 | binding.surfaceInputOverlay.layout = OverlayLayout.Portrait |
@@ -535,7 +484,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
535 | } | 484 | } |
536 | 485 | ||
537 | private fun updateShowFpsOverlay() { | 486 | private fun updateShowFpsOverlay() { |
538 | if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) { | 487 | val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean() |
488 | binding.showFpsText.setVisible(showOverlay) | ||
489 | if (showOverlay) { | ||
539 | val SYSTEM_FPS = 0 | 490 | val SYSTEM_FPS = 0 |
540 | val FPS = 1 | 491 | val FPS = 1 |
541 | val FRAMETIME = 2 | 492 | val FRAMETIME = 2 |
@@ -555,17 +506,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
555 | } | 506 | } |
556 | } | 507 | } |
557 | perfStatsUpdateHandler.post(perfStatsUpdater!!) | 508 | perfStatsUpdateHandler.post(perfStatsUpdater!!) |
558 | binding.showFpsText.visibility = View.VISIBLE | ||
559 | } else { | 509 | } else { |
560 | if (perfStatsUpdater != null) { | 510 | if (perfStatsUpdater != null) { |
561 | perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) | 511 | perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) |
562 | } | 512 | } |
563 | binding.showFpsText.visibility = View.GONE | ||
564 | } | 513 | } |
565 | } | 514 | } |
566 | 515 | ||
567 | private fun updateThermalOverlay() { | 516 | private fun updateThermalOverlay() { |
568 | if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) { | 517 | val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean() |
518 | binding.showThermalsText.setVisible(showOverlay) | ||
519 | if (showOverlay) { | ||
569 | thermalStatsUpdater = { | 520 | thermalStatsUpdater = { |
570 | if (emulationViewModel.emulationStarted.value && | 521 | if (emulationViewModel.emulationStarted.value && |
571 | !emulationViewModel.isEmulationStopping.value | 522 | !emulationViewModel.isEmulationStopping.value |
@@ -587,12 +538,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
587 | } | 538 | } |
588 | } | 539 | } |
589 | thermalStatsUpdateHandler.post(thermalStatsUpdater!!) | 540 | thermalStatsUpdateHandler.post(thermalStatsUpdater!!) |
590 | binding.showThermalsText.visibility = View.VISIBLE | ||
591 | } else { | 541 | } else { |
592 | if (thermalStatsUpdater != null) { | 542 | if (thermalStatsUpdater != null) { |
593 | thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!) | 543 | thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!) |
594 | } | 544 | } |
595 | binding.showThermalsText.visibility = View.GONE | ||
596 | } | 545 | } |
597 | } | 546 | } |
598 | 547 | ||
@@ -861,12 +810,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
861 | } | 810 | } |
862 | } | 811 | } |
863 | } | 812 | } |
864 | binding.doneControlConfig.visibility = View.VISIBLE | 813 | binding.doneControlConfig.setVisible(false) |
865 | binding.surfaceInputOverlay.setIsInEditMode(true) | 814 | binding.surfaceInputOverlay.setIsInEditMode(true) |
866 | } | 815 | } |
867 | 816 | ||
868 | private fun stopConfiguringControls() { | 817 | private fun stopConfiguringControls() { |
869 | binding.doneControlConfig.visibility = View.GONE | 818 | binding.doneControlConfig.setVisible(false) |
870 | binding.surfaceInputOverlay.setIsInEditMode(false) | 819 | binding.surfaceInputOverlay.setIsInEditMode(false) |
871 | // Unlock the orientation if it was locked for editing | 820 | // Unlock the orientation if it was locked for editing |
872 | if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { | 821 | if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt index 5c558b1a5..3a6f7a38c 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt | |||
@@ -13,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat | |||
13 | import androidx.core.view.updatePadding | 13 | import androidx.core.view.updatePadding |
14 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
15 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
16 | import androidx.lifecycle.Lifecycle | ||
17 | import androidx.lifecycle.lifecycleScope | ||
18 | import androidx.lifecycle.repeatOnLifecycle | ||
19 | import androidx.navigation.findNavController | 16 | import androidx.navigation.findNavController |
20 | import androidx.recyclerview.widget.GridLayoutManager | 17 | import androidx.recyclerview.widget.GridLayoutManager |
21 | import com.google.android.material.transition.MaterialSharedAxis | 18 | import com.google.android.material.transition.MaterialSharedAxis |
@@ -27,6 +24,7 @@ import org.yuzu.yuzu_emu.model.GamesViewModel | |||
27 | import org.yuzu.yuzu_emu.model.HomeViewModel | 24 | import org.yuzu.yuzu_emu.model.HomeViewModel |
28 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 25 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
29 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 26 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
27 | import org.yuzu.yuzu_emu.utils.collect | ||
30 | 28 | ||
31 | class GameFoldersFragment : Fragment() { | 29 | class GameFoldersFragment : Fragment() { |
32 | private var _binding: FragmentFoldersBinding? = null | 30 | private var _binding: FragmentFoldersBinding? = null |
@@ -70,12 +68,8 @@ class GameFoldersFragment : Fragment() { | |||
70 | adapter = FolderAdapter(requireActivity(), gamesViewModel) | 68 | adapter = FolderAdapter(requireActivity(), gamesViewModel) |
71 | } | 69 | } |
72 | 70 | ||
73 | viewLifecycleOwner.lifecycleScope.launch { | 71 | gamesViewModel.folders.collect(viewLifecycleOwner) { |
74 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 72 | (binding.listFolders.adapter as FolderAdapter).submitList(it) |
75 | gamesViewModel.folders.collect { | ||
76 | (binding.listFolders.adapter as FolderAdapter).submitList(it) | ||
77 | } | ||
78 | } | ||
79 | } | 73 | } |
80 | 74 | ||
81 | val mainActivity = requireActivity() as MainActivity | 75 | val mainActivity = requireActivity() as MainActivity |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt index dbd56e84f..97a8954bb 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt | |||
@@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding | |||
27 | import org.yuzu.yuzu_emu.model.GameVerificationResult | 27 | import org.yuzu.yuzu_emu.model.GameVerificationResult |
28 | import org.yuzu.yuzu_emu.model.HomeViewModel | 28 | import org.yuzu.yuzu_emu.model.HomeViewModel |
29 | import org.yuzu.yuzu_emu.utils.GameMetadata | 29 | import org.yuzu.yuzu_emu.utils.GameMetadata |
30 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
30 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 31 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
31 | 32 | ||
32 | class GameInfoFragment : Fragment() { | 33 | class GameInfoFragment : Fragment() { |
@@ -85,7 +86,7 @@ class GameInfoFragment : Fragment() { | |||
85 | copyToClipboard(getString(R.string.developer), args.game.developer) | 86 | copyToClipboard(getString(R.string.developer), args.game.developer) |
86 | } | 87 | } |
87 | } else { | 88 | } else { |
88 | developer.visibility = View.GONE | 89 | developer.setVisible(false) |
89 | } | 90 | } |
90 | 91 | ||
91 | version.setHint(R.string.version) | 92 | version.setHint(R.string.version) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index 3ea5e16ca..c06842c59 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt | |||
@@ -3,11 +3,9 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
7 | import android.content.pm.ShortcutInfo | 6 | import android.content.pm.ShortcutInfo |
8 | import android.content.pm.ShortcutManager | 7 | import android.content.pm.ShortcutManager |
9 | import android.os.Bundle | 8 | import android.os.Bundle |
10 | import android.text.TextUtils | ||
11 | import android.view.LayoutInflater | 9 | import android.view.LayoutInflater |
12 | import android.view.View | 10 | import android.view.View |
13 | import android.view.ViewGroup | 11 | import android.view.ViewGroup |
@@ -18,9 +16,7 @@ import androidx.core.view.WindowInsetsCompat | |||
18 | import androidx.core.view.updatePadding | 16 | import androidx.core.view.updatePadding |
19 | import androidx.fragment.app.Fragment | 17 | import androidx.fragment.app.Fragment |
20 | import androidx.fragment.app.activityViewModels | 18 | import androidx.fragment.app.activityViewModels |
21 | import androidx.lifecycle.Lifecycle | ||
22 | import androidx.lifecycle.lifecycleScope | 19 | import androidx.lifecycle.lifecycleScope |
23 | import androidx.lifecycle.repeatOnLifecycle | ||
24 | import androidx.navigation.findNavController | 20 | import androidx.navigation.findNavController |
25 | import androidx.navigation.fragment.navArgs | 21 | import androidx.navigation.fragment.navArgs |
26 | import androidx.recyclerview.widget.GridLayoutManager | 22 | import androidx.recyclerview.widget.GridLayoutManager |
@@ -46,7 +42,9 @@ import org.yuzu.yuzu_emu.utils.FileUtil | |||
46 | import org.yuzu.yuzu_emu.utils.GameIconUtils | 42 | import org.yuzu.yuzu_emu.utils.GameIconUtils |
47 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 43 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
48 | import org.yuzu.yuzu_emu.utils.MemoryUtil | 44 | import org.yuzu.yuzu_emu.utils.MemoryUtil |
45 | import org.yuzu.yuzu_emu.utils.ViewUtils.marquee | ||
49 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 46 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
47 | import org.yuzu.yuzu_emu.utils.collect | ||
50 | import java.io.BufferedOutputStream | 48 | import java.io.BufferedOutputStream |
51 | import java.io.File | 49 | import java.io.File |
52 | 50 | ||
@@ -76,8 +74,6 @@ class GamePropertiesFragment : Fragment() { | |||
76 | return binding.root | 74 | return binding.root |
77 | } | 75 | } |
78 | 76 | ||
79 | // This is using the correct scope, lint is just acting up | ||
80 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
81 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 77 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
82 | super.onViewCreated(view, savedInstanceState) | 78 | super.onViewCreated(view, savedInstanceState) |
83 | homeViewModel.setNavigationVisibility(visible = false, animated = true) | 79 | homeViewModel.setNavigationVisibility(visible = false, animated = true) |
@@ -107,13 +103,7 @@ class GamePropertiesFragment : Fragment() { | |||
107 | 103 | ||
108 | GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen) | 104 | GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen) |
109 | binding.title.text = args.game.title | 105 | binding.title.text = args.game.title |
110 | binding.title.postDelayed( | 106 | binding.title.marquee() |
111 | { | ||
112 | binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE | ||
113 | binding.title.isSelected = true | ||
114 | }, | ||
115 | 3000 | ||
116 | ) | ||
117 | 107 | ||
118 | binding.buttonStart.setOnClickListener { | 108 | binding.buttonStart.setOnClickListener { |
119 | LaunchGameDialogFragment.newInstance(args.game) | 109 | LaunchGameDialogFragment.newInstance(args.game) |
@@ -122,28 +112,14 @@ class GamePropertiesFragment : Fragment() { | |||
122 | 112 | ||
123 | reloadList() | 113 | reloadList() |
124 | 114 | ||
125 | viewLifecycleOwner.lifecycleScope.apply { | 115 | homeViewModel.openImportSaves.collect( |
126 | launch { | 116 | viewLifecycleOwner, |
127 | repeatOnLifecycle(Lifecycle.State.STARTED) { | 117 | resetState = { homeViewModel.setOpenImportSaves(false) } |
128 | homeViewModel.openImportSaves.collect { | 118 | ) { if (it) importSaves.launch(arrayOf("application/zip")) } |
129 | if (it) { | 119 | homeViewModel.reloadPropertiesList.collect( |
130 | importSaves.launch(arrayOf("application/zip")) | 120 | viewLifecycleOwner, |
131 | homeViewModel.setOpenImportSaves(false) | 121 | resetState = { homeViewModel.reloadPropertiesList(false) } |
132 | } | 122 | ) { if (it) reloadList() } |
133 | } | ||
134 | } | ||
135 | } | ||
136 | launch { | ||
137 | repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
138 | homeViewModel.reloadPropertiesList.collect { | ||
139 | if (it) { | ||
140 | reloadList() | ||
141 | homeViewModel.reloadPropertiesList(false) | ||
142 | } | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | 123 | ||
148 | setInsets() | 124 | setInsets() |
149 | } | 125 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 87e130d3e..14a2504b6 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
@@ -91,6 +91,20 @@ class HomeSettingsFragment : Fragment() { | |||
91 | ) | 91 | ) |
92 | add( | 92 | add( |
93 | HomeSetting( | 93 | HomeSetting( |
94 | R.string.preferences_controls, | ||
95 | R.string.preferences_controls_description, | ||
96 | R.drawable.ic_controller, | ||
97 | { | ||
98 | val action = HomeNavigationDirections.actionGlobalSettingsActivity( | ||
99 | null, | ||
100 | Settings.MenuTag.SECTION_INPUT | ||
101 | ) | ||
102 | binding.root.findNavController().navigate(action) | ||
103 | } | ||
104 | ) | ||
105 | ) | ||
106 | add( | ||
107 | HomeSetting( | ||
94 | R.string.gpu_driver_manager, | 108 | R.string.gpu_driver_manager, |
95 | R.string.install_gpu_driver_description, | 109 | R.string.install_gpu_driver_description, |
96 | R.drawable.ic_build, | 110 | R.drawable.ic_build, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 63112dc6f..d218da1c8 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt | |||
@@ -14,9 +14,6 @@ import androidx.core.view.WindowInsetsCompat | |||
14 | import androidx.core.view.updatePadding | 14 | import androidx.core.view.updatePadding |
15 | import androidx.fragment.app.Fragment | 15 | import androidx.fragment.app.Fragment |
16 | import androidx.fragment.app.activityViewModels | 16 | import androidx.fragment.app.activityViewModels |
17 | import androidx.lifecycle.Lifecycle | ||
18 | import androidx.lifecycle.lifecycleScope | ||
19 | import androidx.lifecycle.repeatOnLifecycle | ||
20 | import androidx.navigation.findNavController | 17 | import androidx.navigation.findNavController |
21 | import androidx.recyclerview.widget.GridLayoutManager | 18 | import androidx.recyclerview.widget.GridLayoutManager |
22 | import com.google.android.material.transition.MaterialSharedAxis | 19 | import com.google.android.material.transition.MaterialSharedAxis |
@@ -35,6 +32,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity | |||
35 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 32 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
36 | import org.yuzu.yuzu_emu.utils.FileUtil | 33 | import org.yuzu.yuzu_emu.utils.FileUtil |
37 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 34 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
35 | import org.yuzu.yuzu_emu.utils.collect | ||
38 | import java.io.BufferedOutputStream | 36 | import java.io.BufferedOutputStream |
39 | import java.io.File | 37 | import java.io.File |
40 | import java.math.BigInteger | 38 | import java.math.BigInteger |
@@ -75,14 +73,10 @@ class InstallableFragment : Fragment() { | |||
75 | binding.root.findNavController().popBackStack() | 73 | binding.root.findNavController().popBackStack() |
76 | } | 74 | } |
77 | 75 | ||
78 | viewLifecycleOwner.lifecycleScope.launch { | 76 | homeViewModel.openImportSaves.collect(viewLifecycleOwner) { |
79 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 77 | if (it) { |
80 | homeViewModel.openImportSaves.collect { | 78 | importSaves.launch(arrayOf("application/zip")) |
81 | if (it) { | 79 | homeViewModel.setOpenImportSaves(false) |
82 | importSaves.launch(arrayOf("application/zip")) | ||
83 | homeViewModel.setOpenImportSaves(false) | ||
84 | } | ||
85 | } | ||
86 | } | 80 | } |
87 | } | 81 | } |
88 | 82 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt index d201cb80c..ee3bb0386 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ProgressDialogFragment.kt | |||
@@ -13,15 +13,13 @@ import androidx.appcompat.app.AlertDialog | |||
13 | import androidx.fragment.app.DialogFragment | 13 | import androidx.fragment.app.DialogFragment |
14 | import androidx.fragment.app.FragmentActivity | 14 | import androidx.fragment.app.FragmentActivity |
15 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
16 | import androidx.lifecycle.Lifecycle | ||
17 | import androidx.lifecycle.ViewModelProvider | 16 | import androidx.lifecycle.ViewModelProvider |
18 | import androidx.lifecycle.lifecycleScope | ||
19 | import androidx.lifecycle.repeatOnLifecycle | ||
20 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
21 | import kotlinx.coroutines.launch | ||
22 | import org.yuzu.yuzu_emu.R | 18 | import org.yuzu.yuzu_emu.R |
23 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 19 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
24 | import org.yuzu.yuzu_emu.model.TaskViewModel | 20 | import org.yuzu.yuzu_emu.model.TaskViewModel |
21 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
22 | import org.yuzu.yuzu_emu.utils.collect | ||
25 | 23 | ||
26 | class ProgressDialogFragment : DialogFragment() { | 24 | class ProgressDialogFragment : DialogFragment() { |
27 | private val taskViewModel: TaskViewModel by activityViewModels() | 25 | private val taskViewModel: TaskViewModel by activityViewModels() |
@@ -64,72 +62,50 @@ class ProgressDialogFragment : DialogFragment() { | |||
64 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 62 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
65 | super.onViewCreated(view, savedInstanceState) | 63 | super.onViewCreated(view, savedInstanceState) |
66 | binding.message.isSelected = true | 64 | binding.message.isSelected = true |
67 | viewLifecycleOwner.lifecycleScope.apply { | 65 | taskViewModel.isComplete.collect(viewLifecycleOwner) { |
68 | launch { | 66 | if (it) { |
69 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 67 | dismiss() |
70 | taskViewModel.isComplete.collect { | 68 | when (val result = taskViewModel.result.value) { |
71 | if (it) { | 69 | is String -> Toast.makeText( |
72 | dismiss() | 70 | requireContext(), |
73 | when (val result = taskViewModel.result.value) { | 71 | result, |
74 | is String -> Toast.makeText( | 72 | Toast.LENGTH_LONG |
75 | requireContext(), | 73 | ).show() |
76 | result, | 74 | |
77 | Toast.LENGTH_LONG | 75 | is MessageDialogFragment -> result.show( |
78 | ).show() | 76 | requireActivity().supportFragmentManager, |
79 | 77 | MessageDialogFragment.TAG | |
80 | is MessageDialogFragment -> result.show( | 78 | ) |
81 | requireActivity().supportFragmentManager, | 79 | |
82 | MessageDialogFragment.TAG | 80 | else -> { |
83 | ) | 81 | // Do nothing |
84 | |||
85 | else -> { | ||
86 | // Do nothing | ||
87 | } | ||
88 | } | ||
89 | taskViewModel.clear() | ||
90 | } | ||
91 | } | 82 | } |
92 | } | 83 | } |
84 | taskViewModel.clear() | ||
93 | } | 85 | } |
94 | launch { | 86 | } |
95 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 87 | taskViewModel.cancelled.collect(viewLifecycleOwner) { |
96 | taskViewModel.cancelled.collect { | 88 | if (it) { |
97 | if (it) { | 89 | dialog?.setTitle(R.string.cancelling) |
98 | dialog?.setTitle(R.string.cancelling) | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | launch { | ||
104 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
105 | taskViewModel.progress.collect { | ||
106 | if (it != 0.0) { | ||
107 | binding.progressBar.apply { | ||
108 | isIndeterminate = false | ||
109 | progress = ( | ||
110 | (it / taskViewModel.maxProgress.value) * | ||
111 | PROGRESS_BAR_RESOLUTION | ||
112 | ).toInt() | ||
113 | min = 0 | ||
114 | max = PROGRESS_BAR_RESOLUTION | ||
115 | } | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | } | 90 | } |
120 | launch { | 91 | } |
121 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 92 | taskViewModel.progress.collect(viewLifecycleOwner) { |
122 | taskViewModel.message.collect { | 93 | if (it != 0.0) { |
123 | if (it.isEmpty()) { | 94 | binding.progressBar.apply { |
124 | binding.message.visibility = View.GONE | 95 | isIndeterminate = false |
125 | } else { | 96 | progress = ( |
126 | binding.message.visibility = View.VISIBLE | 97 | (it / taskViewModel.maxProgress.value) * |
127 | binding.message.text = it | 98 | PROGRESS_BAR_RESOLUTION |
128 | } | 99 | ).toInt() |
129 | } | 100 | min = 0 |
101 | max = PROGRESS_BAR_RESOLUTION | ||
130 | } | 102 | } |
131 | } | 103 | } |
132 | } | 104 | } |
105 | taskViewModel.message.collect(viewLifecycleOwner) { | ||
106 | binding.message.setVisible(it.isNotEmpty()) | ||
107 | binding.message.text = it | ||
108 | } | ||
133 | } | 109 | } |
134 | 110 | ||
135 | // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. | 111 | // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index 20b10b1a0..662ae9760 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
7 | import android.content.Context | 6 | import android.content.Context |
8 | import android.content.SharedPreferences | 7 | import android.content.SharedPreferences |
9 | import android.os.Bundle | 8 | import android.os.Bundle |
@@ -18,14 +17,9 @@ import androidx.core.view.updatePadding | |||
18 | import androidx.core.widget.doOnTextChanged | 17 | import androidx.core.widget.doOnTextChanged |
19 | import androidx.fragment.app.Fragment | 18 | import androidx.fragment.app.Fragment |
20 | import androidx.fragment.app.activityViewModels | 19 | import androidx.fragment.app.activityViewModels |
21 | import androidx.lifecycle.Lifecycle | ||
22 | import androidx.lifecycle.lifecycleScope | ||
23 | import androidx.lifecycle.repeatOnLifecycle | ||
24 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
25 | import info.debatty.java.stringsimilarity.Jaccard | 21 | import info.debatty.java.stringsimilarity.Jaccard |
26 | import info.debatty.java.stringsimilarity.JaroWinkler | 22 | import info.debatty.java.stringsimilarity.JaroWinkler |
27 | import kotlinx.coroutines.flow.collectLatest | ||
28 | import kotlinx.coroutines.launch | ||
29 | import java.util.Locale | 23 | import java.util.Locale |
30 | import org.yuzu.yuzu_emu.R | 24 | import org.yuzu.yuzu_emu.R |
31 | import org.yuzu.yuzu_emu.YuzuApplication | 25 | import org.yuzu.yuzu_emu.YuzuApplication |
@@ -35,6 +29,8 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager | |||
35 | import org.yuzu.yuzu_emu.model.Game | 29 | import org.yuzu.yuzu_emu.model.Game |
36 | import org.yuzu.yuzu_emu.model.GamesViewModel | 30 | import org.yuzu.yuzu_emu.model.GamesViewModel |
37 | import org.yuzu.yuzu_emu.model.HomeViewModel | 31 | import org.yuzu.yuzu_emu.model.HomeViewModel |
32 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
33 | import org.yuzu.yuzu_emu.utils.collect | ||
38 | 34 | ||
39 | class SearchFragment : Fragment() { | 35 | class SearchFragment : Fragment() { |
40 | private var _binding: FragmentSearchBinding? = null | 36 | private var _binding: FragmentSearchBinding? = null |
@@ -58,8 +54,6 @@ class SearchFragment : Fragment() { | |||
58 | return binding.root | 54 | return binding.root |
59 | } | 55 | } |
60 | 56 | ||
61 | // This is using the correct scope, lint is just acting up | ||
62 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
63 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 57 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
64 | super.onViewCreated(view, savedInstanceState) | 58 | super.onViewCreated(view, savedInstanceState) |
65 | homeViewModel.setNavigationVisibility(visible = true, animated = true) | 59 | homeViewModel.setNavigationVisibility(visible = true, animated = true) |
@@ -81,42 +75,18 @@ class SearchFragment : Fragment() { | |||
81 | binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() } | 75 | binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() } |
82 | 76 | ||
83 | binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int -> | 77 | binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int -> |
84 | if (text.toString().isNotEmpty()) { | 78 | binding.clearButton.setVisible(text.toString().isNotEmpty()) |
85 | binding.clearButton.visibility = View.VISIBLE | ||
86 | } else { | ||
87 | binding.clearButton.visibility = View.INVISIBLE | ||
88 | } | ||
89 | filterAndSearch() | 79 | filterAndSearch() |
90 | } | 80 | } |
91 | 81 | ||
92 | viewLifecycleOwner.lifecycleScope.apply { | 82 | gamesViewModel.searchFocused.collect( |
93 | launch { | 83 | viewLifecycleOwner, |
94 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 84 | resetState = { gamesViewModel.setSearchFocused(false) } |
95 | gamesViewModel.searchFocused.collect { | 85 | ) { if (it) focusSearch() } |
96 | if (it) { | 86 | gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() } |
97 | focusSearch() | 87 | gamesViewModel.searchedGames.collect(viewLifecycleOwner) { |
98 | gamesViewModel.setSearchFocused(false) | 88 | (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) |
99 | } | 89 | binding.noResultsView.setVisible(it.isNotEmpty()) |
100 | } | ||
101 | } | ||
102 | } | ||
103 | launch { | ||
104 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
105 | gamesViewModel.games.collectLatest { filterAndSearch() } | ||
106 | } | ||
107 | } | ||
108 | launch { | ||
109 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
110 | gamesViewModel.searchedGames.collect { | ||
111 | (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) | ||
112 | if (it.isEmpty()) { | ||
113 | binding.noResultsView.visibility = View.VISIBLE | ||
114 | } else { | ||
115 | binding.noResultsView.visibility = View.GONE | ||
116 | } | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | } | 90 | } |
121 | 91 | ||
122 | binding.clearButton.setOnClickListener { binding.searchText.setText("") } | 92 | binding.clearButton.setOnClickListener { binding.searchText.setText("") } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index ebf41a639..4f7548e98 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt | |||
@@ -4,7 +4,6 @@ | |||
4 | package org.yuzu.yuzu_emu.fragments | 4 | package org.yuzu.yuzu_emu.fragments |
5 | 5 | ||
6 | import android.Manifest | 6 | import android.Manifest |
7 | import android.annotation.SuppressLint | ||
8 | import android.content.Intent | 7 | import android.content.Intent |
9 | import android.os.Build | 8 | import android.os.Build |
10 | import android.os.Bundle | 9 | import android.os.Bundle |
@@ -23,9 +22,6 @@ import androidx.core.view.isVisible | |||
23 | import androidx.core.view.updatePadding | 22 | import androidx.core.view.updatePadding |
24 | import androidx.fragment.app.Fragment | 23 | import androidx.fragment.app.Fragment |
25 | import androidx.fragment.app.activityViewModels | 24 | import androidx.fragment.app.activityViewModels |
26 | import androidx.lifecycle.Lifecycle | ||
27 | import androidx.lifecycle.lifecycleScope | ||
28 | import androidx.lifecycle.repeatOnLifecycle | ||
29 | import androidx.navigation.findNavController | 25 | import androidx.navigation.findNavController |
30 | import androidx.preference.PreferenceManager | 26 | import androidx.preference.PreferenceManager |
31 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | 27 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback |
@@ -46,6 +42,8 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity | |||
46 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 42 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
47 | import org.yuzu.yuzu_emu.utils.NativeConfig | 43 | import org.yuzu.yuzu_emu.utils.NativeConfig |
48 | import org.yuzu.yuzu_emu.utils.ViewUtils | 44 | import org.yuzu.yuzu_emu.utils.ViewUtils |
45 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
46 | import org.yuzu.yuzu_emu.utils.collect | ||
49 | 47 | ||
50 | class SetupFragment : Fragment() { | 48 | class SetupFragment : Fragment() { |
51 | private var _binding: FragmentSetupBinding? = null | 49 | private var _binding: FragmentSetupBinding? = null |
@@ -77,8 +75,6 @@ class SetupFragment : Fragment() { | |||
77 | return binding.root | 75 | return binding.root |
78 | } | 76 | } |
79 | 77 | ||
80 | // This is using the correct scope, lint is just acting up | ||
81 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
82 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 78 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
83 | mainActivity = requireActivity() as MainActivity | 79 | mainActivity = requireActivity() as MainActivity |
84 | 80 | ||
@@ -210,28 +206,14 @@ class SetupFragment : Fragment() { | |||
210 | ) | 206 | ) |
211 | } | 207 | } |
212 | 208 | ||
213 | viewLifecycleOwner.lifecycleScope.apply { | 209 | homeViewModel.shouldPageForward.collect( |
214 | launch { | 210 | viewLifecycleOwner, |
215 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 211 | resetState = { homeViewModel.setShouldPageForward(false) } |
216 | homeViewModel.shouldPageForward.collect { | 212 | ) { if (it) pageForward() } |
217 | if (it) { | 213 | homeViewModel.gamesDirSelected.collect( |
218 | pageForward() | 214 | viewLifecycleOwner, |
219 | homeViewModel.setShouldPageForward(false) | 215 | resetState = { homeViewModel.setGamesDirSelected(false) } |
220 | } | 216 | ) { if (it) gamesDirCallback.onStepCompleted() } |
221 | } | ||
222 | } | ||
223 | } | ||
224 | launch { | ||
225 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
226 | homeViewModel.gamesDirSelected.collect { | ||
227 | if (it) { | ||
228 | gamesDirCallback.onStepCompleted() | ||
229 | homeViewModel.setGamesDirSelected(false) | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | } | ||
234 | } | ||
235 | 217 | ||
236 | binding.viewPager2.apply { | 218 | binding.viewPager2.apply { |
237 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) | 219 | adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) |
@@ -292,12 +274,8 @@ class SetupFragment : Fragment() { | |||
292 | val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY) | 274 | val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY) |
293 | hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!! | 275 | hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!! |
294 | 276 | ||
295 | if (nextIsVisible) { | 277 | binding.buttonNext.setVisible(nextIsVisible) |
296 | binding.buttonNext.visibility = View.VISIBLE | 278 | binding.buttonBack.setVisible(backIsVisible) |
297 | } | ||
298 | if (backIsVisible) { | ||
299 | binding.buttonBack.visibility = View.VISIBLE | ||
300 | } | ||
301 | } else { | 279 | } else { |
302 | hasBeenWarned = BooleanArray(pages.size) | 280 | hasBeenWarned = BooleanArray(pages.size) |
303 | } | 281 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index c87486c90..66907085a 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | |||
@@ -24,10 +24,10 @@ import androidx.core.content.ContextCompat | |||
24 | import androidx.window.layout.WindowMetricsCalculator | 24 | import androidx.window.layout.WindowMetricsCalculator |
25 | import kotlin.math.max | 25 | import kotlin.math.max |
26 | import kotlin.math.min | 26 | import kotlin.math.min |
27 | import org.yuzu.yuzu_emu.NativeLibrary | 27 | import org.yuzu.yuzu_emu.features.input.NativeInput |
28 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType | ||
29 | import org.yuzu.yuzu_emu.NativeLibrary.StickType | ||
30 | import org.yuzu.yuzu_emu.R | 28 | import org.yuzu.yuzu_emu.R |
29 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
30 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
31 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 31 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
32 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | 32 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting |
33 | import org.yuzu.yuzu_emu.overlay.model.OverlayControl | 33 | import org.yuzu.yuzu_emu.overlay.model.OverlayControl |
@@ -100,19 +100,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
100 | 100 | ||
101 | var shouldUpdateView = false | 101 | var shouldUpdateView = false |
102 | val playerIndex = | 102 | val playerIndex = |
103 | if (NativeLibrary.isHandheldOnly()) { | 103 | if (NativeInput.isHandheldOnly()) { |
104 | NativeLibrary.ConsoleDevice | 104 | NativeInput.ConsoleDevice |
105 | } else { | 105 | } else { |
106 | NativeLibrary.Player1Device | 106 | NativeInput.Player1Device |
107 | } | 107 | } |
108 | 108 | ||
109 | for (button in overlayButtons) { | 109 | for (button in overlayButtons) { |
110 | if (!button.updateStatus(event)) { | 110 | if (!button.updateStatus(event)) { |
111 | continue | 111 | continue |
112 | } | 112 | } |
113 | NativeLibrary.onGamePadButtonEvent( | 113 | NativeInput.onOverlayButtonEvent( |
114 | playerIndex, | 114 | playerIndex, |
115 | button.buttonId, | 115 | button.button, |
116 | button.status | 116 | button.status |
117 | ) | 117 | ) |
118 | playHaptics(event) | 118 | playHaptics(event) |
@@ -123,24 +123,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
123 | if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) { | 123 | if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) { |
124 | continue | 124 | continue |
125 | } | 125 | } |
126 | NativeLibrary.onGamePadButtonEvent( | 126 | NativeInput.onOverlayButtonEvent( |
127 | playerIndex, | 127 | playerIndex, |
128 | dpad.upId, | 128 | dpad.up, |
129 | dpad.upStatus | 129 | dpad.upStatus |
130 | ) | 130 | ) |
131 | NativeLibrary.onGamePadButtonEvent( | 131 | NativeInput.onOverlayButtonEvent( |
132 | playerIndex, | 132 | playerIndex, |
133 | dpad.downId, | 133 | dpad.down, |
134 | dpad.downStatus | 134 | dpad.downStatus |
135 | ) | 135 | ) |
136 | NativeLibrary.onGamePadButtonEvent( | 136 | NativeInput.onOverlayButtonEvent( |
137 | playerIndex, | 137 | playerIndex, |
138 | dpad.leftId, | 138 | dpad.left, |
139 | dpad.leftStatus | 139 | dpad.leftStatus |
140 | ) | 140 | ) |
141 | NativeLibrary.onGamePadButtonEvent( | 141 | NativeInput.onOverlayButtonEvent( |
142 | playerIndex, | 142 | playerIndex, |
143 | dpad.rightId, | 143 | dpad.right, |
144 | dpad.rightStatus | 144 | dpad.rightStatus |
145 | ) | 145 | ) |
146 | playHaptics(event) | 146 | playHaptics(event) |
@@ -151,16 +151,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
151 | if (!joystick.updateStatus(event)) { | 151 | if (!joystick.updateStatus(event)) { |
152 | continue | 152 | continue |
153 | } | 153 | } |
154 | val axisID = joystick.joystickId | 154 | NativeInput.onOverlayJoystickEvent( |
155 | NativeLibrary.onGamePadJoystickEvent( | ||
156 | playerIndex, | 155 | playerIndex, |
157 | axisID, | 156 | joystick.joystick, |
158 | joystick.xAxis, | 157 | joystick.xAxis, |
159 | joystick.realYAxis | 158 | joystick.realYAxis |
160 | ) | 159 | ) |
161 | NativeLibrary.onGamePadButtonEvent( | 160 | NativeInput.onOverlayButtonEvent( |
162 | playerIndex, | 161 | playerIndex, |
163 | joystick.buttonId, | 162 | joystick.button, |
164 | joystick.buttonStatus | 163 | joystick.buttonStatus |
165 | ) | 164 | ) |
166 | playHaptics(event) | 165 | playHaptics(event) |
@@ -187,7 +186,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
187 | motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP | 186 | motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP |
188 | 187 | ||
189 | if (isActionDown && !isTouchInputConsumed(pointerId)) { | 188 | if (isActionDown && !isTouchInputConsumed(pointerId)) { |
190 | NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat()) | 189 | NativeInput.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat()) |
191 | } | 190 | } |
192 | 191 | ||
193 | if (isActionMove) { | 192 | if (isActionMove) { |
@@ -196,12 +195,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
196 | if (isTouchInputConsumed(fingerId)) { | 195 | if (isTouchInputConsumed(fingerId)) { |
197 | continue | 196 | continue |
198 | } | 197 | } |
199 | NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i)) | 198 | NativeInput.onTouchMoved(fingerId, event.getX(i), event.getY(i)) |
200 | } | 199 | } |
201 | } | 200 | } |
202 | 201 | ||
203 | if (isActionUp && !isTouchInputConsumed(pointerId)) { | 202 | if (isActionUp && !isTouchInputConsumed(pointerId)) { |
204 | NativeLibrary.onTouchReleased(pointerId) | 203 | NativeInput.onTouchReleased(pointerId) |
205 | } | 204 | } |
206 | 205 | ||
207 | return true | 206 | return true |
@@ -359,7 +358,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
359 | windowSize, | 358 | windowSize, |
360 | R.drawable.facebutton_a, | 359 | R.drawable.facebutton_a, |
361 | R.drawable.facebutton_a_depressed, | 360 | R.drawable.facebutton_a_depressed, |
362 | ButtonType.BUTTON_A, | 361 | NativeButton.A, |
363 | data, | 362 | data, |
364 | position | 363 | position |
365 | ) | 364 | ) |
@@ -373,7 +372,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
373 | windowSize, | 372 | windowSize, |
374 | R.drawable.facebutton_b, | 373 | R.drawable.facebutton_b, |
375 | R.drawable.facebutton_b_depressed, | 374 | R.drawable.facebutton_b_depressed, |
376 | ButtonType.BUTTON_B, | 375 | NativeButton.B, |
377 | data, | 376 | data, |
378 | position | 377 | position |
379 | ) | 378 | ) |
@@ -387,7 +386,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
387 | windowSize, | 386 | windowSize, |
388 | R.drawable.facebutton_x, | 387 | R.drawable.facebutton_x, |
389 | R.drawable.facebutton_x_depressed, | 388 | R.drawable.facebutton_x_depressed, |
390 | ButtonType.BUTTON_X, | 389 | NativeButton.X, |
391 | data, | 390 | data, |
392 | position | 391 | position |
393 | ) | 392 | ) |
@@ -401,7 +400,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
401 | windowSize, | 400 | windowSize, |
402 | R.drawable.facebutton_y, | 401 | R.drawable.facebutton_y, |
403 | R.drawable.facebutton_y_depressed, | 402 | R.drawable.facebutton_y_depressed, |
404 | ButtonType.BUTTON_Y, | 403 | NativeButton.Y, |
405 | data, | 404 | data, |
406 | position | 405 | position |
407 | ) | 406 | ) |
@@ -415,7 +414,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
415 | windowSize, | 414 | windowSize, |
416 | R.drawable.facebutton_plus, | 415 | R.drawable.facebutton_plus, |
417 | R.drawable.facebutton_plus_depressed, | 416 | R.drawable.facebutton_plus_depressed, |
418 | ButtonType.BUTTON_PLUS, | 417 | NativeButton.Plus, |
419 | data, | 418 | data, |
420 | position | 419 | position |
421 | ) | 420 | ) |
@@ -429,7 +428,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
429 | windowSize, | 428 | windowSize, |
430 | R.drawable.facebutton_minus, | 429 | R.drawable.facebutton_minus, |
431 | R.drawable.facebutton_minus_depressed, | 430 | R.drawable.facebutton_minus_depressed, |
432 | ButtonType.BUTTON_MINUS, | 431 | NativeButton.Minus, |
433 | data, | 432 | data, |
434 | position | 433 | position |
435 | ) | 434 | ) |
@@ -443,7 +442,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
443 | windowSize, | 442 | windowSize, |
444 | R.drawable.facebutton_home, | 443 | R.drawable.facebutton_home, |
445 | R.drawable.facebutton_home_depressed, | 444 | R.drawable.facebutton_home_depressed, |
446 | ButtonType.BUTTON_HOME, | 445 | NativeButton.Home, |
447 | data, | 446 | data, |
448 | position | 447 | position |
449 | ) | 448 | ) |
@@ -457,7 +456,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
457 | windowSize, | 456 | windowSize, |
458 | R.drawable.facebutton_screenshot, | 457 | R.drawable.facebutton_screenshot, |
459 | R.drawable.facebutton_screenshot_depressed, | 458 | R.drawable.facebutton_screenshot_depressed, |
460 | ButtonType.BUTTON_CAPTURE, | 459 | NativeButton.Capture, |
461 | data, | 460 | data, |
462 | position | 461 | position |
463 | ) | 462 | ) |
@@ -471,7 +470,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
471 | windowSize, | 470 | windowSize, |
472 | R.drawable.l_shoulder, | 471 | R.drawable.l_shoulder, |
473 | R.drawable.l_shoulder_depressed, | 472 | R.drawable.l_shoulder_depressed, |
474 | ButtonType.TRIGGER_L, | 473 | NativeButton.L, |
475 | data, | 474 | data, |
476 | position | 475 | position |
477 | ) | 476 | ) |
@@ -485,7 +484,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
485 | windowSize, | 484 | windowSize, |
486 | R.drawable.r_shoulder, | 485 | R.drawable.r_shoulder, |
487 | R.drawable.r_shoulder_depressed, | 486 | R.drawable.r_shoulder_depressed, |
488 | ButtonType.TRIGGER_R, | 487 | NativeButton.R, |
489 | data, | 488 | data, |
490 | position | 489 | position |
491 | ) | 490 | ) |
@@ -499,7 +498,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
499 | windowSize, | 498 | windowSize, |
500 | R.drawable.zl_trigger, | 499 | R.drawable.zl_trigger, |
501 | R.drawable.zl_trigger_depressed, | 500 | R.drawable.zl_trigger_depressed, |
502 | ButtonType.TRIGGER_ZL, | 501 | NativeButton.ZL, |
503 | data, | 502 | data, |
504 | position | 503 | position |
505 | ) | 504 | ) |
@@ -513,7 +512,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
513 | windowSize, | 512 | windowSize, |
514 | R.drawable.zr_trigger, | 513 | R.drawable.zr_trigger, |
515 | R.drawable.zr_trigger_depressed, | 514 | R.drawable.zr_trigger_depressed, |
516 | ButtonType.TRIGGER_ZR, | 515 | NativeButton.ZR, |
517 | data, | 516 | data, |
518 | position | 517 | position |
519 | ) | 518 | ) |
@@ -527,7 +526,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
527 | windowSize, | 526 | windowSize, |
528 | R.drawable.button_l3, | 527 | R.drawable.button_l3, |
529 | R.drawable.button_l3_depressed, | 528 | R.drawable.button_l3_depressed, |
530 | ButtonType.STICK_L, | 529 | NativeButton.LStick, |
531 | data, | 530 | data, |
532 | position | 531 | position |
533 | ) | 532 | ) |
@@ -541,7 +540,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
541 | windowSize, | 540 | windowSize, |
542 | R.drawable.button_r3, | 541 | R.drawable.button_r3, |
543 | R.drawable.button_r3_depressed, | 542 | R.drawable.button_r3_depressed, |
544 | ButtonType.STICK_R, | 543 | NativeButton.RStick, |
545 | data, | 544 | data, |
546 | position | 545 | position |
547 | ) | 546 | ) |
@@ -556,8 +555,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
556 | R.drawable.joystick_range, | 555 | R.drawable.joystick_range, |
557 | R.drawable.joystick, | 556 | R.drawable.joystick, |
558 | R.drawable.joystick_depressed, | 557 | R.drawable.joystick_depressed, |
559 | StickType.STICK_L, | 558 | NativeAnalog.LStick, |
560 | ButtonType.STICK_L, | 559 | NativeButton.LStick, |
561 | data, | 560 | data, |
562 | position | 561 | position |
563 | ) | 562 | ) |
@@ -572,8 +571,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
572 | R.drawable.joystick_range, | 571 | R.drawable.joystick_range, |
573 | R.drawable.joystick, | 572 | R.drawable.joystick, |
574 | R.drawable.joystick_depressed, | 573 | R.drawable.joystick_depressed, |
575 | StickType.STICK_R, | 574 | NativeAnalog.RStick, |
576 | ButtonType.STICK_R, | 575 | NativeButton.RStick, |
577 | data, | 576 | data, |
578 | position | 577 | position |
579 | ) | 578 | ) |
@@ -835,7 +834,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
835 | windowSize: Pair<Point, Point>, | 834 | windowSize: Pair<Point, Point>, |
836 | defaultResId: Int, | 835 | defaultResId: Int, |
837 | pressedResId: Int, | 836 | pressedResId: Int, |
838 | buttonId: Int, | 837 | button: NativeButton, |
839 | overlayControlData: OverlayControlData, | 838 | overlayControlData: OverlayControlData, |
840 | position: Pair<Double, Double> | 839 | position: Pair<Double, Double> |
841 | ): InputOverlayDrawableButton { | 840 | ): InputOverlayDrawableButton { |
@@ -869,7 +868,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
869 | res, | 868 | res, |
870 | defaultStateBitmap, | 869 | defaultStateBitmap, |
871 | pressedStateBitmap, | 870 | pressedStateBitmap, |
872 | buttonId, | 871 | button, |
873 | overlayControlData | 872 | overlayControlData |
874 | ) | 873 | ) |
875 | 874 | ||
@@ -940,11 +939,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
940 | res, | 939 | res, |
941 | defaultStateBitmap, | 940 | defaultStateBitmap, |
942 | pressedOneDirectionStateBitmap, | 941 | pressedOneDirectionStateBitmap, |
943 | pressedTwoDirectionsStateBitmap, | 942 | pressedTwoDirectionsStateBitmap |
944 | ButtonType.DPAD_UP, | ||
945 | ButtonType.DPAD_DOWN, | ||
946 | ButtonType.DPAD_LEFT, | ||
947 | ButtonType.DPAD_RIGHT | ||
948 | ) | 943 | ) |
949 | 944 | ||
950 | // Get the minimum and maximum coordinates of the screen where the button can be placed. | 945 | // Get the minimum and maximum coordinates of the screen where the button can be placed. |
@@ -993,8 +988,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
993 | resOuter: Int, | 988 | resOuter: Int, |
994 | defaultResInner: Int, | 989 | defaultResInner: Int, |
995 | pressedResInner: Int, | 990 | pressedResInner: Int, |
996 | joystick: Int, | 991 | joystick: NativeAnalog, |
997 | buttonId: Int, | 992 | button: NativeButton, |
998 | overlayControlData: OverlayControlData, | 993 | overlayControlData: OverlayControlData, |
999 | position: Pair<Double, Double> | 994 | position: Pair<Double, Double> |
1000 | ): InputOverlayDrawableJoystick { | 995 | ): InputOverlayDrawableJoystick { |
@@ -1042,7 +1037,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : | |||
1042 | outerRect, | 1037 | outerRect, |
1043 | innerRect, | 1038 | innerRect, |
1044 | joystick, | 1039 | joystick, |
1045 | buttonId, | 1040 | button, |
1046 | overlayControlData.id | 1041 | overlayControlData.id |
1047 | ) | 1042 | ) |
1048 | 1043 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt index b14a4f96e..fee3d04ee 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt | |||
@@ -9,7 +9,8 @@ import android.graphics.Canvas | |||
9 | import android.graphics.Rect | 9 | import android.graphics.Rect |
10 | import android.graphics.drawable.BitmapDrawable | 10 | import android.graphics.drawable.BitmapDrawable |
11 | import android.view.MotionEvent | 11 | import android.view.MotionEvent |
12 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonState | 12 | import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState |
13 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
13 | import org.yuzu.yuzu_emu.overlay.model.OverlayControlData | 14 | import org.yuzu.yuzu_emu.overlay.model.OverlayControlData |
14 | 15 | ||
15 | /** | 16 | /** |
@@ -19,13 +20,13 @@ import org.yuzu.yuzu_emu.overlay.model.OverlayControlData | |||
19 | * @param res [Resources] instance. | 20 | * @param res [Resources] instance. |
20 | * @param defaultStateBitmap [Bitmap] to use with the default state Drawable. | 21 | * @param defaultStateBitmap [Bitmap] to use with the default state Drawable. |
21 | * @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable. | 22 | * @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable. |
22 | * @param buttonId Identifier for this type of button. | 23 | * @param button [NativeButton] for this type of button. |
23 | */ | 24 | */ |
24 | class InputOverlayDrawableButton( | 25 | class InputOverlayDrawableButton( |
25 | res: Resources, | 26 | res: Resources, |
26 | defaultStateBitmap: Bitmap, | 27 | defaultStateBitmap: Bitmap, |
27 | pressedStateBitmap: Bitmap, | 28 | pressedStateBitmap: Bitmap, |
28 | val buttonId: Int, | 29 | val button: NativeButton, |
29 | val overlayControlData: OverlayControlData | 30 | val overlayControlData: OverlayControlData |
30 | ) { | 31 | ) { |
31 | // The ID value what motion event is tracking | 32 | // The ID value what motion event is tracking |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 8aef6f5a5..0cb6ff244 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt | |||
@@ -9,7 +9,8 @@ import android.graphics.Canvas | |||
9 | import android.graphics.Rect | 9 | import android.graphics.Rect |
10 | import android.graphics.drawable.BitmapDrawable | 10 | import android.graphics.drawable.BitmapDrawable |
11 | import android.view.MotionEvent | 11 | import android.view.MotionEvent |
12 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonState | 12 | import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState |
13 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
13 | 14 | ||
14 | /** | 15 | /** |
15 | * Custom [BitmapDrawable] that is capable | 16 | * Custom [BitmapDrawable] that is capable |
@@ -19,20 +20,12 @@ import org.yuzu.yuzu_emu.NativeLibrary.ButtonState | |||
19 | * @param defaultStateBitmap [Bitmap] of the default state. | 20 | * @param defaultStateBitmap [Bitmap] of the default state. |
20 | * @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction. | 21 | * @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction. |
21 | * @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction. | 22 | * @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction. |
22 | * @param buttonUp Identifier for the up button. | ||
23 | * @param buttonDown Identifier for the down button. | ||
24 | * @param buttonLeft Identifier for the left button. | ||
25 | * @param buttonRight Identifier for the right button. | ||
26 | */ | 23 | */ |
27 | class InputOverlayDrawableDpad( | 24 | class InputOverlayDrawableDpad( |
28 | res: Resources, | 25 | res: Resources, |
29 | defaultStateBitmap: Bitmap, | 26 | defaultStateBitmap: Bitmap, |
30 | pressedOneDirectionStateBitmap: Bitmap, | 27 | pressedOneDirectionStateBitmap: Bitmap, |
31 | pressedTwoDirectionsStateBitmap: Bitmap, | 28 | pressedTwoDirectionsStateBitmap: Bitmap |
32 | buttonUp: Int, | ||
33 | buttonDown: Int, | ||
34 | buttonLeft: Int, | ||
35 | buttonRight: Int | ||
36 | ) { | 29 | ) { |
37 | /** | 30 | /** |
38 | * Gets one of the InputOverlayDrawableDpad's button IDs. | 31 | * Gets one of the InputOverlayDrawableDpad's button IDs. |
@@ -40,10 +33,10 @@ class InputOverlayDrawableDpad( | |||
40 | * @return the requested InputOverlayDrawableDpad's button ID. | 33 | * @return the requested InputOverlayDrawableDpad's button ID. |
41 | */ | 34 | */ |
42 | // The ID identifying what type of button this Drawable represents. | 35 | // The ID identifying what type of button this Drawable represents. |
43 | val upId: Int | 36 | val up = NativeButton.DUp |
44 | val downId: Int | 37 | val down = NativeButton.DDown |
45 | val leftId: Int | 38 | val left = NativeButton.DLeft |
46 | val rightId: Int | 39 | val right = NativeButton.DRight |
47 | var trackId: Int | 40 | var trackId: Int |
48 | 41 | ||
49 | val width: Int | 42 | val width: Int |
@@ -69,10 +62,6 @@ class InputOverlayDrawableDpad( | |||
69 | this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap) | 62 | this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap) |
70 | width = this.defaultStateBitmap.intrinsicWidth | 63 | width = this.defaultStateBitmap.intrinsicWidth |
71 | height = this.defaultStateBitmap.intrinsicHeight | 64 | height = this.defaultStateBitmap.intrinsicHeight |
72 | upId = buttonUp | ||
73 | downId = buttonDown | ||
74 | leftId = buttonLeft | ||
75 | rightId = buttonRight | ||
76 | trackId = -1 | 65 | trackId = -1 |
77 | } | 66 | } |
78 | 67 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index 113bf7c24..4b07107fc 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt | |||
@@ -13,7 +13,9 @@ import kotlin.math.atan2 | |||
13 | import kotlin.math.cos | 13 | import kotlin.math.cos |
14 | import kotlin.math.sin | 14 | import kotlin.math.sin |
15 | import kotlin.math.sqrt | 15 | import kotlin.math.sqrt |
16 | import org.yuzu.yuzu_emu.NativeLibrary | 16 | import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState |
17 | import org.yuzu.yuzu_emu.features.input.model.NativeAnalog | ||
18 | import org.yuzu.yuzu_emu.features.input.model.NativeButton | ||
17 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 19 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
18 | 20 | ||
19 | /** | 21 | /** |
@@ -26,8 +28,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | |||
26 | * @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick. | 28 | * @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick. |
27 | * @param rectOuter [Rect] which represents the outer joystick bounds. | 29 | * @param rectOuter [Rect] which represents the outer joystick bounds. |
28 | * @param rectInner [Rect] which represents the inner joystick bounds. | 30 | * @param rectInner [Rect] which represents the inner joystick bounds. |
29 | * @param joystickId The ID value what type of joystick this Drawable represents. | 31 | * @param joystick The [NativeAnalog] this Drawable represents. |
30 | * @param buttonId The ID value what type of button this Drawable represents. | 32 | * @param button The [NativeButton] this Drawable represents. |
31 | */ | 33 | */ |
32 | class InputOverlayDrawableJoystick( | 34 | class InputOverlayDrawableJoystick( |
33 | res: Resources, | 35 | res: Resources, |
@@ -36,8 +38,8 @@ class InputOverlayDrawableJoystick( | |||
36 | bitmapInnerPressed: Bitmap, | 38 | bitmapInnerPressed: Bitmap, |
37 | rectOuter: Rect, | 39 | rectOuter: Rect, |
38 | rectInner: Rect, | 40 | rectInner: Rect, |
39 | val joystickId: Int, | 41 | val joystick: NativeAnalog, |
40 | val buttonId: Int, | 42 | val button: NativeButton, |
41 | val prefId: String | 43 | val prefId: String |
42 | ) { | 44 | ) { |
43 | // The ID value what motion event is tracking | 45 | // The ID value what motion event is tracking |
@@ -69,8 +71,7 @@ class InputOverlayDrawableJoystick( | |||
69 | 71 | ||
70 | // TODO: Add button support | 72 | // TODO: Add button support |
71 | val buttonStatus: Int | 73 | val buttonStatus: Int |
72 | get() = | 74 | get() = ButtonState.RELEASED |
73 | NativeLibrary.ButtonState.RELEASED | ||
74 | var bounds: Rect | 75 | var bounds: Rect |
75 | get() = outerBitmap.bounds | 76 | get() = outerBitmap.bounds |
76 | set(bounds) { | 77 | set(bounds) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 23ca49b53..fadb20e39 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.ui | 4 | package org.yuzu.yuzu_emu.ui |
5 | 5 | ||
6 | import android.annotation.SuppressLint | ||
7 | import android.os.Bundle | 6 | import android.os.Bundle |
8 | import android.view.LayoutInflater | 7 | import android.view.LayoutInflater |
9 | import android.view.View | 8 | import android.view.View |
@@ -14,19 +13,16 @@ import androidx.core.view.WindowInsetsCompat | |||
14 | import androidx.core.view.updatePadding | 13 | import androidx.core.view.updatePadding |
15 | import androidx.fragment.app.Fragment | 14 | import androidx.fragment.app.Fragment |
16 | import androidx.fragment.app.activityViewModels | 15 | import androidx.fragment.app.activityViewModels |
17 | import androidx.lifecycle.Lifecycle | ||
18 | import androidx.lifecycle.lifecycleScope | ||
19 | import androidx.lifecycle.repeatOnLifecycle | ||
20 | import com.google.android.material.color.MaterialColors | 16 | import com.google.android.material.color.MaterialColors |
21 | import kotlinx.coroutines.flow.collectLatest | ||
22 | import kotlinx.coroutines.launch | ||
23 | import org.yuzu.yuzu_emu.R | 17 | import org.yuzu.yuzu_emu.R |
24 | import org.yuzu.yuzu_emu.adapters.GameAdapter | 18 | import org.yuzu.yuzu_emu.adapters.GameAdapter |
25 | import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding | 19 | import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding |
26 | import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager | 20 | import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager |
27 | import org.yuzu.yuzu_emu.model.GamesViewModel | 21 | import org.yuzu.yuzu_emu.model.GamesViewModel |
28 | import org.yuzu.yuzu_emu.model.HomeViewModel | 22 | import org.yuzu.yuzu_emu.model.HomeViewModel |
23 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
29 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins | 24 | import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins |
25 | import org.yuzu.yuzu_emu.utils.collect | ||
30 | 26 | ||
31 | class GamesFragment : Fragment() { | 27 | class GamesFragment : Fragment() { |
32 | private var _binding: FragmentGamesBinding? = null | 28 | private var _binding: FragmentGamesBinding? = null |
@@ -44,8 +40,6 @@ class GamesFragment : Fragment() { | |||
44 | return binding.root | 40 | return binding.root |
45 | } | 41 | } |
46 | 42 | ||
47 | // This is using the correct scope, lint is just acting up | ||
48 | @SuppressLint("UnsafeRepeatOnLifecycleDetector") | ||
49 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 43 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
50 | super.onViewCreated(view, savedInstanceState) | 44 | super.onViewCreated(view, savedInstanceState) |
51 | homeViewModel.setNavigationVisibility(visible = true, animated = true) | 45 | homeViewModel.setNavigationVisibility(visible = true, animated = true) |
@@ -88,49 +82,28 @@ class GamesFragment : Fragment() { | |||
88 | } | 82 | } |
89 | } | 83 | } |
90 | 84 | ||
91 | viewLifecycleOwner.lifecycleScope.apply { | 85 | gamesViewModel.isReloading.collect(viewLifecycleOwner) { |
92 | launch { | 86 | binding.swipeRefresh.isRefreshing = it |
93 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | 87 | binding.noticeText.setVisible( |
94 | gamesViewModel.isReloading.collect { | 88 | visible = gamesViewModel.games.value.isEmpty() && !it, |
95 | binding.swipeRefresh.isRefreshing = it | 89 | gone = false |
96 | if (gamesViewModel.games.value.isEmpty() && !it) { | 90 | ) |
97 | binding.noticeText.visibility = View.VISIBLE | 91 | } |
98 | } else { | 92 | gamesViewModel.games.collect(viewLifecycleOwner) { |
99 | binding.noticeText.visibility = View.INVISIBLE | 93 | (binding.gridGames.adapter as GameAdapter).submitList(it) |
100 | } | 94 | } |
101 | } | 95 | gamesViewModel.shouldSwapData.collect( |
102 | } | 96 | viewLifecycleOwner, |
103 | } | 97 | resetState = { gamesViewModel.setShouldSwapData(false) } |
104 | launch { | 98 | ) { |
105 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | 99 | if (it) { |
106 | gamesViewModel.games.collectLatest { | 100 | (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value) |
107 | (binding.gridGames.adapter as GameAdapter).submitList(it) | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | launch { | ||
112 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | ||
113 | gamesViewModel.shouldSwapData.collect { | ||
114 | if (it) { | ||
115 | (binding.gridGames.adapter as GameAdapter).submitList( | ||
116 | gamesViewModel.games.value | ||
117 | ) | ||
118 | gamesViewModel.setShouldSwapData(false) | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | launch { | ||
124 | repeatOnLifecycle(Lifecycle.State.RESUMED) { | ||
125 | gamesViewModel.shouldScrollToTop.collect { | ||
126 | if (it) { | ||
127 | scrollToTop() | ||
128 | gamesViewModel.setShouldScrollToTop(false) | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | } | 101 | } |
133 | } | 102 | } |
103 | gamesViewModel.shouldScrollToTop.collect( | ||
104 | viewLifecycleOwner, | ||
105 | resetState = { gamesViewModel.setShouldScrollToTop(false) } | ||
106 | ) { if (it) scrollToTop() } | ||
134 | 107 | ||
135 | setInsets() | 108 | setInsets() |
136 | } | 109 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 4df4ac4c6..d16f8a931 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
@@ -19,9 +19,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen | |||
19 | import androidx.core.view.ViewCompat | 19 | import androidx.core.view.ViewCompat |
20 | import androidx.core.view.WindowCompat | 20 | import androidx.core.view.WindowCompat |
21 | import androidx.core.view.WindowInsetsCompat | 21 | import androidx.core.view.WindowInsetsCompat |
22 | import androidx.lifecycle.Lifecycle | ||
23 | import androidx.lifecycle.lifecycleScope | ||
24 | import androidx.lifecycle.repeatOnLifecycle | ||
25 | import androidx.navigation.NavController | 22 | import androidx.navigation.NavController |
26 | import androidx.navigation.fragment.NavHostFragment | 23 | import androidx.navigation.fragment.NavHostFragment |
27 | import androidx.navigation.ui.setupWithNavController | 24 | import androidx.navigation.ui.setupWithNavController |
@@ -30,7 +27,6 @@ import com.google.android.material.color.MaterialColors | |||
30 | import com.google.android.material.navigation.NavigationBarView | 27 | import com.google.android.material.navigation.NavigationBarView |
31 | import java.io.File | 28 | import java.io.File |
32 | import java.io.FilenameFilter | 29 | import java.io.FilenameFilter |
33 | import kotlinx.coroutines.launch | ||
34 | import org.yuzu.yuzu_emu.HomeNavigationDirections | 30 | import org.yuzu.yuzu_emu.HomeNavigationDirections |
35 | import org.yuzu.yuzu_emu.NativeLibrary | 31 | import org.yuzu.yuzu_emu.NativeLibrary |
36 | import org.yuzu.yuzu_emu.R | 32 | import org.yuzu.yuzu_emu.R |
@@ -47,6 +43,7 @@ import org.yuzu.yuzu_emu.model.InstallResult | |||
47 | import org.yuzu.yuzu_emu.model.TaskState | 43 | import org.yuzu.yuzu_emu.model.TaskState |
48 | import org.yuzu.yuzu_emu.model.TaskViewModel | 44 | import org.yuzu.yuzu_emu.model.TaskViewModel |
49 | import org.yuzu.yuzu_emu.utils.* | 45 | import org.yuzu.yuzu_emu.utils.* |
46 | import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible | ||
50 | import java.io.BufferedInputStream | 47 | import java.io.BufferedInputStream |
51 | import java.io.BufferedOutputStream | 48 | import java.io.BufferedOutputStream |
52 | import java.util.zip.ZipEntry | 49 | import java.util.zip.ZipEntry |
@@ -139,42 +136,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
139 | 136 | ||
140 | // Prevents navigation from being drawn for a short time on recreation if set to hidden | 137 | // Prevents navigation from being drawn for a short time on recreation if set to hidden |
141 | if (!homeViewModel.navigationVisible.value.first) { | 138 | if (!homeViewModel.navigationVisible.value.first) { |
142 | binding.navigationView.visibility = View.INVISIBLE | 139 | binding.navigationView.setVisible(visible = false, gone = false) |
143 | binding.statusBarShade.visibility = View.INVISIBLE | 140 | binding.statusBarShade.setVisible(visible = false, gone = false) |
144 | } | 141 | } |
145 | 142 | ||
146 | lifecycleScope.apply { | 143 | homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) } |
147 | launch { | 144 | homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) } |
148 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 145 | homeViewModel.contentToInstall.collect( |
149 | homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) } | 146 | this, |
150 | } | 147 | resetState = { homeViewModel.setContentToInstall(null) } |
151 | } | 148 | ) { |
152 | launch { | 149 | if (it != null) { |
153 | repeatOnLifecycle(Lifecycle.State.CREATED) { | 150 | installContent(it) |
154 | homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } | ||
155 | } | ||
156 | } | ||
157 | launch { | ||
158 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
159 | homeViewModel.contentToInstall.collect { | ||
160 | if (it != null) { | ||
161 | installContent(it) | ||
162 | homeViewModel.setContentToInstall(null) | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | launch { | ||
168 | repeatOnLifecycle(Lifecycle.State.CREATED) { | ||
169 | homeViewModel.checkKeys.collect { | ||
170 | if (it) { | ||
171 | checkKeys() | ||
172 | homeViewModel.setCheckKeys(false) | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | } | 151 | } |
177 | } | 152 | } |
153 | homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) { | ||
154 | if (it) checkKeys() | ||
155 | } | ||
178 | 156 | ||
179 | setInsets() | 157 | setInsets() |
180 | } | 158 | } |
@@ -214,18 +192,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
214 | 192 | ||
215 | private fun showNavigation(visible: Boolean, animated: Boolean) { | 193 | private fun showNavigation(visible: Boolean, animated: Boolean) { |
216 | if (!animated) { | 194 | if (!animated) { |
217 | if (visible) { | 195 | binding.navigationView.setVisible(visible) |
218 | binding.navigationView.visibility = View.VISIBLE | ||
219 | } else { | ||
220 | binding.navigationView.visibility = View.INVISIBLE | ||
221 | } | ||
222 | return | 196 | return |
223 | } | 197 | } |
224 | 198 | ||
225 | val smallLayout = resources.getBoolean(R.bool.small_layout) | 199 | val smallLayout = resources.getBoolean(R.bool.small_layout) |
226 | binding.navigationView.animate().apply { | 200 | binding.navigationView.animate().apply { |
227 | if (visible) { | 201 | if (visible) { |
228 | binding.navigationView.visibility = View.VISIBLE | 202 | binding.navigationView.setVisible(true) |
229 | duration = 300 | 203 | duration = 300 |
230 | interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f) | 204 | interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f) |
231 | 205 | ||
@@ -264,7 +238,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
264 | } | 238 | } |
265 | }.withEndAction { | 239 | }.withEndAction { |
266 | if (!visible) { | 240 | if (!visible) { |
267 | binding.navigationView.visibility = View.INVISIBLE | 241 | binding.navigationView.setVisible(visible = false, gone = false) |
268 | } | 242 | } |
269 | }.start() | 243 | }.start() |
270 | } | 244 | } |
@@ -272,7 +246,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
272 | private fun showStatusBarShade(visible: Boolean) { | 246 | private fun showStatusBarShade(visible: Boolean) { |
273 | binding.statusBarShade.animate().apply { | 247 | binding.statusBarShade.animate().apply { |
274 | if (visible) { | 248 | if (visible) { |
275 | binding.statusBarShade.visibility = View.VISIBLE | 249 | binding.statusBarShade.setVisible(true) |
276 | binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2 | 250 | binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2 |
277 | duration = 300 | 251 | duration = 300 |
278 | translationY(0f) | 252 | translationY(0f) |
@@ -284,7 +258,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
284 | } | 258 | } |
285 | }.withEndAction { | 259 | }.withEndAction { |
286 | if (!visible) { | 260 | if (!visible) { |
287 | binding.statusBarShade.visibility = View.INVISIBLE | 261 | binding.statusBarShade.setVisible(visible = false, gone = false) |
288 | } | 262 | } |
289 | }.start() | 263 | }.start() |
290 | } | 264 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index e63382e1d..2c7356e6a 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt | |||
@@ -6,439 +6,89 @@ package org.yuzu.yuzu_emu.utils | |||
6 | import android.view.InputDevice | 6 | import android.view.InputDevice |
7 | import android.view.KeyEvent | 7 | import android.view.KeyEvent |
8 | import android.view.MotionEvent | 8 | import android.view.MotionEvent |
9 | import kotlin.math.sqrt | 9 | import org.yuzu.yuzu_emu.features.input.NativeInput |
10 | import org.yuzu.yuzu_emu.NativeLibrary | 10 | import org.yuzu.yuzu_emu.features.input.YuzuInputOverlayDevice |
11 | import org.yuzu.yuzu_emu.features.input.YuzuPhysicalDevice | ||
11 | 12 | ||
12 | object InputHandler { | 13 | object InputHandler { |
13 | private var controllerIds = getGameControllerIds() | 14 | var androidControllers = mapOf<Int, YuzuPhysicalDevice>() |
14 | 15 | var registeredControllers = mutableListOf<ParamPackage>() | |
15 | fun initialize() { | ||
16 | // Connect first controller | ||
17 | NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)) | ||
18 | } | ||
19 | |||
20 | fun updateControllerIds() { | ||
21 | controllerIds = getGameControllerIds() | ||
22 | } | ||
23 | 16 | ||
24 | fun dispatchKeyEvent(event: KeyEvent): Boolean { | 17 | fun dispatchKeyEvent(event: KeyEvent): Boolean { |
25 | val button: Int = when (event.device.vendorId) { | ||
26 | 0x045E -> getInputXboxButtonKey(event.keyCode) | ||
27 | 0x054C -> getInputDS5ButtonKey(event.keyCode) | ||
28 | 0x057E -> getInputJoyconButtonKey(event.keyCode) | ||
29 | 0x1532 -> getInputRazerButtonKey(event.keyCode) | ||
30 | 0x3537 -> getInputRedmagicButtonKey(event.keyCode) | ||
31 | 0x358A -> getInputBackboneLabsButtonKey(event.keyCode) | ||
32 | else -> getInputGenericButtonKey(event.keyCode) | ||
33 | } | ||
34 | |||
35 | val action = when (event.action) { | 18 | val action = when (event.action) { |
36 | KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED | 19 | KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED |
37 | KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED | 20 | KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED |
38 | else -> return false | 21 | else -> return false |
39 | } | 22 | } |
40 | 23 | ||
41 | // Ignore invalid buttons | 24 | var controllerData = androidControllers[event.device.controllerNumber] |
42 | if (button < 0) { | 25 | if (controllerData == null) { |
43 | return false | 26 | updateControllerData() |
27 | controllerData = androidControllers[event.device.controllerNumber] ?: return false | ||
44 | } | 28 | } |
45 | 29 | ||
46 | return NativeLibrary.onGamePadButtonEvent( | 30 | NativeInput.onGamePadButtonEvent( |
47 | getPlayerNumber(event.device.controllerNumber, event.deviceId), | 31 | controllerData.getGUID(), |
48 | button, | 32 | controllerData.getPort(), |
33 | event.keyCode, | ||
49 | action | 34 | action |
50 | ) | 35 | ) |
36 | return true | ||
51 | } | 37 | } |
52 | 38 | ||
53 | fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { | 39 | fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { |
54 | val device = event.device | 40 | val controllerData = |
55 | // Check every axis input available on the controller | 41 | androidControllers[event.device.controllerNumber] ?: return false |
56 | for (range in device.motionRanges) { | 42 | event.device.motionRanges.forEach { |
57 | val axis = range.axis | 43 | NativeInput.onGamePadAxisEvent( |
58 | when (device.vendorId) { | 44 | controllerData.getGUID(), |
59 | 0x045E -> setGenericAxisInput(event, axis) | 45 | controllerData.getPort(), |
60 | 0x054C -> setGenericAxisInput(event, axis) | 46 | it.axis, |
61 | 0x057E -> setJoyconAxisInput(event, axis) | 47 | event.getAxisValue(it.axis) |
62 | 0x1532 -> setRazerAxisInput(event, axis) | 48 | ) |
63 | else -> setGenericAxisInput(event, axis) | ||
64 | } | ||
65 | } | 49 | } |
66 | |||
67 | return true | 50 | return true |
68 | } | 51 | } |
69 | 52 | ||
70 | private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int { | 53 | fun getDevices(): Map<Int, YuzuPhysicalDevice> { |
71 | var deviceIndex = index | 54 | val gameControllerDeviceIds = mutableMapOf<Int, YuzuPhysicalDevice>() |
72 | if (deviceId != -1) { | ||
73 | deviceIndex = controllerIds[deviceId] ?: 0 | ||
74 | } | ||
75 | |||
76 | // TODO: Joycons are handled as different controllers. Find a way to merge them. | ||
77 | return when (deviceIndex) { | ||
78 | 2 -> NativeLibrary.Player2Device | ||
79 | 3 -> NativeLibrary.Player3Device | ||
80 | 4 -> NativeLibrary.Player4Device | ||
81 | 5 -> NativeLibrary.Player5Device | ||
82 | 6 -> NativeLibrary.Player6Device | ||
83 | 7 -> NativeLibrary.Player7Device | ||
84 | 8 -> NativeLibrary.Player8Device | ||
85 | else -> if (NativeLibrary.isHandheldOnly()) { | ||
86 | NativeLibrary.ConsoleDevice | ||
87 | } else { | ||
88 | NativeLibrary.Player1Device | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | |||
93 | private fun setStickState(playerNumber: Int, index: Int, xAxis: Float, yAxis: Float) { | ||
94 | // Calculate vector size | ||
95 | val r2 = xAxis * xAxis + yAxis * yAxis | ||
96 | var r = sqrt(r2.toDouble()).toFloat() | ||
97 | |||
98 | // Adjust range of joystick | ||
99 | val deadzone = 0.15f | ||
100 | var x = xAxis | ||
101 | var y = yAxis | ||
102 | |||
103 | if (r > deadzone) { | ||
104 | val deadzoneFactor = 1.0f / r * (r - deadzone) / (1.0f - deadzone) | ||
105 | x *= deadzoneFactor | ||
106 | y *= deadzoneFactor | ||
107 | r *= deadzoneFactor | ||
108 | } else { | ||
109 | x = 0.0f | ||
110 | y = 0.0f | ||
111 | } | ||
112 | |||
113 | // Normalize joystick | ||
114 | if (r > 1.0f) { | ||
115 | x /= r | ||
116 | y /= r | ||
117 | } | ||
118 | |||
119 | NativeLibrary.onGamePadJoystickEvent( | ||
120 | playerNumber, | ||
121 | index, | ||
122 | x, | ||
123 | -y | ||
124 | ) | ||
125 | } | ||
126 | |||
127 | private fun getAxisToButton(axis: Float): Int { | ||
128 | return if (axis > 0.5f) { | ||
129 | NativeLibrary.ButtonState.PRESSED | ||
130 | } else { | ||
131 | NativeLibrary.ButtonState.RELEASED | ||
132 | } | ||
133 | } | ||
134 | |||
135 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { | ||
136 | NativeLibrary.onGamePadButtonEvent( | ||
137 | playerNumber, | ||
138 | NativeLibrary.ButtonType.DPAD_UP, | ||
139 | getAxisToButton(-yAxis) | ||
140 | ) | ||
141 | NativeLibrary.onGamePadButtonEvent( | ||
142 | playerNumber, | ||
143 | NativeLibrary.ButtonType.DPAD_DOWN, | ||
144 | getAxisToButton(yAxis) | ||
145 | ) | ||
146 | NativeLibrary.onGamePadButtonEvent( | ||
147 | playerNumber, | ||
148 | NativeLibrary.ButtonType.DPAD_LEFT, | ||
149 | getAxisToButton(-xAxis) | ||
150 | ) | ||
151 | NativeLibrary.onGamePadButtonEvent( | ||
152 | playerNumber, | ||
153 | NativeLibrary.ButtonType.DPAD_RIGHT, | ||
154 | getAxisToButton(xAxis) | ||
155 | ) | ||
156 | } | ||
157 | |||
158 | private fun getInputDS5ButtonKey(key: Int): Int { | ||
159 | // The missing ds5 buttons are axis | ||
160 | return when (key) { | ||
161 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B | ||
162 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A | ||
163 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y | ||
164 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X | ||
165 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
166 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
167 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
168 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
169 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
170 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
171 | else -> -1 | ||
172 | } | ||
173 | } | ||
174 | |||
175 | private fun getInputJoyconButtonKey(key: Int): Int { | ||
176 | // Joycon support is half dead. A lot of buttons can't be mapped | ||
177 | return when (key) { | ||
178 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B | ||
179 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A | ||
180 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X | ||
181 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y | ||
182 | KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP | ||
183 | KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN | ||
184 | KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT | ||
185 | KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT | ||
186 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
187 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
188 | KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL | ||
189 | KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR | ||
190 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
191 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
192 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
193 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
194 | else -> -1 | ||
195 | } | ||
196 | } | ||
197 | |||
198 | private fun getInputXboxButtonKey(key: Int): Int { | ||
199 | // The missing xbox buttons are axis | ||
200 | return when (key) { | ||
201 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A | ||
202 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B | ||
203 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X | ||
204 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y | ||
205 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
206 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
207 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
208 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
209 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
210 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
211 | else -> -1 | ||
212 | } | ||
213 | } | ||
214 | |||
215 | private fun getInputRazerButtonKey(key: Int): Int { | ||
216 | // The missing xbox buttons are axis | ||
217 | return when (key) { | ||
218 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B | ||
219 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A | ||
220 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y | ||
221 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X | ||
222 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
223 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
224 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
225 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
226 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
227 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
228 | else -> -1 | ||
229 | } | ||
230 | } | ||
231 | |||
232 | private fun getInputRedmagicButtonKey(key: Int): Int { | ||
233 | return when (key) { | ||
234 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B | ||
235 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A | ||
236 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y | ||
237 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X | ||
238 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
239 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
240 | KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL | ||
241 | KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR | ||
242 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
243 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
244 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
245 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
246 | else -> -1 | ||
247 | } | ||
248 | } | ||
249 | |||
250 | private fun getInputBackboneLabsButtonKey(key: Int): Int { | ||
251 | return when (key) { | ||
252 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B | ||
253 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A | ||
254 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y | ||
255 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X | ||
256 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
257 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
258 | KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL | ||
259 | KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR | ||
260 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
261 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
262 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
263 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
264 | else -> -1 | ||
265 | } | ||
266 | } | ||
267 | |||
268 | private fun getInputGenericButtonKey(key: Int): Int { | ||
269 | return when (key) { | ||
270 | KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A | ||
271 | KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B | ||
272 | KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X | ||
273 | KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y | ||
274 | KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP | ||
275 | KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN | ||
276 | KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT | ||
277 | KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT | ||
278 | KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L | ||
279 | KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R | ||
280 | KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL | ||
281 | KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR | ||
282 | KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L | ||
283 | KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R | ||
284 | KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS | ||
285 | KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS | ||
286 | else -> -1 | ||
287 | } | ||
288 | } | ||
289 | |||
290 | private fun setGenericAxisInput(event: MotionEvent, axis: Int) { | ||
291 | val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) | ||
292 | |||
293 | when (axis) { | ||
294 | MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> | ||
295 | setStickState( | ||
296 | playerNumber, | ||
297 | NativeLibrary.StickType.STICK_L, | ||
298 | event.getAxisValue(MotionEvent.AXIS_X), | ||
299 | event.getAxisValue(MotionEvent.AXIS_Y) | ||
300 | ) | ||
301 | MotionEvent.AXIS_RX, MotionEvent.AXIS_RY -> | ||
302 | setStickState( | ||
303 | playerNumber, | ||
304 | NativeLibrary.StickType.STICK_R, | ||
305 | event.getAxisValue(MotionEvent.AXIS_RX), | ||
306 | event.getAxisValue(MotionEvent.AXIS_RY) | ||
307 | ) | ||
308 | MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> | ||
309 | setStickState( | ||
310 | playerNumber, | ||
311 | NativeLibrary.StickType.STICK_R, | ||
312 | event.getAxisValue(MotionEvent.AXIS_Z), | ||
313 | event.getAxisValue(MotionEvent.AXIS_RZ) | ||
314 | ) | ||
315 | MotionEvent.AXIS_LTRIGGER -> | ||
316 | NativeLibrary.onGamePadButtonEvent( | ||
317 | playerNumber, | ||
318 | NativeLibrary.ButtonType.TRIGGER_ZL, | ||
319 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER)) | ||
320 | ) | ||
321 | MotionEvent.AXIS_BRAKE -> | ||
322 | NativeLibrary.onGamePadButtonEvent( | ||
323 | playerNumber, | ||
324 | NativeLibrary.ButtonType.TRIGGER_ZL, | ||
325 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE)) | ||
326 | ) | ||
327 | MotionEvent.AXIS_RTRIGGER -> | ||
328 | NativeLibrary.onGamePadButtonEvent( | ||
329 | playerNumber, | ||
330 | NativeLibrary.ButtonType.TRIGGER_ZR, | ||
331 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER)) | ||
332 | ) | ||
333 | MotionEvent.AXIS_GAS -> | ||
334 | NativeLibrary.onGamePadButtonEvent( | ||
335 | playerNumber, | ||
336 | NativeLibrary.ButtonType.TRIGGER_ZR, | ||
337 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS)) | ||
338 | ) | ||
339 | MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> | ||
340 | setAxisDpadState( | ||
341 | playerNumber, | ||
342 | event.getAxisValue(MotionEvent.AXIS_HAT_X), | ||
343 | event.getAxisValue(MotionEvent.AXIS_HAT_Y) | ||
344 | ) | ||
345 | } | ||
346 | } | ||
347 | |||
348 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { | ||
349 | // Joycon support is half dead. Right joystick doesn't work | ||
350 | val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) | ||
351 | |||
352 | when (axis) { | ||
353 | MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> | ||
354 | setStickState( | ||
355 | playerNumber, | ||
356 | NativeLibrary.StickType.STICK_L, | ||
357 | event.getAxisValue(MotionEvent.AXIS_X), | ||
358 | event.getAxisValue(MotionEvent.AXIS_Y) | ||
359 | ) | ||
360 | MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> | ||
361 | setStickState( | ||
362 | playerNumber, | ||
363 | NativeLibrary.StickType.STICK_R, | ||
364 | event.getAxisValue(MotionEvent.AXIS_Z), | ||
365 | event.getAxisValue(MotionEvent.AXIS_RZ) | ||
366 | ) | ||
367 | MotionEvent.AXIS_RX, MotionEvent.AXIS_RY -> | ||
368 | setStickState( | ||
369 | playerNumber, | ||
370 | NativeLibrary.StickType.STICK_R, | ||
371 | event.getAxisValue(MotionEvent.AXIS_RX), | ||
372 | event.getAxisValue(MotionEvent.AXIS_RY) | ||
373 | ) | ||
374 | } | ||
375 | } | ||
376 | |||
377 | private fun setRazerAxisInput(event: MotionEvent, axis: Int) { | ||
378 | val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId) | ||
379 | |||
380 | when (axis) { | ||
381 | MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> | ||
382 | setStickState( | ||
383 | playerNumber, | ||
384 | NativeLibrary.StickType.STICK_L, | ||
385 | event.getAxisValue(MotionEvent.AXIS_X), | ||
386 | event.getAxisValue(MotionEvent.AXIS_Y) | ||
387 | ) | ||
388 | MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> | ||
389 | setStickState( | ||
390 | playerNumber, | ||
391 | NativeLibrary.StickType.STICK_R, | ||
392 | event.getAxisValue(MotionEvent.AXIS_Z), | ||
393 | event.getAxisValue(MotionEvent.AXIS_RZ) | ||
394 | ) | ||
395 | MotionEvent.AXIS_BRAKE -> | ||
396 | NativeLibrary.onGamePadButtonEvent( | ||
397 | playerNumber, | ||
398 | NativeLibrary.ButtonType.TRIGGER_ZL, | ||
399 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE)) | ||
400 | ) | ||
401 | MotionEvent.AXIS_GAS -> | ||
402 | NativeLibrary.onGamePadButtonEvent( | ||
403 | playerNumber, | ||
404 | NativeLibrary.ButtonType.TRIGGER_ZR, | ||
405 | getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS)) | ||
406 | ) | ||
407 | MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> | ||
408 | setAxisDpadState( | ||
409 | playerNumber, | ||
410 | event.getAxisValue(MotionEvent.AXIS_HAT_X), | ||
411 | event.getAxisValue(MotionEvent.AXIS_HAT_Y) | ||
412 | ) | ||
413 | } | ||
414 | } | ||
415 | |||
416 | fun getGameControllerIds(): Map<Int, Int> { | ||
417 | val gameControllerDeviceIds = mutableMapOf<Int, Int>() | ||
418 | val deviceIds = InputDevice.getDeviceIds() | 55 | val deviceIds = InputDevice.getDeviceIds() |
419 | var controllerSlot = 1 | 56 | var port = 0 |
57 | val inputSettings = NativeConfig.getInputSettings(true) | ||
420 | deviceIds.forEach { deviceId -> | 58 | deviceIds.forEach { deviceId -> |
421 | InputDevice.getDevice(deviceId)?.apply { | 59 | InputDevice.getDevice(deviceId)?.apply { |
422 | // Don't over-assign controllers | ||
423 | if (controllerSlot >= 8) { | ||
424 | return gameControllerDeviceIds | ||
425 | } | ||
426 | |||
427 | // Verify that the device has gamepad buttons, control sticks, or both. | 60 | // Verify that the device has gamepad buttons, control sticks, or both. |
428 | if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || | 61 | if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || |
429 | sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK | 62 | sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK |
430 | ) { | 63 | ) { |
431 | // This device is a game controller. Store its device ID. | 64 | if (!gameControllerDeviceIds.contains(controllerNumber)) { |
432 | if (deviceId and id and vendorId and productId != 0) { | 65 | gameControllerDeviceIds[controllerNumber] = YuzuPhysicalDevice( |
433 | // Additionally filter out devices that have no ID | 66 | this, |
434 | gameControllerDeviceIds | 67 | port, |
435 | .takeIf { !it.contains(deviceId) } | 68 | inputSettings[port].useSystemVibrator |
436 | ?.put(deviceId, controllerSlot) | 69 | ) |
437 | controllerSlot++ | ||
438 | } | 70 | } |
71 | port++ | ||
439 | } | 72 | } |
440 | } | 73 | } |
441 | } | 74 | } |
442 | return gameControllerDeviceIds | 75 | return gameControllerDeviceIds |
443 | } | 76 | } |
77 | |||
78 | fun updateControllerData() { | ||
79 | androidControllers = getDevices() | ||
80 | androidControllers.forEach { | ||
81 | NativeInput.registerController(it.value) | ||
82 | } | ||
83 | |||
84 | // Register the input overlay on a dedicated port for all player 1 vibrations | ||
85 | NativeInput.registerController(YuzuInputOverlayDevice(androidControllers.isEmpty(), 100)) | ||
86 | registeredControllers.clear() | ||
87 | NativeInput.getInputDevices().forEach { | ||
88 | registeredControllers.add(ParamPackage(it)) | ||
89 | } | ||
90 | registeredControllers.sortBy { it.get("port", 0) } | ||
91 | } | ||
92 | |||
93 | fun InputDevice.getGUID(): String = String.format("%016x%016x", productId, vendorId) | ||
444 | } | 94 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt new file mode 100755 index 000000000..d5c19c681 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LifecycleUtils.kt | |||
@@ -0,0 +1,38 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.utils | ||
5 | |||
6 | import androidx.lifecycle.Lifecycle | ||
7 | import androidx.lifecycle.LifecycleOwner | ||
8 | import androidx.lifecycle.lifecycleScope | ||
9 | import androidx.lifecycle.repeatOnLifecycle | ||
10 | import kotlinx.coroutines.flow.Flow | ||
11 | import kotlinx.coroutines.flow.MutableStateFlow | ||
12 | import kotlinx.coroutines.launch | ||
13 | |||
14 | /** | ||
15 | * Collects this [Flow] with a given [LifecycleOwner]. | ||
16 | * @param scope [LifecycleOwner] that this [Flow] will be collected with. | ||
17 | * @param repeatState When to repeat collection on this [Flow]. | ||
18 | * @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after | ||
19 | * [stateCollector] has been run. | ||
20 | * @param stateCollector Lambda that receives new state. | ||
21 | */ | ||
22 | inline fun <reified T> Flow<T>.collect( | ||
23 | scope: LifecycleOwner, | ||
24 | repeatState: Lifecycle.State = Lifecycle.State.CREATED, | ||
25 | crossinline resetState: () -> Unit = {}, | ||
26 | crossinline stateCollector: (state: T) -> Unit | ||
27 | ) { | ||
28 | scope.apply { | ||
29 | lifecycleScope.launch { | ||
30 | repeatOnLifecycle(repeatState) { | ||
31 | this@collect.collect { | ||
32 | stateCollector(it) | ||
33 | resetState() | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt index a4c14b3a7..7228f25d2 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt | |||
@@ -6,6 +6,8 @@ package org.yuzu.yuzu_emu.utils | |||
6 | import org.yuzu.yuzu_emu.model.GameDir | 6 | import org.yuzu.yuzu_emu.model.GameDir |
7 | import org.yuzu.yuzu_emu.overlay.model.OverlayControlData | 7 | import org.yuzu.yuzu_emu.overlay.model.OverlayControlData |
8 | 8 | ||
9 | import org.yuzu.yuzu_emu.features.input.model.PlayerInput | ||
10 | |||
9 | object NativeConfig { | 11 | object NativeConfig { |
10 | /** | 12 | /** |
11 | * Loads global config. | 13 | * Loads global config. |
@@ -168,4 +170,17 @@ object NativeConfig { | |||
168 | */ | 170 | */ |
169 | @Synchronized | 171 | @Synchronized |
170 | external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>) | 172 | external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>) |
173 | |||
174 | @Synchronized | ||
175 | external fun getInputSettings(global: Boolean): Array<PlayerInput> | ||
176 | |||
177 | @Synchronized | ||
178 | external fun setInputSettings(value: Array<PlayerInput>, global: Boolean) | ||
179 | |||
180 | /** | ||
181 | * Saves control values for a specific player | ||
182 | * Must be used when per game config is loaded | ||
183 | */ | ||
184 | @Synchronized | ||
185 | external fun saveControlPlayerValues() | ||
171 | } | 186 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt index 68ed66565..331b7ddca 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt | |||
@@ -14,7 +14,7 @@ import android.os.Build | |||
14 | import android.os.Handler | 14 | import android.os.Handler |
15 | import android.os.Looper | 15 | import android.os.Looper |
16 | import java.io.IOException | 16 | import java.io.IOException |
17 | import org.yuzu.yuzu_emu.NativeLibrary | 17 | import org.yuzu.yuzu_emu.features.input.NativeInput |
18 | 18 | ||
19 | class NfcReader(private val activity: Activity) { | 19 | class NfcReader(private val activity: Activity) { |
20 | private var nfcAdapter: NfcAdapter? = null | 20 | private var nfcAdapter: NfcAdapter? = null |
@@ -76,12 +76,12 @@ class NfcReader(private val activity: Activity) { | |||
76 | amiibo.connect() | 76 | amiibo.connect() |
77 | 77 | ||
78 | val tagData = ntag215ReadAll(amiibo) ?: return | 78 | val tagData = ntag215ReadAll(amiibo) ?: return |
79 | NativeLibrary.onReadNfcTag(tagData) | 79 | NativeInput.onReadNfcTag(tagData) |
80 | 80 | ||
81 | nfcAdapter?.ignore( | 81 | nfcAdapter?.ignore( |
82 | tag, | 82 | tag, |
83 | 1000, | 83 | 1000, |
84 | { NativeLibrary.onRemoveNfcTag() }, | 84 | { NativeInput.onRemoveNfcTag() }, |
85 | Handler(Looper.getMainLooper()) | 85 | Handler(Looper.getMainLooper()) |
86 | ) | 86 | ) |
87 | } | 87 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt new file mode 100755 index 000000000..83fc7da3c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ParamPackage.kt | |||
@@ -0,0 +1,141 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | package org.yuzu.yuzu_emu.utils | ||
5 | |||
6 | // Kotlin version of src/common/param_package.h | ||
7 | class ParamPackage(serialized: String = "") { | ||
8 | private val KEY_VALUE_SEPARATOR = ":" | ||
9 | private val PARAM_SEPARATOR = "," | ||
10 | |||
11 | private val ESCAPE_CHARACTER = "$" | ||
12 | private val KEY_VALUE_SEPARATOR_ESCAPE = "$0" | ||
13 | private val PARAM_SEPARATOR_ESCAPE = "$1" | ||
14 | private val ESCAPE_CHARACTER_ESCAPE = "$2" | ||
15 | |||
16 | private val EMPTY_PLACEHOLDER = "[empty]" | ||
17 | |||
18 | val data = mutableMapOf<String, String>() | ||
19 | |||
20 | init { | ||
21 | val pairs = serialized.split(PARAM_SEPARATOR) | ||
22 | for (pair in pairs) { | ||
23 | val keyValue = pair.split(KEY_VALUE_SEPARATOR).toMutableList() | ||
24 | if (keyValue.size != 2) { | ||
25 | Log.error("[ParamPackage] Invalid key pair $keyValue") | ||
26 | continue | ||
27 | } | ||
28 | |||
29 | keyValue.forEachIndexed { i: Int, _: String -> | ||
30 | keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR_ESCAPE, KEY_VALUE_SEPARATOR) | ||
31 | keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR_ESCAPE, PARAM_SEPARATOR) | ||
32 | keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER_ESCAPE, ESCAPE_CHARACTER) | ||
33 | } | ||
34 | |||
35 | set(keyValue[0], keyValue[1]) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | constructor(params: List<Pair<String, String>>) : this() { | ||
40 | params.forEach { | ||
41 | data[it.first] = it.second | ||
42 | } | ||
43 | } | ||
44 | |||
45 | fun serialize(): String { | ||
46 | if (data.isEmpty()) { | ||
47 | return EMPTY_PLACEHOLDER | ||
48 | } | ||
49 | |||
50 | val result = StringBuilder() | ||
51 | data.forEach { | ||
52 | val keyValue = mutableListOf(it.key, it.value) | ||
53 | keyValue.forEachIndexed { i, _ -> | ||
54 | keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER, ESCAPE_CHARACTER_ESCAPE) | ||
55 | keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR, PARAM_SEPARATOR_ESCAPE) | ||
56 | keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR_ESCAPE) | ||
57 | } | ||
58 | result.append("${keyValue[0]}$KEY_VALUE_SEPARATOR${keyValue[1]}$PARAM_SEPARATOR") | ||
59 | } | ||
60 | return result.removeSuffix(PARAM_SEPARATOR).toString() | ||
61 | } | ||
62 | |||
63 | fun get(key: String, defaultValue: String): String = | ||
64 | if (has(key)) { | ||
65 | data[key]!! | ||
66 | } else { | ||
67 | Log.debug("[ParamPackage] key $key not found") | ||
68 | defaultValue | ||
69 | } | ||
70 | |||
71 | fun get(key: String, defaultValue: Int): Int = | ||
72 | if (has(key)) { | ||
73 | try { | ||
74 | data[key]!!.toInt() | ||
75 | } catch (e: NumberFormatException) { | ||
76 | Log.debug("[ParamPackage] failed to convert ${data[key]!!} to int") | ||
77 | defaultValue | ||
78 | } | ||
79 | } else { | ||
80 | Log.debug("[ParamPackage] key $key not found") | ||
81 | defaultValue | ||
82 | } | ||
83 | |||
84 | private fun Int.toBoolean(): Boolean = | ||
85 | if (this == 1) { | ||
86 | true | ||
87 | } else if (this == 0) { | ||
88 | false | ||
89 | } else { | ||
90 | throw Exception("Tried to convert a value to a boolean that was not 0 or 1!") | ||
91 | } | ||
92 | |||
93 | fun get(key: String, defaultValue: Boolean): Boolean = | ||
94 | if (has(key)) { | ||
95 | try { | ||
96 | get(key, if (defaultValue) 1 else 0).toBoolean() | ||
97 | } catch (e: Exception) { | ||
98 | Log.debug("[ParamPackage] failed to convert ${data[key]!!} to boolean") | ||
99 | defaultValue | ||
100 | } | ||
101 | } else { | ||
102 | Log.debug("[ParamPackage] key $key not found") | ||
103 | defaultValue | ||
104 | } | ||
105 | |||
106 | fun get(key: String, defaultValue: Float): Float = | ||
107 | if (has(key)) { | ||
108 | try { | ||
109 | data[key]!!.toFloat() | ||
110 | } catch (e: NumberFormatException) { | ||
111 | Log.debug("[ParamPackage] failed to convert ${data[key]!!} to float") | ||
112 | defaultValue | ||
113 | } | ||
114 | } else { | ||
115 | Log.debug("[ParamPackage] key $key not found") | ||
116 | defaultValue | ||
117 | } | ||
118 | |||
119 | fun set(key: String, value: String) { | ||
120 | data[key] = value | ||
121 | } | ||
122 | |||
123 | fun set(key: String, value: Int) { | ||
124 | data[key] = value.toString() | ||
125 | } | ||
126 | |||
127 | fun Boolean.toInt(): Int = if (this) 1 else 0 | ||
128 | fun set(key: String, value: Boolean) { | ||
129 | data[key] = value.toInt().toString() | ||
130 | } | ||
131 | |||
132 | fun set(key: String, value: Float) { | ||
133 | data[key] = value.toString() | ||
134 | } | ||
135 | |||
136 | fun has(key: String): Boolean = data.containsKey(key) | ||
137 | |||
138 | fun erase(key: String) = data.remove(key) | ||
139 | |||
140 | fun clear() = data.clear() | ||
141 | } | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt index ffbfa9337..244091aec 100755 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt | |||
@@ -3,8 +3,10 @@ | |||
3 | 3 | ||
4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
5 | 5 | ||
6 | import android.text.TextUtils | ||
6 | import android.view.View | 7 | import android.view.View |
7 | import android.view.ViewGroup | 8 | import android.view.ViewGroup |
9 | import android.widget.TextView | ||
8 | 10 | ||
9 | object ViewUtils { | 11 | object ViewUtils { |
10 | fun showView(view: View, length: Long = 300) { | 12 | fun showView(view: View, length: Long = 300) { |
@@ -57,4 +59,35 @@ object ViewUtils { | |||
57 | } | 59 | } |
58 | this.layoutParams = layoutParams | 60 | this.layoutParams = layoutParams |
59 | } | 61 | } |
62 | |||
63 | /** | ||
64 | * Shows or hides a view. | ||
65 | * @param visible Whether a view will be made View.VISIBLE or View.INVISIBLE/GONE. | ||
66 | * @param gone Optional parameter for hiding a view. Uses View.GONE if true and View.INVISIBLE otherwise. | ||
67 | */ | ||
68 | fun View.setVisible(visible: Boolean, gone: Boolean = true) { | ||
69 | visibility = if (visible) { | ||
70 | View.VISIBLE | ||
71 | } else { | ||
72 | if (gone) { | ||
73 | View.GONE | ||
74 | } else { | ||
75 | View.INVISIBLE | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Starts a marquee on some text. | ||
82 | * @param delay Optional parameter for changing the start delay. 3 seconds of delay by default. | ||
83 | */ | ||
84 | fun TextView.marquee(delay: Long = 3000) { | ||
85 | ellipsize = null | ||
86 | marqueeRepeatLimit = -1 | ||
87 | isSingleLine = true | ||
88 | postDelayed({ | ||
89 | ellipsize = TextUtils.TruncateAt.MARQUEE | ||
90 | isSelected = true | ||
91 | }, delay) | ||
92 | } | ||
60 | } | 93 | } |
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 20b319c12..ec8ae5c57 100755 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt | |||
@@ -12,6 +12,7 @@ add_library(yuzu-android SHARED | |||
12 | native_log.cpp | 12 | native_log.cpp |
13 | android_config.cpp | 13 | android_config.cpp |
14 | android_config.h | 14 | android_config.h |
15 | native_input.cpp | ||
15 | ) | 16 | ) |
16 | 17 | ||
17 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) | 18 | set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) |
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index e147560c3..a79a64afb 100755 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp | |||
@@ -1,6 +1,8 @@ | |||
1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
3 | 3 | ||
4 | #include <common/logging/log.h> | ||
5 | #include <input_common/main.h> | ||
4 | #include "android_config.h" | 6 | #include "android_config.h" |
5 | #include "android_settings.h" | 7 | #include "android_settings.h" |
6 | #include "common/settings_setting.h" | 8 | #include "common/settings_setting.h" |
@@ -32,6 +34,7 @@ void AndroidConfig::ReadAndroidValues() { | |||
32 | ReadOverlayValues(); | 34 | ReadOverlayValues(); |
33 | } | 35 | } |
34 | ReadDriverValues(); | 36 | ReadDriverValues(); |
37 | ReadAndroidControlValues(); | ||
35 | } | 38 | } |
36 | 39 | ||
37 | void AndroidConfig::ReadAndroidUIValues() { | 40 | void AndroidConfig::ReadAndroidUIValues() { |
@@ -107,6 +110,76 @@ void AndroidConfig::ReadOverlayValues() { | |||
107 | EndGroup(); | 110 | EndGroup(); |
108 | } | 111 | } |
109 | 112 | ||
113 | void AndroidConfig::ReadAndroidPlayerValues(std::size_t player_index) { | ||
114 | std::string player_prefix; | ||
115 | if (type != ConfigType::InputProfile) { | ||
116 | player_prefix.append("player_").append(ToString(player_index)).append("_"); | ||
117 | } | ||
118 | |||
119 | auto& player = Settings::values.players.GetValue()[player_index]; | ||
120 | if (IsCustomConfig()) { | ||
121 | const auto profile_name = | ||
122 | ReadStringSetting(std::string(player_prefix).append("profile_name")); | ||
123 | if (profile_name.empty()) { | ||
124 | // Use the global input config | ||
125 | player = Settings::values.players.GetValue(true)[player_index]; | ||
126 | player.profile_name = ""; | ||
127 | return; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | // Android doesn't have default options for controllers. We have the input overlay for that. | ||
132 | for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { | ||
133 | const std::string default_param; | ||
134 | auto& player_buttons = player.buttons[i]; | ||
135 | |||
136 | player_buttons = ReadStringSetting( | ||
137 | std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param); | ||
138 | if (player_buttons.empty()) { | ||
139 | player_buttons = default_param; | ||
140 | } | ||
141 | } | ||
142 | for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { | ||
143 | const std::string default_param; | ||
144 | auto& player_analogs = player.analogs[i]; | ||
145 | |||
146 | player_analogs = ReadStringSetting( | ||
147 | std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param); | ||
148 | if (player_analogs.empty()) { | ||
149 | player_analogs = default_param; | ||
150 | } | ||
151 | } | ||
152 | for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { | ||
153 | const std::string default_param; | ||
154 | auto& player_motions = player.motions[i]; | ||
155 | |||
156 | player_motions = ReadStringSetting( | ||
157 | std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param); | ||
158 | if (player_motions.empty()) { | ||
159 | player_motions = default_param; | ||
160 | } | ||
161 | } | ||
162 | player.use_system_vibrator = ReadBooleanSetting( | ||
163 | std::string(player_prefix).append("use_system_vibrator"), player_index == 0); | ||
164 | } | ||
165 | |||
166 | void AndroidConfig::ReadAndroidControlValues() { | ||
167 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
168 | |||
169 | Settings::values.players.SetGlobal(!IsCustomConfig()); | ||
170 | for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { | ||
171 | ReadAndroidPlayerValues(p); | ||
172 | } | ||
173 | if (IsCustomConfig()) { | ||
174 | EndGroup(); | ||
175 | return; | ||
176 | } | ||
177 | // ReadDebugControlValues(); | ||
178 | // ReadHidbusValues(); | ||
179 | |||
180 | EndGroup(); | ||
181 | } | ||
182 | |||
110 | void AndroidConfig::SaveAndroidValues() { | 183 | void AndroidConfig::SaveAndroidValues() { |
111 | if (global) { | 184 | if (global) { |
112 | SaveAndroidUIValues(); | 185 | SaveAndroidUIValues(); |
@@ -114,6 +187,7 @@ void AndroidConfig::SaveAndroidValues() { | |||
114 | SaveOverlayValues(); | 187 | SaveOverlayValues(); |
115 | } | 188 | } |
116 | SaveDriverValues(); | 189 | SaveDriverValues(); |
190 | SaveAndroidControlValues(); | ||
117 | 191 | ||
118 | WriteToIni(); | 192 | WriteToIni(); |
119 | } | 193 | } |
@@ -187,6 +261,52 @@ void AndroidConfig::SaveOverlayValues() { | |||
187 | EndGroup(); | 261 | EndGroup(); |
188 | } | 262 | } |
189 | 263 | ||
264 | void AndroidConfig::SaveAndroidPlayerValues(std::size_t player_index) { | ||
265 | std::string player_prefix; | ||
266 | if (type != ConfigType::InputProfile) { | ||
267 | player_prefix = std::string("player_").append(ToString(player_index)).append("_"); | ||
268 | } | ||
269 | |||
270 | const auto& player = Settings::values.players.GetValue()[player_index]; | ||
271 | if (IsCustomConfig() && player.profile_name.empty()) { | ||
272 | // No custom profile selected | ||
273 | return; | ||
274 | } | ||
275 | |||
276 | const std::string default_param; | ||
277 | for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { | ||
278 | WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]), | ||
279 | player.buttons[i], std::make_optional(default_param)); | ||
280 | } | ||
281 | for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { | ||
282 | WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), | ||
283 | player.analogs[i], std::make_optional(default_param)); | ||
284 | } | ||
285 | for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) { | ||
286 | WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), | ||
287 | player.motions[i], std::make_optional(default_param)); | ||
288 | } | ||
289 | WriteBooleanSetting(std::string(player_prefix).append("use_system_vibrator"), | ||
290 | player.use_system_vibrator, std::make_optional(player_index == 0)); | ||
291 | } | ||
292 | |||
293 | void AndroidConfig::SaveAndroidControlValues() { | ||
294 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
295 | |||
296 | Settings::values.players.SetGlobal(!IsCustomConfig()); | ||
297 | for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { | ||
298 | SaveAndroidPlayerValues(p); | ||
299 | } | ||
300 | if (IsCustomConfig()) { | ||
301 | EndGroup(); | ||
302 | return; | ||
303 | } | ||
304 | // SaveDebugControlValues(); | ||
305 | // SaveHidbusValues(); | ||
306 | |||
307 | EndGroup(); | ||
308 | } | ||
309 | |||
190 | std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { | 310 | std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { |
191 | auto& map = Settings::values.linkage.by_category; | 311 | auto& map = Settings::values.linkage.by_category; |
192 | if (map.contains(category)) { | 312 | if (map.contains(category)) { |
@@ -194,3 +314,24 @@ std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings:: | |||
194 | } | 314 | } |
195 | return AndroidSettings::values.linkage.by_category[category]; | 315 | return AndroidSettings::values.linkage.by_category[category]; |
196 | } | 316 | } |
317 | |||
318 | void AndroidConfig::ReadAndroidControlPlayerValues(std::size_t player_index) { | ||
319 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
320 | |||
321 | ReadPlayerValues(player_index); | ||
322 | ReadAndroidPlayerValues(player_index); | ||
323 | |||
324 | EndGroup(); | ||
325 | } | ||
326 | |||
327 | void AndroidConfig::SaveAndroidControlPlayerValues(std::size_t player_index) { | ||
328 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
329 | |||
330 | LOG_DEBUG(Config, "Saving players control configuration values"); | ||
331 | SavePlayerValues(player_index); | ||
332 | SaveAndroidPlayerValues(player_index); | ||
333 | |||
334 | EndGroup(); | ||
335 | |||
336 | WriteToIni(); | ||
337 | } | ||
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h index 693e1e3f0..28ef5d0a8 100755 --- a/src/android/app/src/main/jni/android_config.h +++ b/src/android/app/src/main/jni/android_config.h | |||
@@ -13,7 +13,12 @@ public: | |||
13 | void ReloadAllValues() override; | 13 | void ReloadAllValues() override; |
14 | void SaveAllValues() override; | 14 | void SaveAllValues() override; |
15 | 15 | ||
16 | void ReadAndroidControlPlayerValues(std::size_t player_index); | ||
17 | void SaveAndroidControlPlayerValues(std::size_t player_index); | ||
18 | |||
16 | protected: | 19 | protected: |
20 | void ReadAndroidPlayerValues(std::size_t player_index); | ||
21 | void ReadAndroidControlValues(); | ||
17 | void ReadAndroidValues(); | 22 | void ReadAndroidValues(); |
18 | void ReadAndroidUIValues(); | 23 | void ReadAndroidUIValues(); |
19 | void ReadDriverValues(); | 24 | void ReadDriverValues(); |
@@ -27,6 +32,8 @@ protected: | |||
27 | void ReadUILayoutValues() override {} | 32 | void ReadUILayoutValues() override {} |
28 | void ReadMultiplayerValues() override {} | 33 | void ReadMultiplayerValues() override {} |
29 | 34 | ||
35 | void SaveAndroidPlayerValues(std::size_t player_index); | ||
36 | void SaveAndroidControlValues(); | ||
30 | void SaveAndroidValues(); | 37 | void SaveAndroidValues(); |
31 | void SaveAndroidUIValues(); | 38 | void SaveAndroidUIValues(); |
32 | void SaveDriverValues(); | 39 | void SaveDriverValues(); |
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index c927cddda..2768a01c9 100755 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp | |||
@@ -5,6 +5,7 @@ | |||
5 | 5 | ||
6 | #include "common/android/id_cache.h" | 6 | #include "common/android/id_cache.h" |
7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
8 | #include "input_common/drivers/android.h" | ||
8 | #include "input_common/drivers/touch_screen.h" | 9 | #include "input_common/drivers/touch_screen.h" |
9 | #include "input_common/drivers/virtual_amiibo.h" | 10 | #include "input_common/drivers/virtual_amiibo.h" |
10 | #include "input_common/drivers/virtual_gamepad.h" | 11 | #include "input_common/drivers/virtual_gamepad.h" |
@@ -22,43 +23,6 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { | |||
22 | window_info.render_surface = reinterpret_cast<void*>(surface); | 23 | window_info.render_surface = reinterpret_cast<void*>(surface); |
23 | } | 24 | } |
24 | 25 | ||
25 | void EmuWindow_Android::OnTouchPressed(int id, float x, float y) { | ||
26 | const auto [touch_x, touch_y] = MapToTouchScreen(x, y); | ||
27 | m_input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id); | ||
28 | } | ||
29 | |||
30 | void EmuWindow_Android::OnTouchMoved(int id, float x, float y) { | ||
31 | const auto [touch_x, touch_y] = MapToTouchScreen(x, y); | ||
32 | m_input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id); | ||
33 | } | ||
34 | |||
35 | void EmuWindow_Android::OnTouchReleased(int id) { | ||
36 | m_input_subsystem->GetTouchScreen()->TouchReleased(id); | ||
37 | } | ||
38 | |||
39 | void EmuWindow_Android::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) { | ||
40 | m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed); | ||
41 | } | ||
42 | |||
43 | void EmuWindow_Android::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) { | ||
44 | m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y); | ||
45 | } | ||
46 | |||
47 | void EmuWindow_Android::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, | ||
48 | float gyro_y, float gyro_z, float accel_x, | ||
49 | float accel_y, float accel_z) { | ||
50 | m_input_subsystem->GetVirtualGamepad()->SetMotionState( | ||
51 | player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); | ||
52 | } | ||
53 | |||
54 | void EmuWindow_Android::OnReadNfcTag(std::span<u8> data) { | ||
55 | m_input_subsystem->GetVirtualAmiibo()->LoadAmiibo(data); | ||
56 | } | ||
57 | |||
58 | void EmuWindow_Android::OnRemoveNfcTag() { | ||
59 | m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo(); | ||
60 | } | ||
61 | |||
62 | void EmuWindow_Android::OnFrameDisplayed() { | 26 | void EmuWindow_Android::OnFrameDisplayed() { |
63 | if (!m_first_frame) { | 27 | if (!m_first_frame) { |
64 | Common::Android::RunJNIOnFiber<void>( | 28 | Common::Android::RunJNIOnFiber<void>( |
@@ -67,10 +31,9 @@ void EmuWindow_Android::OnFrameDisplayed() { | |||
67 | } | 31 | } |
68 | } | 32 | } |
69 | 33 | ||
70 | EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem, | 34 | EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, |
71 | ANativeWindow* surface, | ||
72 | std::shared_ptr<Common::DynamicLibrary> driver_library) | 35 | std::shared_ptr<Common::DynamicLibrary> driver_library) |
73 | : m_input_subsystem{input_subsystem}, m_driver_library{driver_library} { | 36 | : m_driver_library{driver_library} { |
74 | LOG_INFO(Frontend, "initializing"); | 37 | LOG_INFO(Frontend, "initializing"); |
75 | 38 | ||
76 | if (!surface) { | 39 | if (!surface) { |
@@ -80,10 +43,4 @@ EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsyste | |||
80 | 43 | ||
81 | OnSurfaceChanged(surface); | 44 | OnSurfaceChanged(surface); |
82 | window_info.type = Core::Frontend::WindowSystemType::Android; | 45 | window_info.type = Core::Frontend::WindowSystemType::Android; |
83 | |||
84 | m_input_subsystem->Initialize(); | ||
85 | } | ||
86 | |||
87 | EmuWindow_Android::~EmuWindow_Android() { | ||
88 | m_input_subsystem->Shutdown(); | ||
89 | } | 46 | } |
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index a34a0e479..34704ae95 100755 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h | |||
@@ -30,21 +30,12 @@ private: | |||
30 | class EmuWindow_Android final : public Core::Frontend::EmuWindow { | 30 | class EmuWindow_Android final : public Core::Frontend::EmuWindow { |
31 | 31 | ||
32 | public: | 32 | public: |
33 | EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem, ANativeWindow* surface, | 33 | EmuWindow_Android(ANativeWindow* surface, |
34 | std::shared_ptr<Common::DynamicLibrary> driver_library); | 34 | std::shared_ptr<Common::DynamicLibrary> driver_library); |
35 | 35 | ||
36 | ~EmuWindow_Android(); | 36 | ~EmuWindow_Android() = default; |
37 | 37 | ||
38 | void OnSurfaceChanged(ANativeWindow* surface); | 38 | void OnSurfaceChanged(ANativeWindow* surface); |
39 | void OnTouchPressed(int id, float x, float y); | ||
40 | void OnTouchMoved(int id, float x, float y); | ||
41 | void OnTouchReleased(int id); | ||
42 | void OnGamepadButtonEvent(int player_index, int button_id, bool pressed); | ||
43 | void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y); | ||
44 | void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, | ||
45 | float gyro_z, float accel_x, float accel_y, float accel_z); | ||
46 | void OnReadNfcTag(std::span<u8> data); | ||
47 | void OnRemoveNfcTag(); | ||
48 | void OnFrameDisplayed() override; | 39 | void OnFrameDisplayed() override; |
49 | 40 | ||
50 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override { | 41 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override { |
@@ -55,8 +46,6 @@ public: | |||
55 | }; | 46 | }; |
56 | 47 | ||
57 | private: | 48 | private: |
58 | InputCommon::InputSubsystem* m_input_subsystem{}; | ||
59 | |||
60 | float m_window_width{}; | 49 | float m_window_width{}; |
61 | float m_window_height{}; | 50 | float m_window_height{}; |
62 | 51 | ||
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index a4d8454e8..50cef5d2a 100755 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
@@ -88,6 +88,10 @@ FileSys::ManualContentProvider* EmulationSession::GetContentProvider() { | |||
88 | return m_manual_provider.get(); | 88 | return m_manual_provider.get(); |
89 | } | 89 | } |
90 | 90 | ||
91 | InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() { | ||
92 | return m_input_subsystem; | ||
93 | } | ||
94 | |||
91 | const EmuWindow_Android& EmulationSession::Window() const { | 95 | const EmuWindow_Android& EmulationSession::Window() const { |
92 | return *m_window; | 96 | return *m_window; |
93 | } | 97 | } |
@@ -198,6 +202,8 @@ void EmulationSession::InitializeSystem(bool reload) { | |||
198 | Common::Log::Initialize(); | 202 | Common::Log::Initialize(); |
199 | Common::Log::SetColorConsoleBackendEnabled(true); | 203 | Common::Log::SetColorConsoleBackendEnabled(true); |
200 | Common::Log::Start(); | 204 | Common::Log::Start(); |
205 | |||
206 | m_input_subsystem.Initialize(); | ||
201 | } | 207 | } |
202 | 208 | ||
203 | // Initialize filesystem. | 209 | // Initialize filesystem. |
@@ -222,8 +228,7 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string | |||
222 | std::scoped_lock lock(m_mutex); | 228 | std::scoped_lock lock(m_mutex); |
223 | 229 | ||
224 | // Create the render window. | 230 | // Create the render window. |
225 | m_window = | 231 | m_window = std::make_unique<EmuWindow_Android>(m_native_window, m_vulkan_library); |
226 | std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library); | ||
227 | 232 | ||
228 | // Initialize system. | 233 | // Initialize system. |
229 | jauto android_keyboard = std::make_unique<Common::Android::SoftwareKeyboard::AndroidKeyboard>(); | 234 | jauto android_keyboard = std::make_unique<Common::Android::SoftwareKeyboard::AndroidKeyboard>(); |
@@ -355,60 +360,6 @@ void EmulationSession::RunEmulation() { | |||
355 | m_applet_id = static_cast<int>(Service::AM::AppletId::Application); | 360 | m_applet_id = static_cast<int>(Service::AM::AppletId::Application); |
356 | } | 361 | } |
357 | 362 | ||
358 | bool EmulationSession::IsHandheldOnly() { | ||
359 | jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); | ||
360 | |||
361 | if (npad_style_set.fullkey == 1) { | ||
362 | return false; | ||
363 | } | ||
364 | |||
365 | if (npad_style_set.handheld == 0) { | ||
366 | return false; | ||
367 | } | ||
368 | |||
369 | return !Settings::IsDockedMode(); | ||
370 | } | ||
371 | |||
372 | void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) { | ||
373 | jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); | ||
374 | controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); | ||
375 | } | ||
376 | |||
377 | void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) { | ||
378 | jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); | ||
379 | |||
380 | // Ensure that player1 is configured correctly and handheld disconnected | ||
381 | if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) { | ||
382 | jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); | ||
383 | |||
384 | if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { | ||
385 | handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); | ||
386 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey); | ||
387 | handheld->Disconnect(); | ||
388 | } | ||
389 | } | ||
390 | |||
391 | // Ensure that handheld is configured correctly and player 1 disconnected | ||
392 | if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) { | ||
393 | jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
394 | |||
395 | if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { | ||
396 | player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); | ||
397 | controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); | ||
398 | player1->Disconnect(); | ||
399 | } | ||
400 | } | ||
401 | |||
402 | if (!controller->IsConnected()) { | ||
403 | controller->Connect(); | ||
404 | } | ||
405 | } | ||
406 | |||
407 | void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) { | ||
408 | jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); | ||
409 | controller->Disconnect(); | ||
410 | } | ||
411 | |||
412 | Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { | 363 | Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { |
413 | return m_software_keyboard; | 364 | return m_software_keyboard; |
414 | } | 365 | } |
@@ -574,14 +525,14 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getSystemDriverInfo( | |||
574 | nullptr, nullptr, file_redirect_dir_, nullptr); | 525 | nullptr, nullptr, file_redirect_dir_, nullptr); |
575 | auto driver_library = std::make_shared<Common::DynamicLibrary>(handle); | 526 | auto driver_library = std::make_shared<Common::DynamicLibrary>(handle); |
576 | InputCommon::InputSubsystem input_subsystem; | 527 | InputCommon::InputSubsystem input_subsystem; |
577 | auto m_window = std::make_unique<EmuWindow_Android>( | 528 | auto window = |
578 | &input_subsystem, ANativeWindow_fromSurface(env, j_surf), driver_library); | 529 | std::make_unique<EmuWindow_Android>(ANativeWindow_fromSurface(env, j_surf), driver_library); |
579 | 530 | ||
580 | Vulkan::vk::InstanceDispatch dld; | 531 | Vulkan::vk::InstanceDispatch dld; |
581 | Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance( | 532 | Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance( |
582 | *driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android); | 533 | *driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android); |
583 | 534 | ||
584 | auto surface = Vulkan::CreateSurface(vk_instance, m_window->GetWindowInfo()); | 535 | auto surface = Vulkan::CreateSurface(vk_instance, window->GetWindowInfo()); |
585 | 536 | ||
586 | auto device = Vulkan::CreateDevice(vk_instance, dld, *surface); | 537 | auto device = Vulkan::CreateDevice(vk_instance, dld, *surface); |
587 | 538 | ||
@@ -622,103 +573,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz | |||
622 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); | 573 | return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); |
623 | } | 574 | } |
624 | 575 | ||
625 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { | ||
626 | return EmulationSession::GetInstance().IsHandheldOnly(); | ||
627 | } | ||
628 | |||
629 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz, | ||
630 | jint j_device, jint j_type) { | ||
631 | if (EmulationSession::GetInstance().IsRunning()) { | ||
632 | EmulationSession::GetInstance().SetDeviceType(j_device, j_type); | ||
633 | } | ||
634 | return static_cast<jboolean>(true); | ||
635 | } | ||
636 | |||
637 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz, | ||
638 | jint j_device) { | ||
639 | if (EmulationSession::GetInstance().IsRunning()) { | ||
640 | EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); | ||
641 | } | ||
642 | return static_cast<jboolean>(true); | ||
643 | } | ||
644 | |||
645 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz, | ||
646 | jint j_device) { | ||
647 | if (EmulationSession::GetInstance().IsRunning()) { | ||
648 | EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device); | ||
649 | } | ||
650 | return static_cast<jboolean>(true); | ||
651 | } | ||
652 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz, | ||
653 | jint j_device, jint j_button, | ||
654 | jint action) { | ||
655 | if (EmulationSession::GetInstance().IsRunning()) { | ||
656 | // Ensure gamepad is connected | ||
657 | EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); | ||
658 | EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button, | ||
659 | action != 0); | ||
660 | } | ||
661 | return static_cast<jboolean>(true); | ||
662 | } | ||
663 | |||
664 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz, | ||
665 | jint j_device, jint stick_id, | ||
666 | jfloat x, jfloat y) { | ||
667 | if (EmulationSession::GetInstance().IsRunning()) { | ||
668 | EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(j_device, stick_id, x, y); | ||
669 | } | ||
670 | return static_cast<jboolean>(true); | ||
671 | } | ||
672 | |||
673 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent( | ||
674 | JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y, | ||
675 | jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) { | ||
676 | if (EmulationSession::GetInstance().IsRunning()) { | ||
677 | EmulationSession::GetInstance().Window().OnGamepadMotionEvent( | ||
678 | j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z); | ||
679 | } | ||
680 | return static_cast<jboolean>(true); | ||
681 | } | ||
682 | |||
683 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz, | ||
684 | jbyteArray j_data) { | ||
685 | jboolean isCopy{false}; | ||
686 | std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)), | ||
687 | static_cast<size_t>(env->GetArrayLength(j_data))); | ||
688 | |||
689 | if (EmulationSession::GetInstance().IsRunning()) { | ||
690 | EmulationSession::GetInstance().Window().OnReadNfcTag(data); | ||
691 | } | ||
692 | return static_cast<jboolean>(true); | ||
693 | } | ||
694 | |||
695 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) { | ||
696 | if (EmulationSession::GetInstance().IsRunning()) { | ||
697 | EmulationSession::GetInstance().Window().OnRemoveNfcTag(); | ||
698 | } | ||
699 | return static_cast<jboolean>(true); | ||
700 | } | ||
701 | |||
702 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id, | ||
703 | jfloat x, jfloat y) { | ||
704 | if (EmulationSession::GetInstance().IsRunning()) { | ||
705 | EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y); | ||
706 | } | ||
707 | } | ||
708 | |||
709 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id, | ||
710 | jfloat x, jfloat y) { | ||
711 | if (EmulationSession::GetInstance().IsRunning()) { | ||
712 | EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y); | ||
713 | } | ||
714 | } | ||
715 | |||
716 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) { | ||
717 | if (EmulationSession::GetInstance().IsRunning()) { | ||
718 | EmulationSession::GetInstance().Window().OnTouchReleased(id); | ||
719 | } | ||
720 | } | ||
721 | |||
722 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz, | 576 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz, |
723 | jboolean reload) { | 577 | jboolean reload) { |
724 | // Initialize the emulated system. | 578 | // Initialize the emulated system. |
@@ -759,6 +613,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject | |||
759 | 613 | ||
760 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) { | 614 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) { |
761 | EmulationSession::GetInstance().System().ApplySettings(); | 615 | EmulationSession::GetInstance().System().ApplySettings(); |
616 | EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices(); | ||
762 | } | 617 | } |
763 | 618 | ||
764 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) { | 619 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) { |
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 47936e305..6a4551ada 100755 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h | |||
@@ -23,6 +23,7 @@ public: | |||
23 | const Core::System& System() const; | 23 | const Core::System& System() const; |
24 | Core::System& System(); | 24 | Core::System& System(); |
25 | FileSys::ManualContentProvider* GetContentProvider(); | 25 | FileSys::ManualContentProvider* GetContentProvider(); |
26 | InputCommon::InputSubsystem& GetInputSubsystem(); | ||
26 | 27 | ||
27 | const EmuWindow_Android& Window() const; | 28 | const EmuWindow_Android& Window() const; |
28 | EmuWindow_Android& Window(); | 29 | EmuWindow_Android& Window(); |
@@ -50,10 +51,6 @@ public: | |||
50 | const std::size_t program_index, | 51 | const std::size_t program_index, |
51 | const bool frontend_initiated); | 52 | const bool frontend_initiated); |
52 | 53 | ||
53 | bool IsHandheldOnly(); | ||
54 | void SetDeviceType([[maybe_unused]] int index, int type); | ||
55 | void OnGamepadConnectEvent([[maybe_unused]] int index); | ||
56 | void OnGamepadDisconnectEvent([[maybe_unused]] int index); | ||
57 | Common::Android::SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard(); | 54 | Common::Android::SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard(); |
58 | 55 | ||
59 | static void OnEmulationStarted(); | 56 | static void OnEmulationStarted(); |
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 8ae10fbc7..0b26280c6 100755 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp | |||
@@ -3,7 +3,6 @@ | |||
3 | 3 | ||
4 | #include <string> | 4 | #include <string> |
5 | 5 | ||
6 | #include <common/fs/fs_util.h> | ||
7 | #include <jni.h> | 6 | #include <jni.h> |
8 | 7 | ||
9 | #include "android_config.h" | 8 | #include "android_config.h" |
@@ -425,4 +424,120 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData( | |||
425 | } | 424 | } |
426 | } | 425 | } |
427 | 426 | ||
427 | jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInputSettings(JNIEnv* env, jobject obj, | ||
428 | jboolean j_global) { | ||
429 | Settings::values.players.SetGlobal(static_cast<bool>(j_global)); | ||
430 | auto& players = Settings::values.players.GetValue(); | ||
431 | jobjectArray j_input_settings = | ||
432 | env->NewObjectArray(players.size(), Common::Android::GetPlayerInputClass(), nullptr); | ||
433 | for (size_t i = 0; i < players.size(); ++i) { | ||
434 | auto j_connected = static_cast<jboolean>(players[i].connected); | ||
435 | |||
436 | jobjectArray j_buttons = env->NewObjectArray( | ||
437 | players[i].buttons.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); | ||
438 | for (size_t j = 0; j < players[i].buttons.size(); ++j) { | ||
439 | env->SetObjectArrayElement(j_buttons, j, | ||
440 | Common::Android::ToJString(env, players[i].buttons[j])); | ||
441 | } | ||
442 | jobjectArray j_analogs = env->NewObjectArray( | ||
443 | players[i].analogs.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); | ||
444 | for (size_t j = 0; j < players[i].analogs.size(); ++j) { | ||
445 | env->SetObjectArrayElement(j_analogs, j, | ||
446 | Common::Android::ToJString(env, players[i].analogs[j])); | ||
447 | } | ||
448 | jobjectArray j_motions = env->NewObjectArray( | ||
449 | players[i].motions.size(), Common::Android::GetStringClass(), env->NewStringUTF("")); | ||
450 | for (size_t j = 0; j < players[i].motions.size(); ++j) { | ||
451 | env->SetObjectArrayElement(j_motions, j, | ||
452 | Common::Android::ToJString(env, players[i].motions[j])); | ||
453 | } | ||
454 | |||
455 | auto j_vibration_enabled = static_cast<jboolean>(players[i].vibration_enabled); | ||
456 | auto j_vibration_strength = static_cast<jint>(players[i].vibration_strength); | ||
457 | |||
458 | auto j_body_color_left = static_cast<jlong>(players[i].body_color_left); | ||
459 | auto j_body_color_right = static_cast<jlong>(players[i].body_color_right); | ||
460 | auto j_button_color_left = static_cast<jlong>(players[i].button_color_left); | ||
461 | auto j_button_color_right = static_cast<jlong>(players[i].button_color_right); | ||
462 | |||
463 | auto j_profile_name = Common::Android::ToJString(env, players[i].profile_name); | ||
464 | |||
465 | auto j_use_system_vibrator = players[i].use_system_vibrator; | ||
466 | |||
467 | jobject playerInput = env->NewObject( | ||
468 | Common::Android::GetPlayerInputClass(), Common::Android::GetPlayerInputConstructor(), | ||
469 | j_connected, j_buttons, j_analogs, j_motions, j_vibration_enabled, j_vibration_strength, | ||
470 | j_body_color_left, j_body_color_right, j_button_color_left, j_button_color_right, | ||
471 | j_profile_name, j_use_system_vibrator); | ||
472 | env->SetObjectArrayElement(j_input_settings, i, playerInput); | ||
473 | } | ||
474 | return j_input_settings; | ||
475 | } | ||
476 | |||
477 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInputSettings(JNIEnv* env, jobject obj, | ||
478 | jobjectArray j_value, | ||
479 | jboolean j_global) { | ||
480 | auto& players = Settings::values.players.GetValue(static_cast<bool>(j_global)); | ||
481 | int playersSize = env->GetArrayLength(j_value); | ||
482 | for (int i = 0; i < playersSize; ++i) { | ||
483 | jobject jplayer = env->GetObjectArrayElement(j_value, i); | ||
484 | |||
485 | players[i].connected = static_cast<bool>( | ||
486 | env->GetBooleanField(jplayer, Common::Android::GetPlayerInputConnectedField())); | ||
487 | |||
488 | auto j_buttons_array = static_cast<jobjectArray>( | ||
489 | env->GetObjectField(jplayer, Common::Android::GetPlayerInputButtonsField())); | ||
490 | int buttons_size = env->GetArrayLength(j_buttons_array); | ||
491 | for (int j = 0; j < buttons_size; ++j) { | ||
492 | auto button = static_cast<jstring>(env->GetObjectArrayElement(j_buttons_array, j)); | ||
493 | players[i].buttons[j] = Common::Android::GetJString(env, button); | ||
494 | } | ||
495 | auto j_analogs_array = static_cast<jobjectArray>( | ||
496 | env->GetObjectField(jplayer, Common::Android::GetPlayerInputAnalogsField())); | ||
497 | int analogs_size = env->GetArrayLength(j_analogs_array); | ||
498 | for (int j = 0; j < analogs_size; ++j) { | ||
499 | auto analog = static_cast<jstring>(env->GetObjectArrayElement(j_analogs_array, j)); | ||
500 | players[i].analogs[j] = Common::Android::GetJString(env, analog); | ||
501 | } | ||
502 | auto j_motions_array = static_cast<jobjectArray>( | ||
503 | env->GetObjectField(jplayer, Common::Android::GetPlayerInputMotionsField())); | ||
504 | int motions_size = env->GetArrayLength(j_motions_array); | ||
505 | for (int j = 0; j < motions_size; ++j) { | ||
506 | auto motion = static_cast<jstring>(env->GetObjectArrayElement(j_motions_array, j)); | ||
507 | players[i].motions[j] = Common::Android::GetJString(env, motion); | ||
508 | } | ||
509 | |||
510 | players[i].vibration_enabled = static_cast<bool>( | ||
511 | env->GetBooleanField(jplayer, Common::Android::GetPlayerInputVibrationEnabledField())); | ||
512 | players[i].vibration_strength = static_cast<int>( | ||
513 | env->GetIntField(jplayer, Common::Android::GetPlayerInputVibrationStrengthField())); | ||
514 | |||
515 | players[i].body_color_left = static_cast<u32>( | ||
516 | env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorLeftField())); | ||
517 | players[i].body_color_right = static_cast<u32>( | ||
518 | env->GetLongField(jplayer, Common::Android::GetPlayerInputBodyColorRightField())); | ||
519 | players[i].button_color_left = static_cast<u32>( | ||
520 | env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorLeftField())); | ||
521 | players[i].button_color_right = static_cast<u32>( | ||
522 | env->GetLongField(jplayer, Common::Android::GetPlayerInputButtonColorRightField())); | ||
523 | |||
524 | auto profileName = static_cast<jstring>( | ||
525 | env->GetObjectField(jplayer, Common::Android::GetPlayerInputProfileNameField())); | ||
526 | players[i].profile_name = Common::Android::GetJString(env, profileName); | ||
527 | |||
528 | players[i].use_system_vibrator = | ||
529 | env->GetBooleanField(jplayer, Common::Android::GetPlayerInputUseSystemVibratorField()); | ||
530 | } | ||
531 | } | ||
532 | |||
533 | void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveControlPlayerValues(JNIEnv* env, jobject obj) { | ||
534 | Settings::values.players.SetGlobal(false); | ||
535 | |||
536 | // Clear all controls from the config in case the user reverted back to globals | ||
537 | per_game_config->ClearControlPlayerValues(); | ||
538 | for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) { | ||
539 | per_game_config->SaveAndroidControlPlayerValues(index); | ||
540 | } | ||
541 | } | ||
542 | |||
428 | } // extern "C" | 543 | } // extern "C" |
diff --git a/src/android/app/src/main/jni/native_input.cpp b/src/android/app/src/main/jni/native_input.cpp new file mode 100755 index 000000000..ddf2f297b --- /dev/null +++ b/src/android/app/src/main/jni/native_input.cpp | |||
@@ -0,0 +1,631 @@ | |||
1 | // SPDX-FileCopyrightText: 2024 yuzu Emulator Project | ||
2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
3 | |||
4 | #include <common/fs/fs.h> | ||
5 | #include <common/fs/path_util.h> | ||
6 | #include <common/settings.h> | ||
7 | #include <hid_core/hid_types.h> | ||
8 | #include <jni.h> | ||
9 | |||
10 | #include "android_config.h" | ||
11 | #include "common/android/android_common.h" | ||
12 | #include "common/android/id_cache.h" | ||
13 | #include "hid_core/frontend/emulated_controller.h" | ||
14 | #include "hid_core/hid_core.h" | ||
15 | #include "input_common/drivers/android.h" | ||
16 | #include "input_common/drivers/touch_screen.h" | ||
17 | #include "input_common/drivers/virtual_amiibo.h" | ||
18 | #include "input_common/drivers/virtual_gamepad.h" | ||
19 | #include "native.h" | ||
20 | |||
21 | std::unordered_map<std::string, std::unique_ptr<AndroidConfig>> map_profiles; | ||
22 | |||
23 | bool IsHandheldOnly() { | ||
24 | const auto npad_style_set = | ||
25 | EmulationSession::GetInstance().System().HIDCore().GetSupportedStyleTag(); | ||
26 | |||
27 | if (npad_style_set.fullkey == 1) { | ||
28 | return false; | ||
29 | } | ||
30 | |||
31 | if (npad_style_set.handheld == 0) { | ||
32 | return false; | ||
33 | } | ||
34 | |||
35 | return !Settings::IsDockedMode(); | ||
36 | } | ||
37 | |||
38 | std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { | ||
39 | return filename.replace_extension(); | ||
40 | } | ||
41 | |||
42 | bool IsProfileNameValid(std::string_view profile_name) { | ||
43 | return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos; | ||
44 | } | ||
45 | |||
46 | bool ProfileExistsInFilesystem(std::string_view profile_name) { | ||
47 | return Common::FS::Exists(Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input" / | ||
48 | fmt::format("{}.ini", profile_name)); | ||
49 | } | ||
50 | |||
51 | bool ProfileExistsInMap(const std::string& profile_name) { | ||
52 | return map_profiles.find(profile_name) != map_profiles.end(); | ||
53 | } | ||
54 | |||
55 | bool SaveProfile(const std::string& profile_name, std::size_t player_index) { | ||
56 | if (!ProfileExistsInMap(profile_name)) { | ||
57 | return false; | ||
58 | } | ||
59 | |||
60 | Settings::values.players.GetValue()[player_index].profile_name = profile_name; | ||
61 | map_profiles[profile_name]->SaveAndroidControlPlayerValues(player_index); | ||
62 | return true; | ||
63 | } | ||
64 | |||
65 | bool LoadProfile(std::string& profile_name, std::size_t player_index) { | ||
66 | if (!ProfileExistsInMap(profile_name)) { | ||
67 | return false; | ||
68 | } | ||
69 | |||
70 | if (!ProfileExistsInFilesystem(profile_name)) { | ||
71 | map_profiles.erase(profile_name); | ||
72 | return false; | ||
73 | } | ||
74 | |||
75 | LOG_INFO(Config, "Loading input profile `{}`", profile_name); | ||
76 | |||
77 | Settings::values.players.GetValue()[player_index].profile_name = profile_name; | ||
78 | map_profiles[profile_name]->ReadAndroidControlPlayerValues(player_index); | ||
79 | return true; | ||
80 | } | ||
81 | |||
82 | void ApplyControllerConfig(size_t player_index, | ||
83 | const std::function<void(Core::HID::EmulatedController*)>& apply) { | ||
84 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
85 | if (player_index == 0) { | ||
86 | auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); | ||
87 | auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
88 | handheld->EnableConfiguration(); | ||
89 | player_one->EnableConfiguration(); | ||
90 | apply(handheld); | ||
91 | apply(player_one); | ||
92 | handheld->DisableConfiguration(); | ||
93 | player_one->DisableConfiguration(); | ||
94 | handheld->SaveCurrentConfig(); | ||
95 | player_one->SaveCurrentConfig(); | ||
96 | } else { | ||
97 | auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); | ||
98 | controller->EnableConfiguration(); | ||
99 | apply(controller); | ||
100 | controller->DisableConfiguration(); | ||
101 | controller->SaveCurrentConfig(); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | void ConnectController(size_t player_index, bool connected) { | ||
106 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
107 | if (player_index == 0) { | ||
108 | auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); | ||
109 | auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
110 | handheld->EnableConfiguration(); | ||
111 | player_one->EnableConfiguration(); | ||
112 | if (player_one->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { | ||
113 | if (connected) { | ||
114 | handheld->Connect(); | ||
115 | } else { | ||
116 | handheld->Disconnect(); | ||
117 | } | ||
118 | player_one->Disconnect(); | ||
119 | } else { | ||
120 | if (connected) { | ||
121 | player_one->Connect(); | ||
122 | } else { | ||
123 | player_one->Disconnect(); | ||
124 | } | ||
125 | handheld->Disconnect(); | ||
126 | } | ||
127 | handheld->DisableConfiguration(); | ||
128 | player_one->DisableConfiguration(); | ||
129 | handheld->SaveCurrentConfig(); | ||
130 | player_one->SaveCurrentConfig(); | ||
131 | } else { | ||
132 | auto* controller = hid_core.GetEmulatedControllerByIndex(player_index); | ||
133 | controller->EnableConfiguration(); | ||
134 | if (connected) { | ||
135 | controller->Connect(); | ||
136 | } else { | ||
137 | controller->Disconnect(); | ||
138 | } | ||
139 | controller->DisableConfiguration(); | ||
140 | controller->SaveCurrentConfig(); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | extern "C" { | ||
145 | |||
146 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isHandheldOnly(JNIEnv* env, | ||
147 | jobject j_obj) { | ||
148 | return IsHandheldOnly(); | ||
149 | } | ||
150 | |||
151 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadButtonEvent( | ||
152 | JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_button_id, jint j_action) { | ||
153 | EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetButtonState( | ||
154 | Common::Android::GetJString(env, j_guid), j_port, j_button_id, j_action != 0); | ||
155 | } | ||
156 | |||
157 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadAxisEvent( | ||
158 | JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jint j_stick_id, jfloat j_value) { | ||
159 | EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetAxisPosition( | ||
160 | Common::Android::GetJString(env, j_guid), j_port, j_stick_id, j_value); | ||
161 | } | ||
162 | |||
163 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onGamePadMotionEvent( | ||
164 | JNIEnv* env, jobject j_obj, jstring j_guid, jint j_port, jlong j_delta_timestamp, | ||
165 | jfloat j_x_gyro, jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, | ||
166 | jfloat j_z_accel) { | ||
167 | EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->SetMotionState( | ||
168 | Common::Android::GetJString(env, j_guid), j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, | ||
169 | j_z_gyro, j_x_accel, j_y_accel, j_z_accel); | ||
170 | } | ||
171 | |||
172 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onReadNfcTag(JNIEnv* env, jobject j_obj, | ||
173 | jbyteArray j_data) { | ||
174 | jboolean isCopy{false}; | ||
175 | std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)), | ||
176 | static_cast<size_t>(env->GetArrayLength(j_data))); | ||
177 | |||
178 | if (EmulationSession::GetInstance().IsRunning()) { | ||
179 | EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->LoadAmiibo(data); | ||
180 | } | ||
181 | } | ||
182 | |||
183 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onRemoveNfcTag(JNIEnv* env, jobject j_obj) { | ||
184 | if (EmulationSession::GetInstance().IsRunning()) { | ||
185 | EmulationSession::GetInstance().GetInputSubsystem().GetVirtualAmiibo()->CloseAmiibo(); | ||
186 | } | ||
187 | } | ||
188 | |||
189 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchPressed(JNIEnv* env, jobject j_obj, | ||
190 | jint j_id, jfloat j_x_axis, | ||
191 | jfloat j_y_axis) { | ||
192 | if (EmulationSession::GetInstance().IsRunning()) { | ||
193 | EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed( | ||
194 | j_id, j_x_axis, j_y_axis); | ||
195 | } | ||
196 | } | ||
197 | |||
198 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchMoved(JNIEnv* env, jobject j_obj, | ||
199 | jint j_id, jfloat j_x_axis, | ||
200 | jfloat j_y_axis) { | ||
201 | if (EmulationSession::GetInstance().IsRunning()) { | ||
202 | EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved( | ||
203 | j_id, j_x_axis, j_y_axis); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onTouchReleased(JNIEnv* env, jobject j_obj, | ||
208 | jint j_id) { | ||
209 | if (EmulationSession::GetInstance().IsRunning()) { | ||
210 | EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(j_id); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayButtonEventImpl( | ||
215 | JNIEnv* env, jobject j_obj, jint j_port, jint j_button_id, jint j_action) { | ||
216 | if (EmulationSession::GetInstance().IsRunning()) { | ||
217 | EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetButtonState( | ||
218 | j_port, j_button_id, j_action == 1); | ||
219 | } | ||
220 | } | ||
221 | |||
222 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onOverlayJoystickEventImpl( | ||
223 | JNIEnv* env, jobject j_obj, jint j_port, jint j_stick_id, jfloat j_x_axis, jfloat j_y_axis) { | ||
224 | if (EmulationSession::GetInstance().IsRunning()) { | ||
225 | EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetStickPosition( | ||
226 | j_port, j_stick_id, j_x_axis, j_y_axis); | ||
227 | } | ||
228 | } | ||
229 | |||
230 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_onDeviceMotionEvent( | ||
231 | JNIEnv* env, jobject j_obj, jint j_port, jlong j_delta_timestamp, jfloat j_x_gyro, | ||
232 | jfloat j_y_gyro, jfloat j_z_gyro, jfloat j_x_accel, jfloat j_y_accel, jfloat j_z_accel) { | ||
233 | if (EmulationSession::GetInstance().IsRunning()) { | ||
234 | EmulationSession::GetInstance().GetInputSubsystem().GetVirtualGamepad()->SetMotionState( | ||
235 | j_port, j_delta_timestamp, j_x_gyro, j_y_gyro, j_z_gyro, j_x_accel, j_y_accel, | ||
236 | j_z_accel); | ||
237 | } | ||
238 | } | ||
239 | |||
240 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_reloadInputDevices(JNIEnv* env, | ||
241 | jobject j_obj) { | ||
242 | EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices(); | ||
243 | } | ||
244 | |||
245 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_registerController(JNIEnv* env, | ||
246 | jobject j_obj, | ||
247 | jobject j_device) { | ||
248 | EmulationSession::GetInstance().GetInputSubsystem().GetAndroid()->RegisterController(j_device); | ||
249 | } | ||
250 | |||
251 | jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputDevices(JNIEnv* env, | ||
252 | jobject j_obj) { | ||
253 | auto devices = EmulationSession::GetInstance().GetInputSubsystem().GetInputDevices(); | ||
254 | jobjectArray jdevices = env->NewObjectArray(devices.size(), Common::Android::GetStringClass(), | ||
255 | Common::Android::ToJString(env, "")); | ||
256 | for (size_t i = 0; i < devices.size(); ++i) { | ||
257 | env->SetObjectArrayElement(jdevices, i, | ||
258 | Common::Android::ToJString(env, devices[i].Serialize())); | ||
259 | } | ||
260 | return jdevices; | ||
261 | } | ||
262 | |||
263 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadInputProfiles(JNIEnv* env, | ||
264 | jobject j_obj) { | ||
265 | map_profiles.clear(); | ||
266 | const auto input_profile_loc = | ||
267 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "input"; | ||
268 | |||
269 | if (Common::FS::IsDir(input_profile_loc)) { | ||
270 | Common::FS::IterateDirEntries( | ||
271 | input_profile_loc, | ||
272 | [&](const std::filesystem::path& full_path) { | ||
273 | const auto filename = full_path.filename(); | ||
274 | const auto name_without_ext = | ||
275 | Common::FS::PathToUTF8String(GetNameWithoutExtension(filename)); | ||
276 | |||
277 | if (filename.extension() == ".ini" && IsProfileNameValid(name_without_ext)) { | ||
278 | map_profiles.insert_or_assign( | ||
279 | name_without_ext, std::make_unique<AndroidConfig>( | ||
280 | name_without_ext, Config::ConfigType::InputProfile)); | ||
281 | } | ||
282 | |||
283 | return true; | ||
284 | }, | ||
285 | Common::FS::DirEntryFilter::File); | ||
286 | } | ||
287 | } | ||
288 | |||
289 | jobjectArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getInputProfileNames( | ||
290 | JNIEnv* env, jobject j_obj) { | ||
291 | std::vector<std::string> profile_names; | ||
292 | profile_names.reserve(map_profiles.size()); | ||
293 | |||
294 | auto it = map_profiles.cbegin(); | ||
295 | while (it != map_profiles.cend()) { | ||
296 | const auto& [profile_name, config] = *it; | ||
297 | if (!ProfileExistsInFilesystem(profile_name)) { | ||
298 | it = map_profiles.erase(it); | ||
299 | continue; | ||
300 | } | ||
301 | |||
302 | profile_names.push_back(profile_name); | ||
303 | ++it; | ||
304 | } | ||
305 | |||
306 | std::stable_sort(profile_names.begin(), profile_names.end()); | ||
307 | |||
308 | jobjectArray j_profile_names = | ||
309 | env->NewObjectArray(profile_names.size(), Common::Android::GetStringClass(), | ||
310 | Common::Android::ToJString(env, "")); | ||
311 | for (size_t i = 0; i < profile_names.size(); ++i) { | ||
312 | env->SetObjectArrayElement(j_profile_names, i, | ||
313 | Common::Android::ToJString(env, profile_names[i])); | ||
314 | } | ||
315 | |||
316 | return j_profile_names; | ||
317 | } | ||
318 | |||
319 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isProfileNameValid(JNIEnv* env, | ||
320 | jobject j_obj, | ||
321 | jstring j_name) { | ||
322 | return Common::Android::GetJString(env, j_name).find_first_of("<>:;\"/\\|,.!?*") == | ||
323 | std::string::npos; | ||
324 | } | ||
325 | |||
326 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_createProfile(JNIEnv* env, | ||
327 | jobject j_obj, | ||
328 | jstring j_name, | ||
329 | jint j_player_index) { | ||
330 | auto profile_name = Common::Android::GetJString(env, j_name); | ||
331 | if (ProfileExistsInMap(profile_name)) { | ||
332 | return false; | ||
333 | } | ||
334 | |||
335 | map_profiles.insert_or_assign( | ||
336 | profile_name, | ||
337 | std::make_unique<AndroidConfig>(profile_name, Config::ConfigType::InputProfile)); | ||
338 | |||
339 | return SaveProfile(profile_name, j_player_index); | ||
340 | } | ||
341 | |||
342 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_deleteProfile(JNIEnv* env, | ||
343 | jobject j_obj, | ||
344 | jstring j_name, | ||
345 | jint j_player_index) { | ||
346 | auto profile_name = Common::Android::GetJString(env, j_name); | ||
347 | if (!ProfileExistsInMap(profile_name)) { | ||
348 | return false; | ||
349 | } | ||
350 | |||
351 | if (!ProfileExistsInFilesystem(profile_name) || | ||
352 | Common::FS::RemoveFile(map_profiles[profile_name]->GetConfigFilePath())) { | ||
353 | map_profiles.erase(profile_name); | ||
354 | } | ||
355 | |||
356 | Settings::values.players.GetValue()[j_player_index].profile_name = ""; | ||
357 | return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name); | ||
358 | } | ||
359 | |||
360 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadProfile(JNIEnv* env, jobject j_obj, | ||
361 | jstring j_name, | ||
362 | jint j_player_index) { | ||
363 | auto profile_name = Common::Android::GetJString(env, j_name); | ||
364 | return LoadProfile(profile_name, j_player_index); | ||
365 | } | ||
366 | |||
367 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_saveProfile(JNIEnv* env, jobject j_obj, | ||
368 | jstring j_name, | ||
369 | jint j_player_index) { | ||
370 | auto profile_name = Common::Android::GetJString(env, j_name); | ||
371 | return SaveProfile(profile_name, j_player_index); | ||
372 | } | ||
373 | |||
374 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_loadPerGameConfiguration( | ||
375 | JNIEnv* env, jobject j_obj, jint j_player_index, jint j_selected_index, | ||
376 | jstring j_selected_profile_name) { | ||
377 | static constexpr size_t HANDHELD_INDEX = 8; | ||
378 | |||
379 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
380 | Settings::values.players.SetGlobal(false); | ||
381 | |||
382 | auto profile_name = Common::Android::GetJString(env, j_selected_profile_name); | ||
383 | auto* emulated_controller = hid_core.GetEmulatedControllerByIndex(j_player_index); | ||
384 | |||
385 | if (j_selected_index == 0) { | ||
386 | Settings::values.players.GetValue()[j_player_index].profile_name = ""; | ||
387 | if (j_player_index == 0) { | ||
388 | Settings::values.players.GetValue()[HANDHELD_INDEX] = {}; | ||
389 | } | ||
390 | Settings::values.players.SetGlobal(true); | ||
391 | emulated_controller->ReloadFromSettings(); | ||
392 | return; | ||
393 | } | ||
394 | if (profile_name.empty()) { | ||
395 | return; | ||
396 | } | ||
397 | auto& player = Settings::values.players.GetValue()[j_player_index]; | ||
398 | auto& global_player = Settings::values.players.GetValue(true)[j_player_index]; | ||
399 | player.profile_name = profile_name; | ||
400 | global_player.profile_name = profile_name; | ||
401 | // Read from the profile into the custom player settings | ||
402 | LoadProfile(profile_name, j_player_index); | ||
403 | // Make sure the controller is connected | ||
404 | player.connected = true; | ||
405 | |||
406 | emulated_controller->ReloadFromSettings(); | ||
407 | |||
408 | if (j_player_index > 0) { | ||
409 | return; | ||
410 | } | ||
411 | // Handle Handheld cases | ||
412 | auto& handheld_player = Settings::values.players.GetValue()[HANDHELD_INDEX]; | ||
413 | auto* handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); | ||
414 | if (player.controller_type == Settings::ControllerType::Handheld) { | ||
415 | handheld_player = player; | ||
416 | } else { | ||
417 | handheld_player = {}; | ||
418 | } | ||
419 | handheld_controller->ReloadFromSettings(); | ||
420 | } | ||
421 | |||
422 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_beginMapping(JNIEnv* env, jobject j_obj, | ||
423 | jint jtype) { | ||
424 | EmulationSession::GetInstance().GetInputSubsystem().BeginMapping( | ||
425 | static_cast<InputCommon::Polling::InputType>(jtype)); | ||
426 | } | ||
427 | |||
428 | jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getNextInput(JNIEnv* env, | ||
429 | jobject j_obj) { | ||
430 | return Common::Android::ToJString( | ||
431 | env, EmulationSession::GetInstance().GetInputSubsystem().GetNextInput().Serialize()); | ||
432 | } | ||
433 | |||
434 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_stopMapping(JNIEnv* env, jobject j_obj) { | ||
435 | EmulationSession::GetInstance().GetInputSubsystem().StopMapping(); | ||
436 | } | ||
437 | |||
438 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_updateMappingsWithDefaultImpl( | ||
439 | JNIEnv* env, jobject j_obj, jint j_player_index, jstring j_device_params, | ||
440 | jstring j_display_name) { | ||
441 | auto& input_subsystem = EmulationSession::GetInstance().GetInputSubsystem(); | ||
442 | |||
443 | // Clear all previous mappings | ||
444 | for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { | ||
445 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
446 | controller->SetButtonParam(button_id, {}); | ||
447 | }); | ||
448 | } | ||
449 | for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { | ||
450 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
451 | controller->SetStickParam(analog_id, {}); | ||
452 | }); | ||
453 | } | ||
454 | |||
455 | // Apply new mappings | ||
456 | auto device = Common::ParamPackage(Common::Android::GetJString(env, j_device_params)); | ||
457 | auto button_mappings = input_subsystem.GetButtonMappingForDevice(device); | ||
458 | auto analog_mappings = input_subsystem.GetAnalogMappingForDevice(device); | ||
459 | auto display_name = Common::Android::GetJString(env, j_display_name); | ||
460 | for (const auto& button_mapping : button_mappings) { | ||
461 | const std::size_t index = button_mapping.first; | ||
462 | auto named_mapping = button_mapping.second; | ||
463 | named_mapping.Set("display", display_name); | ||
464 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
465 | controller->SetButtonParam(index, named_mapping); | ||
466 | }); | ||
467 | } | ||
468 | for (const auto& analog_mapping : analog_mappings) { | ||
469 | const std::size_t index = analog_mapping.first; | ||
470 | auto named_mapping = analog_mapping.second; | ||
471 | named_mapping.Set("display", display_name); | ||
472 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
473 | controller->SetStickParam(index, named_mapping); | ||
474 | }); | ||
475 | } | ||
476 | } | ||
477 | |||
478 | jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonParamImpl(JNIEnv* env, | ||
479 | jobject j_obj, | ||
480 | jint j_player_index, | ||
481 | jint j_button) { | ||
482 | return Common::Android::ToJString(env, EmulationSession::GetInstance() | ||
483 | .System() | ||
484 | .HIDCore() | ||
485 | .GetEmulatedControllerByIndex(j_player_index) | ||
486 | ->GetButtonParam(j_button) | ||
487 | .Serialize()); | ||
488 | } | ||
489 | |||
490 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setButtonParamImpl( | ||
491 | JNIEnv* env, jobject j_obj, jint j_player_index, jint j_button_id, jstring j_param) { | ||
492 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
493 | controller->SetButtonParam(j_button_id, | ||
494 | Common::ParamPackage(Common::Android::GetJString(env, j_param))); | ||
495 | }); | ||
496 | } | ||
497 | |||
498 | jstring Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStickParamImpl(JNIEnv* env, | ||
499 | jobject j_obj, | ||
500 | jint j_player_index, | ||
501 | jint j_stick) { | ||
502 | return Common::Android::ToJString(env, EmulationSession::GetInstance() | ||
503 | .System() | ||
504 | .HIDCore() | ||
505 | .GetEmulatedControllerByIndex(j_player_index) | ||
506 | ->GetStickParam(j_stick) | ||
507 | .Serialize()); | ||
508 | } | ||
509 | |||
510 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStickParamImpl( | ||
511 | JNIEnv* env, jobject j_obj, jint j_player_index, jint j_stick_id, jstring j_param) { | ||
512 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
513 | controller->SetStickParam(j_stick_id, | ||
514 | Common::ParamPackage(Common::Android::GetJString(env, j_param))); | ||
515 | }); | ||
516 | } | ||
517 | |||
518 | jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getButtonNameImpl(JNIEnv* env, | ||
519 | jobject j_obj, | ||
520 | jstring j_param) { | ||
521 | return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().GetButtonName( | ||
522 | Common::ParamPackage(Common::Android::GetJString(env, j_param)))); | ||
523 | } | ||
524 | |||
525 | jintArray Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getSupportedStyleTagsImpl( | ||
526 | JNIEnv* env, jobject j_obj, jint j_player_index) { | ||
527 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
528 | const auto npad_style_set = hid_core.GetSupportedStyleTag(); | ||
529 | std::vector<s32> supported_indexes; | ||
530 | if (npad_style_set.fullkey == 1) { | ||
531 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Fullkey)); | ||
532 | } | ||
533 | |||
534 | if (npad_style_set.joycon_dual == 1) { | ||
535 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconDual)); | ||
536 | } | ||
537 | |||
538 | if (npad_style_set.joycon_left == 1) { | ||
539 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconLeft)); | ||
540 | } | ||
541 | |||
542 | if (npad_style_set.joycon_right == 1) { | ||
543 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::JoyconRight)); | ||
544 | } | ||
545 | |||
546 | if (j_player_index == 0 && npad_style_set.handheld == 1) { | ||
547 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::Handheld)); | ||
548 | } | ||
549 | |||
550 | if (npad_style_set.gamecube == 1) { | ||
551 | supported_indexes.push_back(static_cast<u32>(Core::HID::NpadStyleIndex::GameCube)); | ||
552 | } | ||
553 | |||
554 | jintArray j_supported_indexes = env->NewIntArray(supported_indexes.size()); | ||
555 | env->SetIntArrayRegion(j_supported_indexes, 0, supported_indexes.size(), | ||
556 | supported_indexes.data()); | ||
557 | return j_supported_indexes; | ||
558 | } | ||
559 | |||
560 | jint Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getStyleIndexImpl(JNIEnv* env, | ||
561 | jobject j_obj, | ||
562 | jint j_player_index) { | ||
563 | return static_cast<s32>(EmulationSession::GetInstance() | ||
564 | .System() | ||
565 | .HIDCore() | ||
566 | .GetEmulatedControllerByIndex(j_player_index) | ||
567 | ->GetNpadStyleIndex(true)); | ||
568 | } | ||
569 | |||
570 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_setStyleIndexImpl(JNIEnv* env, | ||
571 | jobject j_obj, | ||
572 | jint j_player_index, | ||
573 | jint j_style_index) { | ||
574 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
575 | auto type = static_cast<Core::HID::NpadStyleIndex>(j_style_index); | ||
576 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
577 | controller->SetNpadStyleIndex(type); | ||
578 | }); | ||
579 | if (j_player_index == 0) { | ||
580 | auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); | ||
581 | auto* player_one = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
582 | ConnectController(j_player_index, | ||
583 | player_one->IsConnected(true) || handheld->IsConnected(true)); | ||
584 | } | ||
585 | } | ||
586 | |||
587 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_isControllerImpl(JNIEnv* env, | ||
588 | jobject j_obj, | ||
589 | jstring jparams) { | ||
590 | return static_cast<jint>(EmulationSession::GetInstance().GetInputSubsystem().IsController( | ||
591 | Common::ParamPackage(Common::Android::GetJString(env, jparams)))); | ||
592 | } | ||
593 | |||
594 | jboolean Java_org_yuzu_yuzu_1emu_features_input_NativeInput_getIsConnected(JNIEnv* env, | ||
595 | jobject j_obj, | ||
596 | jint j_player_index) { | ||
597 | auto& hid_core = EmulationSession::GetInstance().System().HIDCore(); | ||
598 | auto* controller = hid_core.GetEmulatedControllerByIndex(static_cast<size_t>(j_player_index)); | ||
599 | if (j_player_index == 0 && | ||
600 | controller->GetNpadStyleIndex(true) == Core::HID::NpadStyleIndex::Handheld) { | ||
601 | return hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld)->IsConnected(true); | ||
602 | } | ||
603 | return controller->IsConnected(true); | ||
604 | } | ||
605 | |||
606 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_connectControllersImpl( | ||
607 | JNIEnv* env, jobject j_obj, jbooleanArray j_connected) { | ||
608 | jboolean isCopy = false; | ||
609 | auto j_connected_array_size = env->GetArrayLength(j_connected); | ||
610 | jboolean* j_connected_array = env->GetBooleanArrayElements(j_connected, &isCopy); | ||
611 | for (int i = 0; i < j_connected_array_size; ++i) { | ||
612 | ConnectController(i, j_connected_array[i]); | ||
613 | } | ||
614 | } | ||
615 | |||
616 | void Java_org_yuzu_yuzu_1emu_features_input_NativeInput_resetControllerMappings( | ||
617 | JNIEnv* env, jobject j_obj, jint j_player_index) { | ||
618 | // Clear all previous mappings | ||
619 | for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) { | ||
620 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
621 | controller->SetButtonParam(button_id, {}); | ||
622 | }); | ||
623 | } | ||
624 | for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { | ||
625 | ApplyControllerConfig(j_player_index, [&](Core::HID::EmulatedController* controller) { | ||
626 | controller->SetStickParam(analog_id, {}); | ||
627 | }); | ||
628 | } | ||
629 | } | ||
630 | |||
631 | } // extern "C" | ||
diff --git a/src/android/app/src/main/res/drawable/button_anim.xml b/src/android/app/src/main/res/drawable/button_anim.xml new file mode 100755 index 000000000..ccdc5ca6a --- /dev/null +++ b/src/android/app/src/main/res/drawable/button_anim.xml | |||
@@ -0,0 +1,142 @@ | |||
1 | <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | xmlns:aapt="http://schemas.android.com/aapt"> | ||
3 | <aapt:attr name="android:drawable"> | ||
4 | <vector | ||
5 | android:width="1000dp" | ||
6 | android:height="1000dp" | ||
7 | android:viewportWidth="1000" | ||
8 | android:viewportHeight="1000"> | ||
9 | <group android:name="_R_G"> | ||
10 | <group | ||
11 | android:name="_R_G_L_0_G" | ||
12 | android:pivotX="100" | ||
13 | android:pivotY="100" | ||
14 | android:scaleX="4.5" | ||
15 | android:scaleY="4.5" | ||
16 | android:translateX="400" | ||
17 | android:translateY="400"> | ||
18 | <path | ||
19 | android:name="_R_G_L_0_G_D_0_P_0" | ||
20 | android:fillAlpha="1" | ||
21 | android:fillColor="?attr/colorSecondaryContainer" | ||
22 | android:fillType="nonZero" | ||
23 | android:pathData=" M198.56 100 C198.56,154.43 154.43,198.56 100,198.56 C45.57,198.56 1.44,154.43 1.44,100 C1.44,45.57 45.57,1.44 100,1.44 C154.43,1.44 198.56,45.57 198.56,100c " /> | ||
24 | <path | ||
25 | android:name="_R_G_L_0_G_D_2_P_0" | ||
26 | android:fillAlpha="0.8" | ||
27 | android:fillColor="?attr/colorOnSecondaryContainer" | ||
28 | android:fillType="nonZero" | ||
29 | android:pathData=" M50.14 151.21 C50.53,150.18 89.6,49.87 90.1,48.63 C90.1,48.63 90.67,47.2 90.67,47.2 C90.67,47.2 101.67,47.2 101.67,47.2 C101.67,47.2 112.67,47.2 112.67,47.2 C112.67,47.2 133.47,99.12 133.47,99.12 C144.91,127.68 154.32,151.17 154.38,151.33 C154.47,151.56 152.2,151.6 143.14,151.55 C143.14,151.55 131.79,151.48 131.79,151.48 C131.79,151.48 127.22,139.57 127.22,139.57 C127.22,139.57 122.65,127.66 122.65,127.66 C122.65,127.66 101.68,127.73 101.68,127.73 C101.68,127.73 80.71,127.8 80.71,127.8 C80.71,127.8 76.38,139.71 76.38,139.71 C76.38,139.71 72.06,151.62 72.06,151.62 C72.06,151.62 61.02,151.62 61.02,151.62 C50.61,151.62 50,151.55 50.14,151.22 C50.14,151.22 50.14,151.21 50.14,151.21c M115.86 110.06 C115.8,109.91 112.55,101.13 108.62,90.56 C104.7,80 101.42,71.43 101.34,71.53 C101.22,71.66 92.84,94.61 87.25,110.06 C87.17,110.29 90.13,110.34 101.56,110.34 C113,110.34 115.95,110.28 115.86,110.06c " /> | ||
30 | </group> | ||
31 | </group> | ||
32 | <group android:name="time_group" /> | ||
33 | </vector> | ||
34 | </aapt:attr> | ||
35 | <target android:name="_R_G_L_0_G"> | ||
36 | <aapt:attr name="android:animation"> | ||
37 | <set android:ordering="together"> | ||
38 | <objectAnimator | ||
39 | android:duration="100" | ||
40 | android:propertyName="scaleX" | ||
41 | android:startOffset="0" | ||
42 | android:valueFrom="4.5" | ||
43 | android:valueTo="3.75" | ||
44 | android:valueType="floatType"> | ||
45 | <aapt:attr name="android:interpolator"> | ||
46 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
47 | </aapt:attr> | ||
48 | </objectAnimator> | ||
49 | <objectAnimator | ||
50 | android:duration="100" | ||
51 | android:propertyName="scaleY" | ||
52 | android:startOffset="0" | ||
53 | android:valueFrom="4.5" | ||
54 | android:valueTo="3.75" | ||
55 | android:valueType="floatType"> | ||
56 | <aapt:attr name="android:interpolator"> | ||
57 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
58 | </aapt:attr> | ||
59 | </objectAnimator> | ||
60 | <objectAnimator | ||
61 | android:duration="234" | ||
62 | android:propertyName="scaleX" | ||
63 | android:startOffset="100" | ||
64 | android:valueFrom="3.75" | ||
65 | android:valueTo="3.75" | ||
66 | android:valueType="floatType"> | ||
67 | <aapt:attr name="android:interpolator"> | ||
68 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
69 | </aapt:attr> | ||
70 | </objectAnimator> | ||
71 | <objectAnimator | ||
72 | android:duration="234" | ||
73 | android:propertyName="scaleY" | ||
74 | android:startOffset="100" | ||
75 | android:valueFrom="3.75" | ||
76 | android:valueTo="3.75" | ||
77 | android:valueType="floatType"> | ||
78 | <aapt:attr name="android:interpolator"> | ||
79 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
80 | </aapt:attr> | ||
81 | </objectAnimator> | ||
82 | <objectAnimator | ||
83 | android:duration="167" | ||
84 | android:propertyName="scaleX" | ||
85 | android:startOffset="334" | ||
86 | android:valueFrom="3.75" | ||
87 | android:valueTo="4.75" | ||
88 | android:valueType="floatType"> | ||
89 | <aapt:attr name="android:interpolator"> | ||
90 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
91 | </aapt:attr> | ||
92 | </objectAnimator> | ||
93 | <objectAnimator | ||
94 | android:duration="167" | ||
95 | android:propertyName="scaleY" | ||
96 | android:startOffset="334" | ||
97 | android:valueFrom="3.75" | ||
98 | android:valueTo="4.75" | ||
99 | android:valueType="floatType"> | ||
100 | <aapt:attr name="android:interpolator"> | ||
101 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
102 | </aapt:attr> | ||
103 | </objectAnimator> | ||
104 | <objectAnimator | ||
105 | android:duration="67" | ||
106 | android:propertyName="scaleX" | ||
107 | android:startOffset="501" | ||
108 | android:valueFrom="4.75" | ||
109 | android:valueTo="4.5" | ||
110 | android:valueType="floatType"> | ||
111 | <aapt:attr name="android:interpolator"> | ||
112 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
113 | </aapt:attr> | ||
114 | </objectAnimator> | ||
115 | <objectAnimator | ||
116 | android:duration="67" | ||
117 | android:propertyName="scaleY" | ||
118 | android:startOffset="501" | ||
119 | android:valueFrom="4.75" | ||
120 | android:valueTo="4.5" | ||
121 | android:valueType="floatType"> | ||
122 | <aapt:attr name="android:interpolator"> | ||
123 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
124 | </aapt:attr> | ||
125 | </objectAnimator> | ||
126 | </set> | ||
127 | </aapt:attr> | ||
128 | </target> | ||
129 | <target android:name="time_group"> | ||
130 | <aapt:attr name="android:animation"> | ||
131 | <set android:ordering="together"> | ||
132 | <objectAnimator | ||
133 | android:duration="1034" | ||
134 | android:propertyName="translateX" | ||
135 | android:startOffset="0" | ||
136 | android:valueFrom="0" | ||
137 | android:valueTo="1" | ||
138 | android:valueType="floatType" /> | ||
139 | </set> | ||
140 | </aapt:attr> | ||
141 | </target> | ||
142 | </animated-vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml b/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml new file mode 100755 index 000000000..8e3c66f74 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_controller_disconnected.xml | |||
@@ -0,0 +1,9 @@ | |||
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | android:width="24dp" | ||
3 | android:height="24dp" | ||
4 | android:viewportWidth="960" | ||
5 | android:viewportHeight="960"> | ||
6 | <path | ||
7 | android:fillColor="?attr/colorControlNormal" | ||
8 | android:pathData="M700,480q-25,0 -42.5,-17.5T640,420q0,-25 17.5,-42.5T700,360q25,0 42.5,17.5T760,420q0,25 -17.5,42.5T700,480ZM366,480ZM280,600v-80h-80v-80h80v-80h80v80h80v80h-80v80h-80ZM160,720q-33,0 -56.5,-23.5T80,640v-320q0,-34 24,-57.5t58,-23.5h77l81,81L160,320v320h366L55,169l57,-57 736,736 -57,57 -185,-185L160,720ZM880,640q0,26 -14,46t-37,29l-29,-29v-366L434,320l-80,-80h446q33,0 56.5,23.5T880,320v320ZM617,503Z" /> | ||
9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_more_vert.xml b/src/android/app/src/main/res/drawable/ic_more_vert.xml new file mode 100755 index 000000000..9f62ac595 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_more_vert.xml | |||
@@ -0,0 +1,9 @@ | |||
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | android:height="24dp" | ||
3 | android:viewportHeight="24" | ||
4 | android:viewportWidth="24" | ||
5 | android:width="24dp"> | ||
6 | <path | ||
7 | android:fillColor="?attr/colorControlNormal" | ||
8 | android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" /> | ||
9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_new_label.xml b/src/android/app/src/main/res/drawable/ic_new_label.xml new file mode 100755 index 000000000..fac562c26 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_new_label.xml | |||
@@ -0,0 +1,9 @@ | |||
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | android:width="24dp" | ||
3 | android:height="24dp" | ||
4 | android:viewportWidth="24" | ||
5 | android:viewportHeight="24"> | ||
6 | <path | ||
7 | android:fillColor="?attr/colorControlNormal" | ||
8 | android:pathData="M21,12l-4.37,6.16C16.26,18.68 15.65,19 15,19h-3l0,-6H9v-3H3V7c0,-1.1 0.9,-2 2,-2h10c0.65,0 1.26,0.31 1.63,0.84L21,12zM10,15H7v-3H5v3H2v2h3v3h2v-3h3V15z" /> | ||
9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_overlay.xml b/src/android/app/src/main/res/drawable/ic_overlay.xml new file mode 100755 index 000000000..c7986c5a2 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_overlay.xml | |||
@@ -0,0 +1,21 @@ | |||
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | android:width="24dp" | ||
3 | android:height="24dp" | ||
4 | android:viewportWidth="24" | ||
5 | android:viewportHeight="24"> | ||
6 | <path | ||
7 | android:fillColor="?attr/colorControlNormal" | ||
8 | android:pathData="M21,5H3C1.9,5 1,5.9 1,7v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7C23,5.9 22.1,5 21,5zM18,17H6V7h12V17z" /> | ||
9 | <path | ||
10 | android:fillColor="?attr/colorControlNormal" | ||
11 | android:pathData="M15,11.25h1.5v1.5h-1.5z" /> | ||
12 | <path | ||
13 | android:fillColor="?attr/colorControlNormal" | ||
14 | android:pathData="M12.5,11.25h1.5v1.5h-1.5z" /> | ||
15 | <path | ||
16 | android:fillColor="?attr/colorControlNormal" | ||
17 | android:pathData="M10,11.25h1.5v1.5h-1.5z" /> | ||
18 | <path | ||
19 | android:fillColor="?attr/colorControlNormal" | ||
20 | android:pathData="M7.5,11.25h1.5v1.5h-1.5z" /> | ||
21 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/ic_share.xml b/src/android/app/src/main/res/drawable/ic_share.xml new file mode 100755 index 000000000..3fc2f3c99 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_share.xml | |||
@@ -0,0 +1,9 @@ | |||
1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | android:width="24dp" | ||
3 | android:height="24dp" | ||
4 | android:viewportWidth="24" | ||
5 | android:viewportHeight="24"> | ||
6 | <path | ||
7 | android:fillColor="?attr/colorControlNormal" | ||
8 | android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" /> | ||
9 | </vector> | ||
diff --git a/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml b/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml new file mode 100755 index 000000000..a1da1316f --- /dev/null +++ b/src/android/app/src/main/res/drawable/stick_one_direction_anim.xml | |||
@@ -0,0 +1,118 @@ | |||
1 | <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | xmlns:aapt="http://schemas.android.com/aapt"> | ||
3 | <aapt:attr name="android:drawable"> | ||
4 | <vector | ||
5 | android:width="1000dp" | ||
6 | android:height="1000dp" | ||
7 | android:viewportWidth="1000" | ||
8 | android:viewportHeight="1000"> | ||
9 | <group android:name="_R_G"> | ||
10 | <group | ||
11 | android:name="_R_G_L_1_G" | ||
12 | android:pivotX="100" | ||
13 | android:pivotY="100" | ||
14 | android:scaleX="5" | ||
15 | android:scaleY="5" | ||
16 | android:translateX="400" | ||
17 | android:translateY="400"> | ||
18 | <path | ||
19 | android:name="_R_G_L_1_G_D_0_P_0" | ||
20 | android:pathData=" M100 199.39 C59.8,199.39 23.56,175.17 8.18,138.04 C-7.2,100.9 1.3,58.15 29.73,29.72 C58.15,1.3 100.9,-7.21 138.04,8.18 C175.18,23.56 199.39,59.8 199.39,100 C199.33,154.87 154.87,199.33 100,199.39c " | ||
21 | android:strokeWidth="1" | ||
22 | android:strokeAlpha="0.6" | ||
23 | android:strokeColor="?attr/colorOutline" | ||
24 | android:strokeLineCap="round" | ||
25 | android:strokeLineJoin="round" /> | ||
26 | </group> | ||
27 | <group | ||
28 | android:name="_R_G_L_0_G_T_1" | ||
29 | android:scaleX="5" | ||
30 | android:scaleY="5" | ||
31 | android:translateX="500" | ||
32 | android:translateY="500"> | ||
33 | <group | ||
34 | android:name="_R_G_L_0_G" | ||
35 | android:translateX="-100" | ||
36 | android:translateY="-100"> | ||
37 | <path | ||
38 | android:name="_R_G_L_0_G_D_0_P_0" | ||
39 | android:fillAlpha="1" | ||
40 | android:fillColor="?attr/colorSecondaryContainer" | ||
41 | android:fillType="nonZero" | ||
42 | android:pathData=" M100.45 28.02 C140.63,28.02 173.2,60.59 173.2,100.77 C173.2,140.95 140.63,173.52 100.45,173.52 C60.27,173.52 27.7,140.95 27.7,100.77 C27.7,60.59 60.27,28.02 100.45,28.02c " /> | ||
43 | <path | ||
44 | android:name="_R_G_L_0_G_D_2_P_0" | ||
45 | android:fillAlpha="0.8" | ||
46 | android:fillColor="?attr/colorOnSecondaryContainer" | ||
47 | android:fillType="nonZero" | ||
48 | android:pathData=" M100.45 50.26 C128.62,50.26 151.46,73.1 151.46,101.28 C151.46,129.45 128.62,152.29 100.45,152.29 C72.27,152.29 49.43,129.45 49.43,101.28 C49.43,73.1 72.27,50.26 100.45,50.26c " /> | ||
49 | </group> | ||
50 | </group> | ||
51 | </group> | ||
52 | <group android:name="time_group" /> | ||
53 | </vector> | ||
54 | </aapt:attr> | ||
55 | <target android:name="_R_G_L_0_G_T_1"> | ||
56 | <aapt:attr name="android:animation"> | ||
57 | <set android:ordering="together"> | ||
58 | <objectAnimator | ||
59 | android:duration="267" | ||
60 | android:pathData="M 500,500C 500,500 364,500 364,500" | ||
61 | android:propertyName="translateXY" | ||
62 | android:propertyXName="translateX" | ||
63 | android:propertyYName="translateY" | ||
64 | android:startOffset="0"> | ||
65 | <aapt:attr name="android:interpolator"> | ||
66 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
67 | </aapt:attr> | ||
68 | </objectAnimator> | ||
69 | <objectAnimator | ||
70 | android:duration="234" | ||
71 | android:pathData="M 364,500C 364,500 364,500 364,500" | ||
72 | android:propertyName="translateXY" | ||
73 | android:propertyXName="translateX" | ||
74 | android:propertyYName="translateY" | ||
75 | android:startOffset="267"> | ||
76 | <aapt:attr name="android:interpolator"> | ||
77 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" /> | ||
78 | </aapt:attr> | ||
79 | </objectAnimator> | ||
80 | <objectAnimator | ||
81 | android:duration="133" | ||
82 | android:pathData="M 364,500C 364,500 525,500 525,500" | ||
83 | android:propertyName="translateXY" | ||
84 | android:propertyXName="translateX" | ||
85 | android:propertyYName="translateY" | ||
86 | android:startOffset="501"> | ||
87 | <aapt:attr name="android:interpolator"> | ||
88 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
89 | </aapt:attr> | ||
90 | </objectAnimator> | ||
91 | <objectAnimator | ||
92 | android:duration="100" | ||
93 | android:pathData="M 525,500C 525,500 500,500 500,500" | ||
94 | android:propertyName="translateXY" | ||
95 | android:propertyXName="translateX" | ||
96 | android:propertyYName="translateY" | ||
97 | android:startOffset="634"> | ||
98 | <aapt:attr name="android:interpolator"> | ||
99 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
100 | </aapt:attr> | ||
101 | </objectAnimator> | ||
102 | </set> | ||
103 | </aapt:attr> | ||
104 | </target> | ||
105 | <target android:name="time_group"> | ||
106 | <aapt:attr name="android:animation"> | ||
107 | <set android:ordering="together"> | ||
108 | <objectAnimator | ||
109 | android:duration="968" | ||
110 | android:propertyName="translateX" | ||
111 | android:startOffset="0" | ||
112 | android:valueFrom="0" | ||
113 | android:valueTo="1" | ||
114 | android:valueType="floatType" /> | ||
115 | </set> | ||
116 | </aapt:attr> | ||
117 | </target> | ||
118 | </animated-vector> | ||
diff --git a/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml b/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml new file mode 100755 index 000000000..bc71adcbd --- /dev/null +++ b/src/android/app/src/main/res/drawable/stick_two_direction_anim.xml | |||
@@ -0,0 +1,173 @@ | |||
1 | <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
2 | xmlns:aapt="http://schemas.android.com/aapt"> | ||
3 | <aapt:attr name="android:drawable"> | ||
4 | <vector | ||
5 | android:width="1000dp" | ||
6 | android:height="1000dp" | ||
7 | android:viewportWidth="1000" | ||
8 | android:viewportHeight="1000"> | ||
9 | <group android:name="_R_G"> | ||
10 | <group | ||
11 | android:name="_R_G_L_1_G" | ||
12 | android:pivotX="100" | ||
13 | android:pivotY="100" | ||
14 | android:scaleX="5" | ||
15 | android:scaleY="5" | ||
16 | android:translateX="400" | ||
17 | android:translateY="400"> | ||
18 | <path | ||
19 | android:name="_R_G_L_1_G_D_0_P_0" | ||
20 | android:pathData=" M100 199.39 C59.8,199.39 23.56,175.17 8.18,138.04 C-7.2,100.9 1.3,58.15 29.73,29.72 C58.15,1.3 100.9,-7.21 138.04,8.18 C175.18,23.56 199.39,59.8 199.39,100 C199.33,154.87 154.87,199.33 100,199.39c " | ||
21 | android:strokeWidth="1" | ||
22 | android:strokeAlpha="0.6" | ||
23 | android:strokeColor="?attr/colorOutline" | ||
24 | android:strokeLineCap="round" | ||
25 | android:strokeLineJoin="round" /> | ||
26 | </group> | ||
27 | <group | ||
28 | android:name="_R_G_L_0_G_T_1" | ||
29 | android:scaleX="5" | ||
30 | android:scaleY="5" | ||
31 | android:translateX="500" | ||
32 | android:translateY="500"> | ||
33 | <group | ||
34 | android:name="_R_G_L_0_G" | ||
35 | android:translateX="-100" | ||
36 | android:translateY="-100"> | ||
37 | <path | ||
38 | android:name="_R_G_L_0_G_D_0_P_0" | ||
39 | android:fillAlpha="1" | ||
40 | android:fillColor="?attr/colorSecondaryContainer" | ||
41 | android:fillType="nonZero" | ||
42 | android:pathData=" M100.45 28.02 C140.63,28.02 173.2,60.59 173.2,100.77 C173.2,140.95 140.63,173.52 100.45,173.52 C60.27,173.52 27.7,140.95 27.7,100.77 C27.7,60.59 60.27,28.02 100.45,28.02c " /> | ||
43 | <path | ||
44 | android:name="_R_G_L_0_G_D_2_P_0" | ||
45 | android:fillAlpha="0.8" | ||
46 | android:fillColor="?attr/colorOnSecondaryContainer" | ||
47 | android:fillType="nonZero" | ||
48 | android:pathData=" M100.45 50.26 C128.62,50.26 151.46,73.1 151.46,101.28 C151.46,129.45 128.62,152.29 100.45,152.29 C72.27,152.29 49.43,129.45 49.43,101.28 C49.43,73.1 72.27,50.26 100.45,50.26c " /> | ||
49 | </group> | ||
50 | </group> | ||
51 | </group> | ||
52 | <group android:name="time_group" /> | ||
53 | </vector> | ||
54 | </aapt:attr> | ||
55 | <target android:name="_R_G_L_0_G_T_1"> | ||
56 | <aapt:attr name="android:animation"> | ||
57 | <set android:ordering="together"> | ||
58 | <objectAnimator | ||
59 | android:duration="267" | ||
60 | android:pathData="M 500,500C 500,500 364,500 364,500" | ||
61 | android:propertyName="translateXY" | ||
62 | android:propertyXName="translateX" | ||
63 | android:propertyYName="translateY" | ||
64 | android:startOffset="0"> | ||
65 | <aapt:attr name="android:interpolator"> | ||
66 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
67 | </aapt:attr> | ||
68 | </objectAnimator> | ||
69 | <objectAnimator | ||
70 | android:duration="234" | ||
71 | android:pathData="M 364,500C 364,500 364,500 364,500" | ||
72 | android:propertyName="translateXY" | ||
73 | android:propertyXName="translateX" | ||
74 | android:propertyYName="translateY" | ||
75 | android:startOffset="267"> | ||
76 | <aapt:attr name="android:interpolator"> | ||
77 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" /> | ||
78 | </aapt:attr> | ||
79 | </objectAnimator> | ||
80 | <objectAnimator | ||
81 | android:duration="133" | ||
82 | android:pathData="M 364,500C 364,500 525,500 525,500" | ||
83 | android:propertyName="translateXY" | ||
84 | android:propertyXName="translateX" | ||
85 | android:propertyYName="translateY" | ||
86 | android:startOffset="501"> | ||
87 | <aapt:attr name="android:interpolator"> | ||
88 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
89 | </aapt:attr> | ||
90 | </objectAnimator> | ||
91 | <objectAnimator | ||
92 | android:duration="100" | ||
93 | android:pathData="M 525,500C 525,500 500,500 500,500" | ||
94 | android:propertyName="translateXY" | ||
95 | android:propertyXName="translateX" | ||
96 | android:propertyYName="translateY" | ||
97 | android:startOffset="634"> | ||
98 | <aapt:attr name="android:interpolator"> | ||
99 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
100 | </aapt:attr> | ||
101 | </objectAnimator> | ||
102 | <objectAnimator | ||
103 | android:duration="400" | ||
104 | android:pathData="M 500,500C 500,500 500,500 500,500" | ||
105 | android:propertyName="translateXY" | ||
106 | android:propertyXName="translateX" | ||
107 | android:propertyYName="translateY" | ||
108 | android:startOffset="734"> | ||
109 | <aapt:attr name="android:interpolator"> | ||
110 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" /> | ||
111 | </aapt:attr> | ||
112 | </objectAnimator> | ||
113 | <objectAnimator | ||
114 | android:duration="267" | ||
115 | android:pathData="M 500,500C 500,500 500,364 500,364" | ||
116 | android:propertyName="translateXY" | ||
117 | android:propertyXName="translateX" | ||
118 | android:propertyYName="translateY" | ||
119 | android:startOffset="1134"> | ||
120 | <aapt:attr name="android:interpolator"> | ||
121 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
122 | </aapt:attr> | ||
123 | </objectAnimator> | ||
124 | <objectAnimator | ||
125 | android:duration="234" | ||
126 | android:pathData="M 500,364C 500,364 500,364 500,364" | ||
127 | android:propertyName="translateXY" | ||
128 | android:propertyXName="translateX" | ||
129 | android:propertyYName="translateY" | ||
130 | android:startOffset="1401"> | ||
131 | <aapt:attr name="android:interpolator"> | ||
132 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0.333 0.667,0.667 1.0,1.0" /> | ||
133 | </aapt:attr> | ||
134 | </objectAnimator> | ||
135 | <objectAnimator | ||
136 | android:duration="133" | ||
137 | android:pathData="M 500,364C 500,364 500,535 500,535" | ||
138 | android:propertyName="translateXY" | ||
139 | android:propertyXName="translateX" | ||
140 | android:propertyYName="translateY" | ||
141 | android:startOffset="1635"> | ||
142 | <aapt:attr name="android:interpolator"> | ||
143 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
144 | </aapt:attr> | ||
145 | </objectAnimator> | ||
146 | <objectAnimator | ||
147 | android:duration="100" | ||
148 | android:pathData="M 500,535C 500,535 500,500 500,500" | ||
149 | android:propertyName="translateXY" | ||
150 | android:propertyXName="translateX" | ||
151 | android:propertyYName="translateY" | ||
152 | android:startOffset="1768"> | ||
153 | <aapt:attr name="android:interpolator"> | ||
154 | <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" /> | ||
155 | </aapt:attr> | ||
156 | </objectAnimator> | ||
157 | </set> | ||
158 | </aapt:attr> | ||
159 | </target> | ||
160 | <target android:name="time_group"> | ||
161 | <aapt:attr name="android:animation"> | ||
162 | <set android:ordering="together"> | ||
163 | <objectAnimator | ||
164 | android:duration="2269" | ||
165 | android:propertyName="translateX" | ||
166 | android:startOffset="0" | ||
167 | android:valueFrom="0" | ||
168 | android:valueTo="1" | ||
169 | android:valueType="floatType" /> | ||
170 | </set> | ||
171 | </aapt:attr> | ||
172 | </target> | ||
173 | </animated-vector> | ||
diff --git a/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml b/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml new file mode 100755 index 000000000..583620dc6 --- /dev/null +++ b/src/android/app/src/main/res/layout-ldrtl/list_item_setting_input.xml | |||
@@ -0,0 +1,63 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
4 | xmlns:tools="http://schemas.android.com/tools" | ||
5 | android:id="@+id/setting_body" | ||
6 | android:layout_width="match_parent" | ||
7 | android:layout_height="wrap_content" | ||
8 | android:background="?android:attr/selectableItemBackground" | ||
9 | android:clickable="true" | ||
10 | android:focusable="true" | ||
11 | android:gravity="center_vertical" | ||
12 | android:minHeight="72dp" | ||
13 | android:padding="16dp" | ||
14 | android:nextFocusLeft="@id/button_options"> | ||
15 | |||
16 | <LinearLayout | ||
17 | android:layout_width="match_parent" | ||
18 | android:layout_height="wrap_content" | ||
19 | android:gravity="center_vertical" | ||
20 | android:orientation="horizontal"> | ||
21 | |||
22 | <LinearLayout | ||
23 | android:layout_width="0dp" | ||
24 | android:layout_height="wrap_content" | ||
25 | android:orientation="vertical" | ||
26 | android:layout_weight="1"> | ||
27 | |||
28 | <com.google.android.material.textview.MaterialTextView | ||
29 | android:id="@+id/text_setting_name" | ||
30 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
31 | android:layout_width="match_parent" | ||
32 | android:layout_height="wrap_content" | ||
33 | android:textAlignment="viewStart" | ||
34 | android:textSize="17sp" | ||
35 | app:lineHeight="22dp" | ||
36 | tools:text="Setting Name" /> | ||
37 | |||
38 | <com.google.android.material.textview.MaterialTextView | ||
39 | android:id="@+id/text_setting_value" | ||
40 | style="@style/TextAppearance.Material3.LabelMedium" | ||
41 | android:layout_width="match_parent" | ||
42 | android:layout_height="wrap_content" | ||
43 | android:layout_marginTop="@dimen/spacing_small" | ||
44 | android:textAlignment="viewStart" | ||
45 | android:textStyle="bold" | ||
46 | android:textSize="13sp" | ||
47 | tools:text="1x" /> | ||
48 | |||
49 | </LinearLayout> | ||
50 | |||
51 | <Button | ||
52 | android:id="@+id/button_options" | ||
53 | style="?attr/materialIconButtonStyle" | ||
54 | android:layout_width="wrap_content" | ||
55 | android:layout_height="wrap_content" | ||
56 | android:nextFocusRight="@id/setting_body" | ||
57 | app:icon="@drawable/ic_more_vert" | ||
58 | app:iconSize="24dp" | ||
59 | app:iconTint="?attr/colorOnSurface" /> | ||
60 | |||
61 | </LinearLayout> | ||
62 | |||
63 | </RelativeLayout> | ||
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml index bda524f0f..09e26990b 100755 --- a/src/android/app/src/main/res/layout/card_driver_option.xml +++ b/src/android/app/src/main/res/layout/card_driver_option.xml | |||
@@ -39,10 +39,7 @@ | |||
39 | style="@style/TextAppearance.Material3.TitleMedium" | 39 | style="@style/TextAppearance.Material3.TitleMedium" |
40 | android:layout_width="match_parent" | 40 | android:layout_width="match_parent" |
41 | android:layout_height="wrap_content" | 41 | android:layout_height="wrap_content" |
42 | android:ellipsize="none" | ||
43 | android:marqueeRepeatLimit="marquee_forever" | ||
44 | android:requiresFadingEdge="horizontal" | 42 | android:requiresFadingEdge="horizontal" |
45 | android:singleLine="true" | ||
46 | android:textAlignment="viewStart" | 43 | android:textAlignment="viewStart" |
47 | tools:text="@string/select_gpu_driver_default" /> | 44 | tools:text="@string/select_gpu_driver_default" /> |
48 | 45 | ||
@@ -52,10 +49,7 @@ | |||
52 | android:layout_width="match_parent" | 49 | android:layout_width="match_parent" |
53 | android:layout_height="wrap_content" | 50 | android:layout_height="wrap_content" |
54 | android:layout_marginTop="6dp" | 51 | android:layout_marginTop="6dp" |
55 | android:ellipsize="none" | ||
56 | android:marqueeRepeatLimit="marquee_forever" | ||
57 | android:requiresFadingEdge="horizontal" | 52 | android:requiresFadingEdge="horizontal" |
58 | android:singleLine="true" | ||
59 | android:textAlignment="viewStart" | 53 | android:textAlignment="viewStart" |
60 | tools:text="@string/install_gpu_driver_description" /> | 54 | tools:text="@string/install_gpu_driver_description" /> |
61 | 55 | ||
@@ -65,10 +59,7 @@ | |||
65 | android:layout_width="match_parent" | 59 | android:layout_width="match_parent" |
66 | android:layout_height="wrap_content" | 60 | android:layout_height="wrap_content" |
67 | android:layout_marginTop="6dp" | 61 | android:layout_marginTop="6dp" |
68 | android:ellipsize="none" | ||
69 | android:marqueeRepeatLimit="marquee_forever" | ||
70 | android:requiresFadingEdge="horizontal" | 62 | android:requiresFadingEdge="horizontal" |
71 | android:singleLine="true" | ||
72 | android:textAlignment="viewStart" | 63 | android:textAlignment="viewStart" |
73 | tools:text="@string/install_gpu_driver_description" /> | 64 | tools:text="@string/install_gpu_driver_description" /> |
74 | 65 | ||
diff --git a/src/android/app/src/main/res/layout/card_folder.xml b/src/android/app/src/main/res/layout/card_folder.xml index ed4a7ca8f..e3a5f1a86 100755 --- a/src/android/app/src/main/res/layout/card_folder.xml +++ b/src/android/app/src/main/res/layout/card_folder.xml | |||
@@ -21,10 +21,7 @@ | |||
21 | android:layout_width="0dp" | 21 | android:layout_width="0dp" |
22 | android:layout_height="wrap_content" | 22 | android:layout_height="wrap_content" |
23 | android:layout_gravity="center_vertical|start" | 23 | android:layout_gravity="center_vertical|start" |
24 | android:ellipsize="none" | ||
25 | android:marqueeRepeatLimit="marquee_forever" | ||
26 | android:requiresFadingEdge="horizontal" | 24 | android:requiresFadingEdge="horizontal" |
27 | android:singleLine="true" | ||
28 | android:textAlignment="viewStart" | 25 | android:textAlignment="viewStart" |
29 | app:layout_constraintBottom_toBottomOf="parent" | 26 | app:layout_constraintBottom_toBottomOf="parent" |
30 | app:layout_constraintEnd_toStartOf="@+id/button_layout" | 27 | app:layout_constraintEnd_toStartOf="@+id/button_layout" |
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml index 6340171ec..411b50315 100755 --- a/src/android/app/src/main/res/layout/card_game.xml +++ b/src/android/app/src/main/res/layout/card_game.xml | |||
@@ -40,10 +40,7 @@ | |||
40 | android:layout_width="0dp" | 40 | android:layout_width="0dp" |
41 | android:layout_height="wrap_content" | 41 | android:layout_height="wrap_content" |
42 | android:layout_marginTop="8dp" | 42 | android:layout_marginTop="8dp" |
43 | android:ellipsize="none" | ||
44 | android:marqueeRepeatLimit="marquee_forever" | ||
45 | android:requiresFadingEdge="horizontal" | 43 | android:requiresFadingEdge="horizontal" |
46 | android:singleLine="true" | ||
47 | android:textAlignment="center" | 44 | android:textAlignment="center" |
48 | android:textSize="14sp" | 45 | android:textSize="14sp" |
49 | app:layout_constraintEnd_toEndOf="@+id/image_game_screen" | 46 | app:layout_constraintEnd_toEndOf="@+id/image_game_screen" |
diff --git a/src/android/app/src/main/res/layout/card_simple_outlined.xml b/src/android/app/src/main/res/layout/card_simple_outlined.xml index b73930e7e..e29df6a2d 100755 --- a/src/android/app/src/main/res/layout/card_simple_outlined.xml +++ b/src/android/app/src/main/res/layout/card_simple_outlined.xml | |||
@@ -59,9 +59,6 @@ | |||
59 | android:textAlignment="viewStart" | 59 | android:textAlignment="viewStart" |
60 | android:textSize="14sp" | 60 | android:textSize="14sp" |
61 | android:textStyle="bold" | 61 | android:textStyle="bold" |
62 | android:singleLine="true" | ||
63 | android:marqueeRepeatLimit="marquee_forever" | ||
64 | android:ellipsize="none" | ||
65 | android:requiresFadingEdge="horizontal" | 62 | android:requiresFadingEdge="horizontal" |
66 | android:layout_marginTop="6dp" | 63 | android:layout_marginTop="6dp" |
67 | android:visibility="gone" | 64 | android:visibility="gone" |
diff --git a/src/android/app/src/main/res/layout/dialog_input_profiles.xml b/src/android/app/src/main/res/layout/dialog_input_profiles.xml new file mode 100755 index 000000000..6ad76fe41 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_input_profiles.xml | |||
@@ -0,0 +1,6 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | android:id="@+id/list_profiles" | ||
4 | android:layout_width="match_parent" | ||
5 | android:layout_height="wrap_content" | ||
6 | android:fadeScrollbars="false" /> | ||
diff --git a/src/android/app/src/main/res/layout/dialog_mapping.xml b/src/android/app/src/main/res/layout/dialog_mapping.xml new file mode 100755 index 000000000..06190b8d2 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_mapping.xml | |||
@@ -0,0 +1,26 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | android:layout_width="match_parent" | ||
4 | android:layout_height="wrap_content" | ||
5 | xmlns:tools="http://schemas.android.com/tools" | ||
6 | android:defaultFocusHighlightEnabled="false" | ||
7 | android:focusable="true" | ||
8 | android:focusableInTouchMode="true" | ||
9 | android:focusedByDefault="true" | ||
10 | android:orientation="horizontal" | ||
11 | android:gravity="center"> | ||
12 | |||
13 | <ImageView | ||
14 | android:id="@+id/image_stick_animation" | ||
15 | android:layout_width="@dimen/mapping_anim_size" | ||
16 | android:layout_height="@dimen/mapping_anim_size" | ||
17 | tools:src="@drawable/stick_two_direction_anim" /> | ||
18 | |||
19 | <ImageView | ||
20 | android:id="@+id/image_button_animation" | ||
21 | android:layout_width="@dimen/mapping_anim_size" | ||
22 | android:layout_height="@dimen/mapping_anim_size" | ||
23 | android:layout_marginStart="48dp" | ||
24 | tools:src="@drawable/button_anim" /> | ||
25 | |||
26 | </LinearLayout> | ||
diff --git a/src/android/app/src/main/res/layout/fragment_game_properties.xml b/src/android/app/src/main/res/layout/fragment_game_properties.xml index 436ebd79d..5e3f3cf28 100755 --- a/src/android/app/src/main/res/layout/fragment_game_properties.xml +++ b/src/android/app/src/main/res/layout/fragment_game_properties.xml | |||
@@ -76,10 +76,7 @@ | |||
76 | android:layout_marginTop="12dp" | 76 | android:layout_marginTop="12dp" |
77 | android:layout_marginBottom="12dp" | 77 | android:layout_marginBottom="12dp" |
78 | android:layout_marginHorizontal="16dp" | 78 | android:layout_marginHorizontal="16dp" |
79 | android:ellipsize="none" | ||
80 | android:marqueeRepeatLimit="marquee_forever" | ||
81 | android:requiresFadingEdge="horizontal" | 79 | android:requiresFadingEdge="horizontal" |
82 | android:singleLine="true" | ||
83 | android:textAlignment="center" | 80 | android:textAlignment="center" |
84 | tools:text="deko_basic" /> | 81 | tools:text="deko_basic" /> |
85 | 82 | ||
diff --git a/src/android/app/src/main/res/layout/list_item_input_profile.xml b/src/android/app/src/main/res/layout/list_item_input_profile.xml new file mode 100755 index 000000000..a08dccf0c --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_input_profile.xml | |||
@@ -0,0 +1,74 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
4 | xmlns:tools="http://schemas.android.com/tools" | ||
5 | android:layout_width="match_parent" | ||
6 | android:layout_height="wrap_content" | ||
7 | android:focusable="false" | ||
8 | android:paddingHorizontal="20dp" | ||
9 | android:paddingVertical="16dp"> | ||
10 | |||
11 | <com.google.android.material.textview.MaterialTextView | ||
12 | android:id="@+id/title" | ||
13 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
14 | android:layout_width="0dp" | ||
15 | android:layout_height="0dp" | ||
16 | android:textAlignment="viewStart" | ||
17 | android:gravity="start|center_vertical" | ||
18 | android:textSize="17sp" | ||
19 | android:layout_marginEnd="16dp" | ||
20 | app:layout_constraintBottom_toBottomOf="@+id/button_layout" | ||
21 | app:layout_constraintEnd_toStartOf="@+id/button_layout" | ||
22 | app:layout_constraintStart_toStartOf="parent" | ||
23 | app:layout_constraintTop_toTopOf="parent" | ||
24 | app:lineHeight="28dp" | ||
25 | tools:text="My profile" /> | ||
26 | |||
27 | <LinearLayout | ||
28 | android:id="@+id/button_layout" | ||
29 | android:layout_width="wrap_content" | ||
30 | android:layout_height="wrap_content" | ||
31 | android:gravity="center_vertical" | ||
32 | android:orientation="horizontal" | ||
33 | app:layout_constraintEnd_toEndOf="parent" | ||
34 | app:layout_constraintTop_toTopOf="parent"> | ||
35 | |||
36 | <Button | ||
37 | android:id="@+id/button_new" | ||
38 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
39 | android:layout_width="wrap_content" | ||
40 | android:layout_height="wrap_content" | ||
41 | android:contentDescription="@string/create_new_profile" | ||
42 | android:tooltipText="@string/create_new_profile" | ||
43 | app:icon="@drawable/ic_new_label" /> | ||
44 | |||
45 | <Button | ||
46 | android:id="@+id/button_delete" | ||
47 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
48 | android:layout_width="wrap_content" | ||
49 | android:layout_height="wrap_content" | ||
50 | android:contentDescription="@string/delete" | ||
51 | android:tooltipText="@string/delete" | ||
52 | app:icon="@drawable/ic_delete" /> | ||
53 | |||
54 | <Button | ||
55 | android:id="@+id/button_save" | ||
56 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
57 | android:layout_width="wrap_content" | ||
58 | android:layout_height="wrap_content" | ||
59 | android:contentDescription="@string/save" | ||
60 | android:tooltipText="@string/save" | ||
61 | app:icon="@drawable/ic_save" /> | ||
62 | |||
63 | <Button | ||
64 | android:id="@+id/button_load" | ||
65 | style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" | ||
66 | android:layout_width="wrap_content" | ||
67 | android:layout_height="wrap_content" | ||
68 | android:contentDescription="@string/load" | ||
69 | android:tooltipText="@string/load" | ||
70 | app:icon="@drawable/ic_import" /> | ||
71 | |||
72 | </LinearLayout> | ||
73 | |||
74 | </androidx.constraintlayout.widget.ConstraintLayout> | ||
diff --git a/src/android/app/src/main/res/layout/list_item_setting_input.xml b/src/android/app/src/main/res/layout/list_item_setting_input.xml new file mode 100755 index 000000000..d67cbe245 --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_setting_input.xml | |||
@@ -0,0 +1,63 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
4 | xmlns:tools="http://schemas.android.com/tools" | ||
5 | android:id="@+id/setting_body" | ||
6 | android:layout_width="match_parent" | ||
7 | android:layout_height="wrap_content" | ||
8 | android:background="?android:attr/selectableItemBackground" | ||
9 | android:clickable="true" | ||
10 | android:focusable="true" | ||
11 | android:gravity="center_vertical" | ||
12 | android:minHeight="72dp" | ||
13 | android:padding="16dp" | ||
14 | android:nextFocusRight="@id/button_options"> | ||
15 | |||
16 | <LinearLayout | ||
17 | android:layout_width="match_parent" | ||
18 | android:layout_height="wrap_content" | ||
19 | android:gravity="center_vertical" | ||
20 | android:orientation="horizontal"> | ||
21 | |||
22 | <LinearLayout | ||
23 | android:layout_width="0dp" | ||
24 | android:layout_height="wrap_content" | ||
25 | android:orientation="vertical" | ||
26 | android:layout_weight="1"> | ||
27 | |||
28 | <com.google.android.material.textview.MaterialTextView | ||
29 | android:id="@+id/text_setting_name" | ||
30 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
31 | android:layout_width="match_parent" | ||
32 | android:layout_height="wrap_content" | ||
33 | android:textAlignment="viewStart" | ||
34 | android:textSize="17sp" | ||
35 | app:lineHeight="22dp" | ||
36 | tools:text="Setting Name" /> | ||
37 | |||
38 | <com.google.android.material.textview.MaterialTextView | ||
39 | android:id="@+id/text_setting_value" | ||
40 | style="@style/TextAppearance.Material3.LabelMedium" | ||
41 | android:layout_width="match_parent" | ||
42 | android:layout_height="wrap_content" | ||
43 | android:layout_marginTop="@dimen/spacing_small" | ||
44 | android:textAlignment="viewStart" | ||
45 | android:textStyle="bold" | ||
46 | android:textSize="13sp" | ||
47 | tools:text="1x" /> | ||
48 | |||
49 | </LinearLayout> | ||
50 | |||
51 | <Button | ||
52 | android:id="@+id/button_options" | ||
53 | style="?attr/materialIconButtonStyle" | ||
54 | android:layout_width="wrap_content" | ||
55 | android:layout_height="wrap_content" | ||
56 | android:nextFocusLeft="@id/setting_body" | ||
57 | app:icon="@drawable/ic_more_vert" | ||
58 | app:iconSize="24dp" | ||
59 | app:iconTint="?attr/colorOnSurface" /> | ||
60 | |||
61 | </LinearLayout> | ||
62 | |||
63 | </RelativeLayout> | ||
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml index eecb0563b..867197ebc 100755 --- a/src/android/app/src/main/res/menu/menu_in_game.xml +++ b/src/android/app/src/main/res/menu/menu_in_game.xml | |||
@@ -17,8 +17,13 @@ | |||
17 | android:title="@string/per_game_settings" /> | 17 | android:title="@string/per_game_settings" /> |
18 | 18 | ||
19 | <item | 19 | <item |
20 | android:id="@+id/menu_overlay_controls" | 20 | android:id="@+id/menu_controls" |
21 | android:icon="@drawable/ic_controller" | 21 | android:icon="@drawable/ic_controller" |
22 | android:title="@string/preferences_controls" /> | ||
23 | |||
24 | <item | ||
25 | android:id="@+id/menu_overlay_controls" | ||
26 | android:icon="@drawable/ic_overlay" | ||
22 | android:title="@string/emulation_input_overlay" /> | 27 | android:title="@string/emulation_input_overlay" /> |
23 | 28 | ||
24 | <item | 29 | <item |
diff --git a/src/android/app/src/main/res/menu/menu_input_options.xml b/src/android/app/src/main/res/menu/menu_input_options.xml new file mode 100755 index 000000000..81ea5043f --- /dev/null +++ b/src/android/app/src/main/res/menu/menu_input_options.xml | |||
@@ -0,0 +1,34 @@ | |||
1 | <?xml version="1.0" encoding="utf-8"?> | ||
2 | <menu xmlns:android="http://schemas.android.com/apk/res/android"> | ||
3 | |||
4 | <item | ||
5 | android:id="@+id/invert_axis" | ||
6 | android:title="@string/invert_axis" | ||
7 | android:visible="false" /> | ||
8 | |||
9 | <item | ||
10 | android:id="@+id/invert_button" | ||
11 | android:title="@string/invert_button" | ||
12 | android:visible="false" /> | ||
13 | |||
14 | <item | ||
15 | android:id="@+id/toggle_button" | ||
16 | android:title="@string/toggle_button" | ||
17 | android:visible="false" /> | ||
18 | |||
19 | <item | ||
20 | android:id="@+id/turbo_button" | ||
21 | android:title="@string/turbo_button" | ||
22 | android:visible="false" /> | ||
23 | |||
24 | <item | ||
25 | android:id="@+id/set_threshold" | ||
26 | android:title="@string/set_threshold" | ||
27 | android:visible="false" /> | ||
28 | |||
29 | <item | ||
30 | android:id="@+id/toggle_axis" | ||
31 | android:title="@string/toggle_axis" | ||
32 | android:visible="false" /> | ||
33 | |||
34 | </menu> | ||
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml index 1d87d36b3..e4c66e7d5 100755 --- a/src/android/app/src/main/res/navigation/settings_navigation.xml +++ b/src/android/app/src/main/res/navigation/settings_navigation.xml | |||
@@ -26,7 +26,7 @@ | |||
26 | 26 | ||
27 | <fragment | 27 | <fragment |
28 | android:id="@+id/settingsSearchFragment" | 28 | android:id="@+id/settingsSearchFragment" |
29 | android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment" | 29 | android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsSearchFragment" |
30 | android:label="SettingsSearchFragment" /> | 30 | android:label="SettingsSearchFragment" /> |
31 | 31 | ||
32 | </navigation> | 32 | </navigation> |
diff --git a/src/android/app/src/main/res/values-w600dp/dimens.xml b/src/android/app/src/main/res/values-w600dp/dimens.xml index 128319e27..0e2d40876 100755 --- a/src/android/app/src/main/res/values-w600dp/dimens.xml +++ b/src/android/app/src/main/res/values-w600dp/dimens.xml | |||
@@ -2,4 +2,6 @@ | |||
2 | <resources> | 2 | <resources> |
3 | <dimen name="spacing_navigation">0dp</dimen> | 3 | <dimen name="spacing_navigation">0dp</dimen> |
4 | <dimen name="spacing_navigation_rail">80dp</dimen> | 4 | <dimen name="spacing_navigation_rail">80dp</dimen> |
5 | |||
6 | <dimen name="mapping_anim_size">100dp</dimen> | ||
5 | </resources> | 7 | </resources> |
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 992b5ae44..bf733637f 100755 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml | |||
@@ -18,4 +18,6 @@ | |||
18 | 18 | ||
19 | <dimen name="dialog_margin">20dp</dimen> | 19 | <dimen name="dialog_margin">20dp</dimen> |
20 | <dimen name="elevated_app_bar">3dp</dimen> | 20 | <dimen name="elevated_app_bar">3dp</dimen> |
21 | |||
22 | <dimen name="mapping_anim_size">75dp</dimen> | ||
21 | </resources> | 23 | </resources> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 78a4c958a..6a631f664 100755 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
@@ -255,6 +255,92 @@ | |||
255 | <string name="audio_volume">Volume</string> | 255 | <string name="audio_volume">Volume</string> |
256 | <string name="audio_volume_description">Specifies the volume of audio output.</string> | 256 | <string name="audio_volume_description">Specifies the volume of audio output.</string> |
257 | 257 | ||
258 | <!-- Input strings --> | ||
259 | <string name="buttons">Buttons</string> | ||
260 | <string name="button_a">A</string> | ||
261 | <string name="button_b">B</string> | ||
262 | <string name="button_x">X</string> | ||
263 | <string name="button_y">Y</string> | ||
264 | <string name="button_plus">Plus</string> | ||
265 | <string name="button_minus">Minus</string> | ||
266 | <string name="button_home">Home</string> | ||
267 | <string name="button_capture">Capture</string> | ||
268 | <string name="start_pause">Start/Pause</string> | ||
269 | <string name="dpad">D-Pad</string> | ||
270 | <string name="up">Up</string> | ||
271 | <string name="down">Down</string> | ||
272 | <string name="left">Left</string> | ||
273 | <string name="right">Right</string> | ||
274 | <string name="left_stick">Left stick</string> | ||
275 | <string name="control_stick">Control stick</string> | ||
276 | <string name="right_stick">Right stick</string> | ||
277 | <string name="c_stick">C-Stick</string> | ||
278 | <string name="pressed">Pressed</string> | ||
279 | <string name="range">Range</string> | ||
280 | <string name="deadzone">Deadzone</string> | ||
281 | <string name="modifier">Modifier</string> | ||
282 | <string name="modifier_range">Modifier range</string> | ||
283 | <string name="triggers">Triggers</string> | ||
284 | <string name="button_l">L</string> | ||
285 | <string name="button_r">R</string> | ||
286 | <string name="button_zl">ZL</string> | ||
287 | <string name="button_zr">ZR</string> | ||
288 | <string name="button_sl_left">Left SL</string> | ||
289 | <string name="button_sr_left">Left SR</string> | ||
290 | <string name="button_sl_right">Right SL</string> | ||
291 | <string name="button_sr_right">Right SR</string> | ||
292 | <string name="button_z">Z</string> | ||
293 | <string name="invalid">Invalid</string> | ||
294 | <string name="not_set">Not set</string> | ||
295 | <string name="unknown">Unknown</string> | ||
296 | <string name="qualified_hat">%1$s%2$s%3$sHat %4$s</string> | ||
297 | <string name="qualified_button_stick_axis">%1$s%2$s%3$sAxis %4$s</string> | ||
298 | <string name="qualified_button">%1$s%2$s%3$sButton %4$s</string> | ||
299 | <string name="qualified_axis">Axis %1$s%2$s</string> | ||
300 | <string name="unused">Unused</string> | ||
301 | <string name="input_prompt">Move or press an input</string> | ||
302 | <string name="unsupported_input">Unsupported input type</string> | ||
303 | <string name="input_mapping_filter">Input mapping filter</string> | ||
304 | <string name="input_mapping_filter_description">Select a device to filter mapping inputs</string> | ||
305 | <string name="auto_map">Auto-map a controller</string> | ||
306 | <string name="auto_map_description">Select a device to attempt auto-mapping</string> | ||
307 | <string name="attempted_auto_map">Attempted auto-map with %1$s</string> | ||
308 | <string name="controller_type">Controller type</string> | ||
309 | <string name="pro_controller">Pro Controller</string> | ||
310 | <string name="handheld">Handheld</string> | ||
311 | <string name="dual_joycons">Dual Joycons</string> | ||
312 | <string name="left_joycon">Left Joycon</string> | ||
313 | <string name="right_joycon">Right Joycon</string> | ||
314 | <string name="gamecube_controller">GameCube Controller</string> | ||
315 | <string name="invert_axis">Invert axis</string> | ||
316 | <string name="invert_button">Invert button</string> | ||
317 | <string name="toggle_button">Toggle button</string> | ||
318 | <string name="turbo_button">Turbo button</string> | ||
319 | <string name="set_threshold">Set threshold</string> | ||
320 | <string name="toggle_axis">Toggle axis</string> | ||
321 | <string name="connected">Connected</string> | ||
322 | <string name="use_system_vibrator">Use system vibrator</string> | ||
323 | <string name="input_overlay">Input overlay</string> | ||
324 | <string name="vibration">Vibration</string> | ||
325 | <string name="vibration_strength">Vibration strength</string> | ||
326 | <string name="profile">Profile</string> | ||
327 | <string name="create_new_profile">Create new profile</string> | ||
328 | <string name="enter_profile_name">Enter profile name</string> | ||
329 | <string name="profile_name_already_exists">Profile name already exists</string> | ||
330 | <string name="invalid_profile_name">Invalid profile name</string> | ||
331 | <string name="use_global_input_configuration">Use global input configuration</string> | ||
332 | <string name="player_num_profile">Player %d profile</string> | ||
333 | <string name="delete_input_profile">Delete input profile</string> | ||
334 | <string name="delete_input_profile_description">Are you sure that you want to delete this profile? This is not recoverable.</string> | ||
335 | <string name="stick_map_description">Move a stick left and then up or press a button</string> | ||
336 | <string name="button_map_description">Press a button or move a trigger/stick</string> | ||
337 | <string name="map_dpad_direction">Map to D-Pad %1$s</string> | ||
338 | <string name="map_control">Map to %1$s</string> | ||
339 | <string name="failed_to_load_profile">Failed to load profile</string> | ||
340 | <string name="failed_to_save_profile">Failed to save profile</string> | ||
341 | <string name="reset_mapping">Reset mappings</string> | ||
342 | <string name="reset_mapping_description">Are you sure that you want to reset all mappings for this controller to default? This cannot be undone.</string> | ||
343 | |||
258 | <!-- Miscellaneous --> | 344 | <!-- Miscellaneous --> |
259 | <string name="slider_default">Default</string> | 345 | <string name="slider_default">Default</string> |
260 | <string name="ini_saved">Saved settings</string> | 346 | <string name="ini_saved">Saved settings</string> |
@@ -292,6 +378,10 @@ | |||
292 | <string name="more_options">More options</string> | 378 | <string name="more_options">More options</string> |
293 | <string name="use_global_setting">Use global setting</string> | 379 | <string name="use_global_setting">Use global setting</string> |
294 | <string name="operation_completed_successfully">The operation completed successfully</string> | 380 | <string name="operation_completed_successfully">The operation completed successfully</string> |
381 | <string name="retry">Retry</string> | ||
382 | <string name="confirm">Confirm</string> | ||
383 | <string name="load">Load</string> | ||
384 | <string name="save">Save</string> | ||
295 | 385 | ||
296 | <!-- GPU driver installation --> | 386 | <!-- GPU driver installation --> |
297 | <string name="select_gpu_driver">Select GPU driver</string> | 387 | <string name="select_gpu_driver">Select GPU driver</string> |
@@ -313,6 +403,9 @@ | |||
313 | <string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string> | 403 | <string name="preferences_graphics_description">Accuracy level, resolution, shader cache</string> |
314 | <string name="preferences_audio">Audio</string> | 404 | <string name="preferences_audio">Audio</string> |
315 | <string name="preferences_audio_description">Output engine, volume</string> | 405 | <string name="preferences_audio_description">Output engine, volume</string> |
406 | <string name="preferences_controls">Controls</string> | ||
407 | <string name="preferences_controls_description">Map controller input</string> | ||
408 | <string name="preferences_player">Player %d</string> | ||
316 | <string name="preferences_theme">Theme and color</string> | 409 | <string name="preferences_theme">Theme and color</string> |
317 | <string name="preferences_debug">Debug</string> | 410 | <string name="preferences_debug">Debug</string> |
318 | <string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string> | 411 | <string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string> |
diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index f39262db9..1145cbdf2 100755 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp | |||
@@ -65,6 +65,30 @@ static jclass s_boolean_class; | |||
65 | static jmethodID s_boolean_constructor; | 65 | static jmethodID s_boolean_constructor; |
66 | static jfieldID s_boolean_value_field; | 66 | static jfieldID s_boolean_value_field; |
67 | 67 | ||
68 | static jclass s_player_input_class; | ||
69 | static jmethodID s_player_input_constructor; | ||
70 | static jfieldID s_player_input_connected_field; | ||
71 | static jfieldID s_player_input_buttons_field; | ||
72 | static jfieldID s_player_input_analogs_field; | ||
73 | static jfieldID s_player_input_motions_field; | ||
74 | static jfieldID s_player_input_vibration_enabled_field; | ||
75 | static jfieldID s_player_input_vibration_strength_field; | ||
76 | static jfieldID s_player_input_body_color_left_field; | ||
77 | static jfieldID s_player_input_body_color_right_field; | ||
78 | static jfieldID s_player_input_button_color_left_field; | ||
79 | static jfieldID s_player_input_button_color_right_field; | ||
80 | static jfieldID s_player_input_profile_name_field; | ||
81 | static jfieldID s_player_input_use_system_vibrator_field; | ||
82 | |||
83 | static jclass s_yuzu_input_device_interface; | ||
84 | static jmethodID s_yuzu_input_device_get_name; | ||
85 | static jmethodID s_yuzu_input_device_get_guid; | ||
86 | static jmethodID s_yuzu_input_device_get_port; | ||
87 | static jmethodID s_yuzu_input_device_get_supports_vibration; | ||
88 | static jmethodID s_yuzu_input_device_vibrate; | ||
89 | static jmethodID s_yuzu_input_device_get_axes; | ||
90 | static jmethodID s_yuzu_input_device_has_keys; | ||
91 | |||
68 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; | 92 | static constexpr jint JNI_VERSION = JNI_VERSION_1_6; |
69 | 93 | ||
70 | namespace Common::Android { | 94 | namespace Common::Android { |
@@ -276,6 +300,94 @@ jfieldID GetBooleanValueField() { | |||
276 | return s_boolean_value_field; | 300 | return s_boolean_value_field; |
277 | } | 301 | } |
278 | 302 | ||
303 | jclass GetPlayerInputClass() { | ||
304 | return s_player_input_class; | ||
305 | } | ||
306 | |||
307 | jmethodID GetPlayerInputConstructor() { | ||
308 | return s_player_input_constructor; | ||
309 | } | ||
310 | |||
311 | jfieldID GetPlayerInputConnectedField() { | ||
312 | return s_player_input_connected_field; | ||
313 | } | ||
314 | |||
315 | jfieldID GetPlayerInputButtonsField() { | ||
316 | return s_player_input_buttons_field; | ||
317 | } | ||
318 | |||
319 | jfieldID GetPlayerInputAnalogsField() { | ||
320 | return s_player_input_analogs_field; | ||
321 | } | ||
322 | |||
323 | jfieldID GetPlayerInputMotionsField() { | ||
324 | return s_player_input_motions_field; | ||
325 | } | ||
326 | |||
327 | jfieldID GetPlayerInputVibrationEnabledField() { | ||
328 | return s_player_input_vibration_enabled_field; | ||
329 | } | ||
330 | |||
331 | jfieldID GetPlayerInputVibrationStrengthField() { | ||
332 | return s_player_input_vibration_strength_field; | ||
333 | } | ||
334 | |||
335 | jfieldID GetPlayerInputBodyColorLeftField() { | ||
336 | return s_player_input_body_color_left_field; | ||
337 | } | ||
338 | |||
339 | jfieldID GetPlayerInputBodyColorRightField() { | ||
340 | return s_player_input_body_color_right_field; | ||
341 | } | ||
342 | |||
343 | jfieldID GetPlayerInputButtonColorLeftField() { | ||
344 | return s_player_input_button_color_left_field; | ||
345 | } | ||
346 | |||
347 | jfieldID GetPlayerInputButtonColorRightField() { | ||
348 | return s_player_input_button_color_right_field; | ||
349 | } | ||
350 | |||
351 | jfieldID GetPlayerInputProfileNameField() { | ||
352 | return s_player_input_profile_name_field; | ||
353 | } | ||
354 | |||
355 | jfieldID GetPlayerInputUseSystemVibratorField() { | ||
356 | return s_player_input_use_system_vibrator_field; | ||
357 | } | ||
358 | |||
359 | jclass GetYuzuInputDeviceInterface() { | ||
360 | return s_yuzu_input_device_interface; | ||
361 | } | ||
362 | |||
363 | jmethodID GetYuzuDeviceGetName() { | ||
364 | return s_yuzu_input_device_get_name; | ||
365 | } | ||
366 | |||
367 | jmethodID GetYuzuDeviceGetGUID() { | ||
368 | return s_yuzu_input_device_get_guid; | ||
369 | } | ||
370 | |||
371 | jmethodID GetYuzuDeviceGetPort() { | ||
372 | return s_yuzu_input_device_get_port; | ||
373 | } | ||
374 | |||
375 | jmethodID GetYuzuDeviceGetSupportsVibration() { | ||
376 | return s_yuzu_input_device_get_supports_vibration; | ||
377 | } | ||
378 | |||
379 | jmethodID GetYuzuDeviceVibrate() { | ||
380 | return s_yuzu_input_device_vibrate; | ||
381 | } | ||
382 | |||
383 | jmethodID GetYuzuDeviceGetAxes() { | ||
384 | return s_yuzu_input_device_get_axes; | ||
385 | } | ||
386 | |||
387 | jmethodID GetYuzuDeviceHasKeys() { | ||
388 | return s_yuzu_input_device_has_keys; | ||
389 | } | ||
390 | |||
279 | #ifdef __cplusplus | 391 | #ifdef __cplusplus |
280 | extern "C" { | 392 | extern "C" { |
281 | #endif | 393 | #endif |
@@ -387,6 +499,55 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { | |||
387 | s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); | 499 | s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); |
388 | env->DeleteLocalRef(boolean_class); | 500 | env->DeleteLocalRef(boolean_class); |
389 | 501 | ||
502 | const jclass player_input_class = | ||
503 | env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); | ||
504 | s_player_input_class = reinterpret_cast<jclass>(env->NewGlobalRef(player_input_class)); | ||
505 | s_player_input_constructor = env->GetMethodID( | ||
506 | player_input_class, "<init>", | ||
507 | "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); | ||
508 | s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); | ||
509 | s_player_input_buttons_field = | ||
510 | env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); | ||
511 | s_player_input_analogs_field = | ||
512 | env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); | ||
513 | s_player_input_motions_field = | ||
514 | env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); | ||
515 | s_player_input_vibration_enabled_field = | ||
516 | env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); | ||
517 | s_player_input_vibration_strength_field = | ||
518 | env->GetFieldID(player_input_class, "vibrationStrength", "I"); | ||
519 | s_player_input_body_color_left_field = | ||
520 | env->GetFieldID(player_input_class, "bodyColorLeft", "J"); | ||
521 | s_player_input_body_color_right_field = | ||
522 | env->GetFieldID(player_input_class, "bodyColorRight", "J"); | ||
523 | s_player_input_button_color_left_field = | ||
524 | env->GetFieldID(player_input_class, "buttonColorLeft", "J"); | ||
525 | s_player_input_button_color_right_field = | ||
526 | env->GetFieldID(player_input_class, "buttonColorRight", "J"); | ||
527 | s_player_input_profile_name_field = | ||
528 | env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); | ||
529 | s_player_input_use_system_vibrator_field = | ||
530 | env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); | ||
531 | env->DeleteLocalRef(player_input_class); | ||
532 | |||
533 | const jclass yuzu_input_device_interface = | ||
534 | env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); | ||
535 | s_yuzu_input_device_interface = | ||
536 | reinterpret_cast<jclass>(env->NewGlobalRef(yuzu_input_device_interface)); | ||
537 | s_yuzu_input_device_get_name = | ||
538 | env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); | ||
539 | s_yuzu_input_device_get_guid = | ||
540 | env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); | ||
541 | s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", "()I"); | ||
542 | s_yuzu_input_device_get_supports_vibration = | ||
543 | env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); | ||
544 | s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", "(F)V"); | ||
545 | s_yuzu_input_device_get_axes = | ||
546 | env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); | ||
547 | s_yuzu_input_device_has_keys = | ||
548 | env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); | ||
549 | env->DeleteLocalRef(yuzu_input_device_interface); | ||
550 | |||
390 | // Initialize Android Storage | 551 | // Initialize Android Storage |
391 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); | 552 | Common::FS::Android::RegisterCallbacks(env, s_native_library_class); |
392 | 553 | ||
@@ -416,6 +577,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { | |||
416 | env->DeleteGlobalRef(s_double_class); | 577 | env->DeleteGlobalRef(s_double_class); |
417 | env->DeleteGlobalRef(s_integer_class); | 578 | env->DeleteGlobalRef(s_integer_class); |
418 | env->DeleteGlobalRef(s_boolean_class); | 579 | env->DeleteGlobalRef(s_boolean_class); |
580 | env->DeleteGlobalRef(s_player_input_class); | ||
581 | env->DeleteGlobalRef(s_yuzu_input_device_interface); | ||
419 | 582 | ||
420 | // UnInitialize applets | 583 | // UnInitialize applets |
421 | SoftwareKeyboard::CleanupJNI(env); | 584 | SoftwareKeyboard::CleanupJNI(env); |
diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 47802f96c..cd2844dcc 100755 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h | |||
@@ -85,4 +85,28 @@ jclass GetBooleanClass(); | |||
85 | jmethodID GetBooleanConstructor(); | 85 | jmethodID GetBooleanConstructor(); |
86 | jfieldID GetBooleanValueField(); | 86 | jfieldID GetBooleanValueField(); |
87 | 87 | ||
88 | jclass GetPlayerInputClass(); | ||
89 | jmethodID GetPlayerInputConstructor(); | ||
90 | jfieldID GetPlayerInputConnectedField(); | ||
91 | jfieldID GetPlayerInputButtonsField(); | ||
92 | jfieldID GetPlayerInputAnalogsField(); | ||
93 | jfieldID GetPlayerInputMotionsField(); | ||
94 | jfieldID GetPlayerInputVibrationEnabledField(); | ||
95 | jfieldID GetPlayerInputVibrationStrengthField(); | ||
96 | jfieldID GetPlayerInputBodyColorLeftField(); | ||
97 | jfieldID GetPlayerInputBodyColorRightField(); | ||
98 | jfieldID GetPlayerInputButtonColorLeftField(); | ||
99 | jfieldID GetPlayerInputButtonColorRightField(); | ||
100 | jfieldID GetPlayerInputProfileNameField(); | ||
101 | jfieldID GetPlayerInputUseSystemVibratorField(); | ||
102 | |||
103 | jclass GetYuzuInputDeviceInterface(); | ||
104 | jmethodID GetYuzuDeviceGetName(); | ||
105 | jmethodID GetYuzuDeviceGetGUID(); | ||
106 | jmethodID GetYuzuDeviceGetPort(); | ||
107 | jmethodID GetYuzuDeviceGetSupportsVibration(); | ||
108 | jmethodID GetYuzuDeviceVibrate(); | ||
109 | jmethodID GetYuzuDeviceGetAxes(); | ||
110 | jmethodID GetYuzuDeviceHasKeys(); | ||
111 | |||
88 | } // namespace Common::Android | 112 | } // namespace Common::Android |
diff --git a/src/common/settings_input.h b/src/common/settings_input.h index fccd14810..086503d8e 100755 --- a/src/common/settings_input.h +++ b/src/common/settings_input.h | |||
@@ -395,6 +395,10 @@ struct PlayerInput { | |||
395 | u32 button_color_left; | 395 | u32 button_color_left; |
396 | u32 button_color_right; | 396 | u32 button_color_right; |
397 | std::string profile_name; | 397 | std::string profile_name; |
398 | |||
399 | // This is meant to tell the Android frontend whether to use a device's built-in vibration | ||
400 | // motor or a controller's vibrations. | ||
401 | bool use_system_vibrator; | ||
398 | }; | 402 | }; |
399 | 403 | ||
400 | struct TouchscreenInput { | 404 | struct TouchscreenInput { |
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 66135e7c3..690ef3548 100755 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp | |||
@@ -117,9 +117,9 @@ bool StandardVmCallbacks::IsAddressInRange(VAddr in) const { | |||
117 | (in < metadata.heap_extents.base || | 117 | (in < metadata.heap_extents.base || |
118 | in >= metadata.heap_extents.base + metadata.heap_extents.size) && | 118 | in >= metadata.heap_extents.base + metadata.heap_extents.size) && |
119 | (in < metadata.alias_extents.base || | 119 | (in < metadata.alias_extents.base || |
120 | in >= metadata.heap_extents.base + metadata.alias_extents.size) && | 120 | in >= metadata.alias_extents.base + metadata.alias_extents.size) && |
121 | (in < metadata.aslr_extents.base || | 121 | (in < metadata.aslr_extents.base || |
122 | in >= metadata.heap_extents.base + metadata.aslr_extents.size)) { | 122 | in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) { |
123 | LOG_DEBUG(CheatEngine, | 123 | LOG_DEBUG(CheatEngine, |
124 | "Cheat attempting to access memory at invalid address={:016X}, if this " | 124 | "Cheat attempting to access memory at invalid address={:016X}, if this " |
125 | "persists, " | 125 | "persists, " |
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 2bebfeef9..95f8c8c36 100755 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp | |||
@@ -138,6 +138,7 @@ void Config::ReadPlayerValues(const std::size_t player_index) { | |||
138 | if (profile_name.empty()) { | 138 | if (profile_name.empty()) { |
139 | // Use the global input config | 139 | // Use the global input config |
140 | player = Settings::values.players.GetValue(true)[player_index]; | 140 | player = Settings::values.players.GetValue(true)[player_index]; |
141 | player.profile_name = ""; | ||
141 | return; | 142 | return; |
142 | } | 143 | } |
143 | player.profile_name = profile_name; | 144 | player.profile_name = profile_name; |
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index f3efe3465..c4e97a47b 100755 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h | |||
@@ -251,11 +251,12 @@ inline InstallResult InstallNCA(FileSys::VfsFilesystem& vfs, const std::string& | |||
251 | * \param callback Callback to report the progress of the installation. The first size_t | 251 | * \param callback Callback to report the progress of the installation. The first size_t |
252 | * parameter is the total size of the installed contents and the second is the current progress. If | 252 | * parameter is the total size of the installed contents and the second is the current progress. If |
253 | * you return true to the callback, it will cancel the installation as soon as possible. | 253 | * you return true to the callback, it will cancel the installation as soon as possible. |
254 | * \param firmware_only Set to true to only scan system nand NCAs (firmware), post firmware install. | ||
254 | * \return A list of entries that failed to install. Returns an empty vector if successful. | 255 | * \return A list of entries that failed to install. Returns an empty vector if successful. |
255 | */ | 256 | */ |
256 | inline std::vector<std::string> VerifyInstalledContents( | 257 | inline std::vector<std::string> VerifyInstalledContents( |
257 | Core::System& system, FileSys::ManualContentProvider& provider, | 258 | Core::System& system, FileSys::ManualContentProvider& provider, |
258 | const std::function<bool(size_t, size_t)>& callback) { | 259 | const std::function<bool(size_t, size_t)>& callback, bool firmware_only = false) { |
259 | // Get content registries. | 260 | // Get content registries. |
260 | auto bis_contents = system.GetFileSystemController().GetSystemNANDContents(); | 261 | auto bis_contents = system.GetFileSystemController().GetSystemNANDContents(); |
261 | auto user_contents = system.GetFileSystemController().GetUserNANDContents(); | 262 | auto user_contents = system.GetFileSystemController().GetUserNANDContents(); |
@@ -264,7 +265,7 @@ inline std::vector<std::string> VerifyInstalledContents( | |||
264 | if (bis_contents) { | 265 | if (bis_contents) { |
265 | content_providers.push_back(bis_contents); | 266 | content_providers.push_back(bis_contents); |
266 | } | 267 | } |
267 | if (user_contents) { | 268 | if (user_contents && !firmware_only) { |
268 | content_providers.push_back(user_contents); | 269 | content_providers.push_back(user_contents); |
269 | } | 270 | } |
270 | 271 | ||
diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp index 819460eb5..3fa06d188 100755 --- a/src/hid_core/frontend/emulated_controller.cpp +++ b/src/hid_core/frontend/emulated_controller.cpp | |||
@@ -176,16 +176,19 @@ void EmulatedController::LoadDevices() { | |||
176 | camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; | 176 | camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; |
177 | ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; | 177 | ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; |
178 | nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; | 178 | nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; |
179 | android_params = Common::ParamPackage{"engine:android,port:100"}; | ||
179 | } | 180 | } |
180 | 181 | ||
181 | output_params[LeftIndex] = left_joycon; | 182 | output_params[LeftIndex] = left_joycon; |
182 | output_params[RightIndex] = right_joycon; | 183 | output_params[RightIndex] = right_joycon; |
183 | output_params[2] = camera_params[1]; | 184 | output_params[2] = camera_params[1]; |
184 | output_params[3] = nfc_params[0]; | 185 | output_params[3] = nfc_params[0]; |
186 | output_params[4] = android_params; | ||
185 | output_params[LeftIndex].Set("output", true); | 187 | output_params[LeftIndex].Set("output", true); |
186 | output_params[RightIndex].Set("output", true); | 188 | output_params[RightIndex].Set("output", true); |
187 | output_params[2].Set("output", true); | 189 | output_params[2].Set("output", true); |
188 | output_params[3].Set("output", true); | 190 | output_params[3].Set("output", true); |
191 | output_params[4].Set("output", true); | ||
189 | 192 | ||
190 | LoadTASParams(); | 193 | LoadTASParams(); |
191 | LoadVirtualGamepadParams(); | 194 | LoadVirtualGamepadParams(); |
@@ -578,6 +581,9 @@ void EmulatedController::DisableConfiguration() { | |||
578 | 581 | ||
579 | // Get Joycon colors before turning on the controller | 582 | // Get Joycon colors before turning on the controller |
580 | for (const auto& color_device : color_devices) { | 583 | for (const auto& color_device : color_devices) { |
584 | if (color_device == nullptr) { | ||
585 | continue; | ||
586 | } | ||
581 | color_device->ForceUpdate(); | 587 | color_device->ForceUpdate(); |
582 | } | 588 | } |
583 | 589 | ||
@@ -1277,6 +1283,10 @@ bool EmulatedController::SetVibration(DeviceIndex device_index, const VibrationV | |||
1277 | .high_frequency = vibration.high_frequency, | 1283 | .high_frequency = vibration.high_frequency, |
1278 | .type = type, | 1284 | .type = type, |
1279 | }; | 1285 | }; |
1286 | |||
1287 | // Send vibrations to Android's input overlay | ||
1288 | output_devices[4]->SetVibration(status); | ||
1289 | |||
1280 | return output_devices[index]->SetVibration(status) == Common::Input::DriverResult::Success; | 1290 | return output_devices[index]->SetVibration(status) == Common::Input::DriverResult::Success; |
1281 | } | 1291 | } |
1282 | 1292 | ||
diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index 701b38300..ab3c6fcd3 100755 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h | |||
@@ -21,7 +21,7 @@ | |||
21 | 21 | ||
22 | namespace Core::HID { | 22 | namespace Core::HID { |
23 | const std::size_t max_emulated_controllers = 2; | 23 | const std::size_t max_emulated_controllers = 2; |
24 | const std::size_t output_devices_size = 4; | 24 | const std::size_t output_devices_size = 5; |
25 | struct ControllerMotionInfo { | 25 | struct ControllerMotionInfo { |
26 | Common::Input::MotionStatus raw_status{}; | 26 | Common::Input::MotionStatus raw_status{}; |
27 | MotionInput emulated{}; | 27 | MotionInput emulated{}; |
@@ -597,6 +597,7 @@ private: | |||
597 | CameraParams camera_params; | 597 | CameraParams camera_params; |
598 | RingAnalogParams ring_params; | 598 | RingAnalogParams ring_params; |
599 | NfcParams nfc_params; | 599 | NfcParams nfc_params; |
600 | Common::ParamPackage android_params; | ||
600 | OutputParams output_params; | 601 | OutputParams output_params; |
601 | 602 | ||
602 | ButtonDevices button_devices; | 603 | ButtonDevices button_devices; |
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 0bc65262b..3c0e6f98d 100755 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt | |||
@@ -2,8 +2,6 @@ | |||
2 | # SPDX-License-Identifier: GPL-2.0-or-later | 2 | # SPDX-License-Identifier: GPL-2.0-or-later |
3 | 3 | ||
4 | add_library(input_common STATIC | 4 | add_library(input_common STATIC |
5 | drivers/android.cpp | ||
6 | drivers/android.h | ||
7 | drivers/camera.cpp | 5 | drivers/camera.cpp |
8 | drivers/camera.h | 6 | drivers/camera.h |
9 | drivers/keyboard.cpp | 7 | drivers/keyboard.cpp |
@@ -94,3 +92,11 @@ target_link_libraries(input_common PUBLIC hid_core PRIVATE common Boost::headers | |||
94 | if (YUZU_USE_PRECOMPILED_HEADERS) | 92 | if (YUZU_USE_PRECOMPILED_HEADERS) |
95 | target_precompile_headers(input_common PRIVATE precompiled_headers.h) | 93 | target_precompile_headers(input_common PRIVATE precompiled_headers.h) |
96 | endif() | 94 | endif() |
95 | |||
96 | if (ANDROID) | ||
97 | target_sources(input_common PRIVATE | ||
98 | drivers/android.cpp | ||
99 | drivers/android.h | ||
100 | ) | ||
101 | target_link_libraries(input_common PRIVATE android) | ||
102 | endif() | ||
diff --git a/src/input_common/drivers/android.cpp b/src/input_common/drivers/android.cpp index b6a03fdc0..e859cc538 100755 --- a/src/input_common/drivers/android.cpp +++ b/src/input_common/drivers/android.cpp | |||
@@ -1,30 +1,47 @@ | |||
1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
3 | 3 | ||
4 | #include <set> | ||
5 | #include <common/settings_input.h> | ||
6 | #include <jni.h> | ||
7 | #include "common/android/android_common.h" | ||
8 | #include "common/android/id_cache.h" | ||
4 | #include "input_common/drivers/android.h" | 9 | #include "input_common/drivers/android.h" |
5 | 10 | ||
6 | namespace InputCommon { | 11 | namespace InputCommon { |
7 | 12 | ||
8 | Android::Android(std::string input_engine_) : InputEngine(std::move(input_engine_)) {} | 13 | Android::Android(std::string input_engine_) : InputEngine(std::move(input_engine_)) {} |
9 | 14 | ||
10 | void Android::RegisterController(std::size_t controller_number) { | 15 | void Android::RegisterController(jobject j_input_device) { |
11 | PreSetController(GetIdentifier(controller_number)); | 16 | auto env = Common::Android::GetEnvForThread(); |
17 | const std::string guid = Common::Android::GetJString( | ||
18 | env, static_cast<jstring>( | ||
19 | env->CallObjectMethod(j_input_device, Common::Android::GetYuzuDeviceGetGUID()))); | ||
20 | const s32 port = env->CallIntMethod(j_input_device, Common::Android::GetYuzuDeviceGetPort()); | ||
21 | const auto identifier = GetIdentifier(guid, static_cast<size_t>(port)); | ||
22 | PreSetController(identifier); | ||
23 | |||
24 | if (input_devices.find(identifier) != input_devices.end()) { | ||
25 | env->DeleteGlobalRef(input_devices[identifier]); | ||
26 | } | ||
27 | auto new_device = env->NewGlobalRef(j_input_device); | ||
28 | input_devices[identifier] = new_device; | ||
12 | } | 29 | } |
13 | 30 | ||
14 | void Android::SetButtonState(std::size_t controller_number, int button_id, bool value) { | 31 | void Android::SetButtonState(std::string guid, size_t port, int button_id, bool value) { |
15 | const auto identifier = GetIdentifier(controller_number); | 32 | const auto identifier = GetIdentifier(guid, port); |
16 | SetButton(identifier, button_id, value); | 33 | SetButton(identifier, button_id, value); |
17 | } | 34 | } |
18 | 35 | ||
19 | void Android::SetAxisState(std::size_t controller_number, int axis_id, float value) { | 36 | void Android::SetAxisPosition(std::string guid, size_t port, int axis_id, float value) { |
20 | const auto identifier = GetIdentifier(controller_number); | 37 | const auto identifier = GetIdentifier(guid, port); |
21 | SetAxis(identifier, axis_id, value); | 38 | SetAxis(identifier, axis_id, value); |
22 | } | 39 | } |
23 | 40 | ||
24 | void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x, | 41 | void Android::SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x, |
25 | float gyro_y, float gyro_z, float accel_x, float accel_y, | 42 | float gyro_y, float gyro_z, float accel_x, float accel_y, |
26 | float accel_z) { | 43 | float accel_z) { |
27 | const auto identifier = GetIdentifier(controller_number); | 44 | const auto identifier = GetIdentifier(guid, port); |
28 | const BasicMotion motion_data{ | 45 | const BasicMotion motion_data{ |
29 | .gyro_x = gyro_x, | 46 | .gyro_x = gyro_x, |
30 | .gyro_y = gyro_y, | 47 | .gyro_y = gyro_y, |
@@ -37,10 +54,295 @@ void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp, | |||
37 | SetMotion(identifier, 0, motion_data); | 54 | SetMotion(identifier, 0, motion_data); |
38 | } | 55 | } |
39 | 56 | ||
40 | PadIdentifier Android::GetIdentifier(std::size_t controller_number) const { | 57 | Common::Input::DriverResult Android::SetVibration( |
58 | [[maybe_unused]] const PadIdentifier& identifier, | ||
59 | [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { | ||
60 | auto device = input_devices.find(identifier); | ||
61 | if (device != input_devices.end()) { | ||
62 | Common::Android::RunJNIOnFiber<void>([&](JNIEnv* env) { | ||
63 | float average_intensity = | ||
64 | static_cast<float>((vibration.high_amplitude + vibration.low_amplitude) / 2.0); | ||
65 | env->CallVoidMethod(device->second, Common::Android::GetYuzuDeviceVibrate(), | ||
66 | average_intensity); | ||
67 | }); | ||
68 | return Common::Input::DriverResult::Success; | ||
69 | } | ||
70 | return Common::Input::DriverResult::NotSupported; | ||
71 | } | ||
72 | |||
73 | bool Android::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { | ||
74 | auto device = input_devices.find(identifier); | ||
75 | if (device != input_devices.end()) { | ||
76 | return Common::Android::RunJNIOnFiber<bool>([&](JNIEnv* env) { | ||
77 | return static_cast<bool>(env->CallBooleanMethod( | ||
78 | device->second, Common::Android::GetYuzuDeviceGetSupportsVibration())); | ||
79 | }); | ||
80 | } | ||
81 | return false; | ||
82 | } | ||
83 | |||
84 | std::vector<Common::ParamPackage> Android::GetInputDevices() const { | ||
85 | std::vector<Common::ParamPackage> devices; | ||
86 | auto env = Common::Android::GetEnvForThread(); | ||
87 | for (const auto& [key, value] : input_devices) { | ||
88 | auto name_object = static_cast<jstring>( | ||
89 | env->CallObjectMethod(value, Common::Android::GetYuzuDeviceGetName())); | ||
90 | const std::string name = | ||
91 | fmt::format("{} {}", Common::Android::GetJString(env, name_object), key.port); | ||
92 | devices.emplace_back(Common::ParamPackage{ | ||
93 | {"engine", GetEngineName()}, | ||
94 | {"display", std::move(name)}, | ||
95 | {"guid", key.guid.RawString()}, | ||
96 | {"port", std::to_string(key.port)}, | ||
97 | }); | ||
98 | } | ||
99 | return devices; | ||
100 | } | ||
101 | |||
102 | std::set<s32> Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const { | ||
103 | auto j_axes = static_cast<jobjectArray>( | ||
104 | env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceGetAxes())); | ||
105 | std::set<s32> axes; | ||
106 | for (int i = 0; i < env->GetArrayLength(j_axes); ++i) { | ||
107 | jobject axis = env->GetObjectArrayElement(j_axes, i); | ||
108 | axes.insert(env->GetIntField(axis, Common::Android::GetIntegerValueField())); | ||
109 | } | ||
110 | return axes; | ||
111 | } | ||
112 | |||
113 | Common::ParamPackage Android::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, | ||
114 | int axis_y) const { | ||
115 | Common::ParamPackage params; | ||
116 | params.Set("engine", GetEngineName()); | ||
117 | params.Set("port", static_cast<int>(identifier.port)); | ||
118 | params.Set("guid", identifier.guid.RawString()); | ||
119 | params.Set("axis_x", axis_x); | ||
120 | params.Set("axis_y", axis_y); | ||
121 | params.Set("offset_x", 0); | ||
122 | params.Set("offset_y", 0); | ||
123 | params.Set("invert_x", "+"); | ||
124 | |||
125 | // Invert Y-Axis by default | ||
126 | params.Set("invert_y", "-"); | ||
127 | return params; | ||
128 | } | ||
129 | |||
130 | Common::ParamPackage Android::BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis, | ||
131 | bool invert) const { | ||
132 | Common::ParamPackage params{}; | ||
133 | params.Set("engine", GetEngineName()); | ||
134 | params.Set("port", static_cast<int>(identifier.port)); | ||
135 | params.Set("guid", identifier.guid.RawString()); | ||
136 | params.Set("axis", axis); | ||
137 | params.Set("threshold", "0.5"); | ||
138 | params.Set("invert", invert ? "-" : "+"); | ||
139 | return params; | ||
140 | } | ||
141 | |||
142 | Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier identifier, | ||
143 | s32 button) const { | ||
144 | Common::ParamPackage params{}; | ||
145 | params.Set("engine", GetEngineName()); | ||
146 | params.Set("port", static_cast<int>(identifier.port)); | ||
147 | params.Set("guid", identifier.guid.RawString()); | ||
148 | params.Set("button", button); | ||
149 | return params; | ||
150 | } | ||
151 | |||
152 | bool Android::MatchVID(Common::UUID device, const std::vector<std::string>& vids) const { | ||
153 | for (size_t i = 0; i < vids.size(); ++i) { | ||
154 | auto fucker = device.RawString(); | ||
155 | if (fucker.find(vids[i]) != std::string::npos) { | ||
156 | return true; | ||
157 | } | ||
158 | } | ||
159 | return false; | ||
160 | } | ||
161 | |||
162 | AnalogMapping Android::GetAnalogMappingForDevice(const Common::ParamPackage& params) { | ||
163 | if (!params.Has("guid") || !params.Has("port")) { | ||
164 | return {}; | ||
165 | } | ||
166 | |||
167 | auto identifier = | ||
168 | GetIdentifier(params.Get("guid", ""), static_cast<size_t>(params.Get("port", 0))); | ||
169 | auto& j_device = input_devices[identifier]; | ||
170 | if (j_device == nullptr) { | ||
171 | return {}; | ||
172 | } | ||
173 | |||
174 | auto env = Common::Android::GetEnvForThread(); | ||
175 | std::set<s32> axes = GetDeviceAxes(env, j_device); | ||
176 | if (axes.size() == 0) { | ||
177 | return {}; | ||
178 | } | ||
179 | |||
180 | AnalogMapping mapping = {}; | ||
181 | if (axes.find(AXIS_X) != axes.end() && axes.find(AXIS_Y) != axes.end()) { | ||
182 | mapping.insert_or_assign(Settings::NativeAnalog::LStick, | ||
183 | BuildParamPackageForAnalog(identifier, AXIS_X, AXIS_Y)); | ||
184 | } | ||
185 | |||
186 | if (axes.find(AXIS_RX) != axes.end() && axes.find(AXIS_RY) != axes.end()) { | ||
187 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, | ||
188 | BuildParamPackageForAnalog(identifier, AXIS_RX, AXIS_RY)); | ||
189 | } else if (axes.find(AXIS_Z) != axes.end() && axes.find(AXIS_RZ) != axes.end()) { | ||
190 | mapping.insert_or_assign(Settings::NativeAnalog::RStick, | ||
191 | BuildParamPackageForAnalog(identifier, AXIS_Z, AXIS_RZ)); | ||
192 | } | ||
193 | return mapping; | ||
194 | } | ||
195 | |||
196 | ButtonMapping Android::GetButtonMappingForDevice(const Common::ParamPackage& params) { | ||
197 | if (!params.Has("guid") || !params.Has("port")) { | ||
198 | return {}; | ||
199 | } | ||
200 | |||
201 | auto identifier = | ||
202 | GetIdentifier(params.Get("guid", ""), static_cast<size_t>(params.Get("port", 0))); | ||
203 | auto& j_device = input_devices[identifier]; | ||
204 | if (j_device == nullptr) { | ||
205 | return {}; | ||
206 | } | ||
207 | |||
208 | auto env = Common::Android::GetEnvForThread(); | ||
209 | jintArray j_keys = env->NewIntArray(static_cast<int>(keycode_ids.size())); | ||
210 | env->SetIntArrayRegion(j_keys, 0, static_cast<int>(keycode_ids.size()), keycode_ids.data()); | ||
211 | auto j_has_keys_object = static_cast<jbooleanArray>( | ||
212 | env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceHasKeys(), j_keys)); | ||
213 | jboolean isCopy = false; | ||
214 | jboolean* j_has_keys = env->GetBooleanArrayElements(j_has_keys_object, &isCopy); | ||
215 | |||
216 | std::set<s32> available_keys; | ||
217 | for (size_t i = 0; i < keycode_ids.size(); ++i) { | ||
218 | if (j_has_keys[i]) { | ||
219 | available_keys.insert(keycode_ids[i]); | ||
220 | } | ||
221 | } | ||
222 | |||
223 | // Some devices use axes instead of buttons for certain controls so we need all the axes here | ||
224 | std::set<s32> axes = GetDeviceAxes(env, j_device); | ||
225 | |||
226 | ButtonMapping mapping = {}; | ||
227 | if (axes.find(AXIS_HAT_X) != axes.end() && axes.find(AXIS_HAT_Y) != axes.end()) { | ||
228 | mapping.insert_or_assign(Settings::NativeButton::DUp, | ||
229 | BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, true)); | ||
230 | mapping.insert_or_assign(Settings::NativeButton::DDown, | ||
231 | BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, false)); | ||
232 | mapping.insert_or_assign(Settings::NativeButton::DLeft, | ||
233 | BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, true)); | ||
234 | mapping.insert_or_assign(Settings::NativeButton::DRight, | ||
235 | BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, false)); | ||
236 | } else if (available_keys.find(KEYCODE_DPAD_UP) != available_keys.end() && | ||
237 | available_keys.find(KEYCODE_DPAD_DOWN) != available_keys.end() && | ||
238 | available_keys.find(KEYCODE_DPAD_LEFT) != available_keys.end() && | ||
239 | available_keys.find(KEYCODE_DPAD_RIGHT) != available_keys.end()) { | ||
240 | mapping.insert_or_assign(Settings::NativeButton::DUp, | ||
241 | BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_UP)); | ||
242 | mapping.insert_or_assign(Settings::NativeButton::DDown, | ||
243 | BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_DOWN)); | ||
244 | mapping.insert_or_assign(Settings::NativeButton::DLeft, | ||
245 | BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_LEFT)); | ||
246 | mapping.insert_or_assign(Settings::NativeButton::DRight, | ||
247 | BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_RIGHT)); | ||
248 | } | ||
249 | |||
250 | if (axes.find(AXIS_LTRIGGER) != axes.end()) { | ||
251 | mapping.insert_or_assign(Settings::NativeButton::ZL, BuildAnalogParamPackageForButton( | ||
252 | identifier, AXIS_LTRIGGER, false)); | ||
253 | } else if (available_keys.find(KEYCODE_BUTTON_L2) != available_keys.end()) { | ||
254 | mapping.insert_or_assign(Settings::NativeButton::ZL, | ||
255 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L2)); | ||
256 | } | ||
257 | |||
258 | if (axes.find(AXIS_RTRIGGER) != axes.end()) { | ||
259 | mapping.insert_or_assign(Settings::NativeButton::ZR, BuildAnalogParamPackageForButton( | ||
260 | identifier, AXIS_RTRIGGER, false)); | ||
261 | } else if (available_keys.find(KEYCODE_BUTTON_R2) != available_keys.end()) { | ||
262 | mapping.insert_or_assign(Settings::NativeButton::ZR, | ||
263 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R2)); | ||
264 | } | ||
265 | |||
266 | if (available_keys.find(KEYCODE_BUTTON_A) != available_keys.end()) { | ||
267 | if (MatchVID(identifier.guid, flipped_ab_vids)) { | ||
268 | mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton( | ||
269 | identifier, KEYCODE_BUTTON_A)); | ||
270 | } else { | ||
271 | mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton( | ||
272 | identifier, KEYCODE_BUTTON_A)); | ||
273 | } | ||
274 | } | ||
275 | if (available_keys.find(KEYCODE_BUTTON_B) != available_keys.end()) { | ||
276 | if (MatchVID(identifier.guid, flipped_ab_vids)) { | ||
277 | mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton( | ||
278 | identifier, KEYCODE_BUTTON_B)); | ||
279 | } else { | ||
280 | mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton( | ||
281 | identifier, KEYCODE_BUTTON_B)); | ||
282 | } | ||
283 | } | ||
284 | if (available_keys.find(KEYCODE_BUTTON_X) != available_keys.end()) { | ||
285 | if (MatchVID(identifier.guid, flipped_xy_vids)) { | ||
286 | mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton( | ||
287 | identifier, KEYCODE_BUTTON_X)); | ||
288 | } else { | ||
289 | mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton( | ||
290 | identifier, KEYCODE_BUTTON_X)); | ||
291 | } | ||
292 | } | ||
293 | if (available_keys.find(KEYCODE_BUTTON_Y) != available_keys.end()) { | ||
294 | if (MatchVID(identifier.guid, flipped_xy_vids)) { | ||
295 | mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton( | ||
296 | identifier, KEYCODE_BUTTON_Y)); | ||
297 | } else { | ||
298 | mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton( | ||
299 | identifier, KEYCODE_BUTTON_Y)); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | if (available_keys.find(KEYCODE_BUTTON_L1) != available_keys.end()) { | ||
304 | mapping.insert_or_assign(Settings::NativeButton::L, | ||
305 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L1)); | ||
306 | } | ||
307 | if (available_keys.find(KEYCODE_BUTTON_R1) != available_keys.end()) { | ||
308 | mapping.insert_or_assign(Settings::NativeButton::R, | ||
309 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R1)); | ||
310 | } | ||
311 | |||
312 | if (available_keys.find(KEYCODE_BUTTON_THUMBL) != available_keys.end()) { | ||
313 | mapping.insert_or_assign( | ||
314 | Settings::NativeButton::LStick, | ||
315 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBL)); | ||
316 | } | ||
317 | if (available_keys.find(KEYCODE_BUTTON_THUMBR) != available_keys.end()) { | ||
318 | mapping.insert_or_assign( | ||
319 | Settings::NativeButton::RStick, | ||
320 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBR)); | ||
321 | } | ||
322 | |||
323 | if (available_keys.find(KEYCODE_BUTTON_START) != available_keys.end()) { | ||
324 | mapping.insert_or_assign( | ||
325 | Settings::NativeButton::Plus, | ||
326 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_START)); | ||
327 | } | ||
328 | if (available_keys.find(KEYCODE_BUTTON_SELECT) != available_keys.end()) { | ||
329 | mapping.insert_or_assign( | ||
330 | Settings::NativeButton::Minus, | ||
331 | BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_SELECT)); | ||
332 | } | ||
333 | |||
334 | return mapping; | ||
335 | } | ||
336 | |||
337 | Common::Input::ButtonNames Android::GetUIName( | ||
338 | [[maybe_unused]] const Common::ParamPackage& params) const { | ||
339 | return Common::Input::ButtonNames::Value; | ||
340 | } | ||
341 | |||
342 | PadIdentifier Android::GetIdentifier(const std::string& guid, size_t port) const { | ||
41 | return { | 343 | return { |
42 | .guid = Common::UUID{}, | 344 | .guid = Common::UUID{guid}, |
43 | .port = controller_number, | 345 | .port = port, |
44 | .pad = 0, | 346 | .pad = 0, |
45 | }; | 347 | }; |
46 | } | 348 | } |
diff --git a/src/input_common/drivers/android.h b/src/input_common/drivers/android.h index 3f01817f6..ac60e3598 100755 --- a/src/input_common/drivers/android.h +++ b/src/input_common/drivers/android.h | |||
@@ -3,6 +3,8 @@ | |||
3 | 3 | ||
4 | #pragma once | 4 | #pragma once |
5 | 5 | ||
6 | #include <set> | ||
7 | #include <jni.h> | ||
6 | #include "input_common/input_engine.h" | 8 | #include "input_common/input_engine.h" |
7 | 9 | ||
8 | namespace InputCommon { | 10 | namespace InputCommon { |
@@ -15,40 +17,121 @@ public: | |||
15 | explicit Android(std::string input_engine_); | 17 | explicit Android(std::string input_engine_); |
16 | 18 | ||
17 | /** | 19 | /** |
18 | * Registers controller number to accept new inputs | 20 | * Registers controller number to accept new inputs. |
19 | * @param controller_number the controller number that will take this action | 21 | * @param j_input_device YuzuInputDevice object from the Android frontend to register. |
20 | */ | 22 | */ |
21 | void RegisterController(std::size_t controller_number); | 23 | void RegisterController(jobject j_input_device); |
22 | 24 | ||
23 | /** | 25 | /** |
24 | * Sets the status of all buttons bound with the key to pressed | 26 | * Sets the status of a button on a specific controller. |
25 | * @param controller_number the controller number that will take this action | 27 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. |
26 | * @param button_id the id of the button | 28 | * @param port Port determined by controller connection order. |
27 | * @param value indicates if the button is pressed or not | 29 | * @param button_id The Android Keycode corresponding to this event. |
30 | * @param value Whether the button is pressed or not. | ||
28 | */ | 31 | */ |
29 | void SetButtonState(std::size_t controller_number, int button_id, bool value); | 32 | void SetButtonState(std::string guid, size_t port, int button_id, bool value); |
30 | 33 | ||
31 | /** | 34 | /** |
32 | * Sets the status of a analog input to a specific player index | 35 | * Sets the status of an axis on a specific controller. |
33 | * @param controller_number the controller number that will take this action | 36 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. |
34 | * @param axis_id the id of the axis to move | 37 | * @param port Port determined by controller connection order. |
35 | * @param value the analog position of the axis | 38 | * @param axis_id The Android axis ID corresponding to this event. |
39 | * @param value Value along the given axis. | ||
36 | */ | 40 | */ |
37 | void SetAxisState(std::size_t controller_number, int axis_id, float value); | 41 | void SetAxisPosition(std::string guid, size_t port, int axis_id, float value); |
38 | 42 | ||
39 | /** | 43 | /** |
40 | * Sets the status of the motion sensor to a specific player index | 44 | * Sets the status of the motion sensor on a specific controller |
41 | * @param controller_number the controller number that will take this action | 45 | * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. |
42 | * @param delta_timestamp time passed since last reading | 46 | * @param port Port determined by controller connection order. |
43 | * @param gyro_x,gyro_y,gyro_z the gyro sensor readings | 47 | * @param delta_timestamp Time passed since the last read. |
44 | * @param accel_x,accel_y,accel_z the accelerometer reading | 48 | * @param gyro_x,gyro_y,gyro_z Gyro sensor readings. |
49 | * @param accel_x,accel_y,accel_z Accelerometer sensor readings. | ||
45 | */ | 50 | */ |
46 | void SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x, | 51 | void SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x, |
47 | float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z); | 52 | float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z); |
48 | 53 | ||
54 | Common::Input::DriverResult SetVibration( | ||
55 | const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; | ||
56 | |||
57 | bool IsVibrationEnabled(const PadIdentifier& identifier) override; | ||
58 | |||
59 | std::vector<Common::ParamPackage> GetInputDevices() const override; | ||
60 | |||
61 | /** | ||
62 | * Gets the axes reported by the YuzuInputDevice. | ||
63 | * @param env JNI environment pointer. | ||
64 | * @param j_device YuzuInputDevice from the Android frontend. | ||
65 | * @return Set of the axes reported by the underlying Android InputDevice | ||
66 | */ | ||
67 | std::set<s32> GetDeviceAxes(JNIEnv* env, jobject& j_device) const; | ||
68 | |||
69 | Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, | ||
70 | int axis_y) const; | ||
71 | |||
72 | Common::ParamPackage BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis, | ||
73 | bool invert) const; | ||
74 | |||
75 | Common::ParamPackage BuildButtonParamPackageForButton(PadIdentifier identifier, | ||
76 | s32 button) const; | ||
77 | |||
78 | bool MatchVID(Common::UUID device, const std::vector<std::string>& vids) const; | ||
79 | |||
80 | AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; | ||
81 | |||
82 | ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; | ||
83 | |||
84 | Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; | ||
85 | |||
49 | private: | 86 | private: |
87 | std::unordered_map<PadIdentifier, jobject> input_devices; | ||
88 | |||
50 | /// Returns the correct identifier corresponding to the player index | 89 | /// Returns the correct identifier corresponding to the player index |
51 | PadIdentifier GetIdentifier(std::size_t controller_number) const; | 90 | PadIdentifier GetIdentifier(const std::string& guid, size_t port) const; |
91 | |||
92 | static constexpr s32 AXIS_X = 0; | ||
93 | static constexpr s32 AXIS_Y = 1; | ||
94 | static constexpr s32 AXIS_Z = 11; | ||
95 | static constexpr s32 AXIS_RX = 12; | ||
96 | static constexpr s32 AXIS_RY = 13; | ||
97 | static constexpr s32 AXIS_RZ = 14; | ||
98 | static constexpr s32 AXIS_HAT_X = 15; | ||
99 | static constexpr s32 AXIS_HAT_Y = 16; | ||
100 | static constexpr s32 AXIS_LTRIGGER = 17; | ||
101 | static constexpr s32 AXIS_RTRIGGER = 18; | ||
102 | |||
103 | static constexpr s32 KEYCODE_DPAD_UP = 19; | ||
104 | static constexpr s32 KEYCODE_DPAD_DOWN = 20; | ||
105 | static constexpr s32 KEYCODE_DPAD_LEFT = 21; | ||
106 | static constexpr s32 KEYCODE_DPAD_RIGHT = 22; | ||
107 | static constexpr s32 KEYCODE_BUTTON_A = 96; | ||
108 | static constexpr s32 KEYCODE_BUTTON_B = 97; | ||
109 | static constexpr s32 KEYCODE_BUTTON_X = 99; | ||
110 | static constexpr s32 KEYCODE_BUTTON_Y = 100; | ||
111 | static constexpr s32 KEYCODE_BUTTON_L1 = 102; | ||
112 | static constexpr s32 KEYCODE_BUTTON_R1 = 103; | ||
113 | static constexpr s32 KEYCODE_BUTTON_L2 = 104; | ||
114 | static constexpr s32 KEYCODE_BUTTON_R2 = 105; | ||
115 | static constexpr s32 KEYCODE_BUTTON_THUMBL = 106; | ||
116 | static constexpr s32 KEYCODE_BUTTON_THUMBR = 107; | ||
117 | static constexpr s32 KEYCODE_BUTTON_START = 108; | ||
118 | static constexpr s32 KEYCODE_BUTTON_SELECT = 109; | ||
119 | const std::vector<s32> keycode_ids{ | ||
120 | KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, | ||
121 | KEYCODE_BUTTON_A, KEYCODE_BUTTON_B, KEYCODE_BUTTON_X, KEYCODE_BUTTON_Y, | ||
122 | KEYCODE_BUTTON_L1, KEYCODE_BUTTON_R1, KEYCODE_BUTTON_L2, KEYCODE_BUTTON_R2, | ||
123 | KEYCODE_BUTTON_THUMBL, KEYCODE_BUTTON_THUMBR, KEYCODE_BUTTON_START, KEYCODE_BUTTON_SELECT, | ||
124 | }; | ||
125 | |||
126 | const std::string sony_vid{"054c"}; | ||
127 | const std::string nintendo_vid{"057e"}; | ||
128 | const std::string razer_vid{"1532"}; | ||
129 | const std::string redmagic_vid{"3537"}; | ||
130 | const std::string backbone_labs_vid{"358a"}; | ||
131 | const std::vector<std::string> flipped_ab_vids{sony_vid, nintendo_vid, razer_vid, redmagic_vid, | ||
132 | backbone_labs_vid}; | ||
133 | const std::vector<std::string> flipped_xy_vids{sony_vid, razer_vid, redmagic_vid, | ||
134 | backbone_labs_vid}; | ||
52 | }; | 135 | }; |
53 | 136 | ||
54 | } // namespace InputCommon | 137 | } // namespace InputCommon |
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index a40fdccc5..96aac78fe 100755 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp | |||
@@ -4,7 +4,6 @@ | |||
4 | #include <memory> | 4 | #include <memory> |
5 | #include "common/input.h" | 5 | #include "common/input.h" |
6 | #include "common/param_package.h" | 6 | #include "common/param_package.h" |
7 | #include "input_common/drivers/android.h" | ||
8 | #include "input_common/drivers/camera.h" | 7 | #include "input_common/drivers/camera.h" |
9 | #include "input_common/drivers/keyboard.h" | 8 | #include "input_common/drivers/keyboard.h" |
10 | #include "input_common/drivers/mouse.h" | 9 | #include "input_common/drivers/mouse.h" |
@@ -28,6 +27,10 @@ | |||
28 | #include "input_common/drivers/sdl_driver.h" | 27 | #include "input_common/drivers/sdl_driver.h" |
29 | #endif | 28 | #endif |
30 | 29 | ||
30 | #ifdef ANDROID | ||
31 | #include "input_common/drivers/android.h" | ||
32 | #endif | ||
33 | |||
31 | namespace InputCommon { | 34 | namespace InputCommon { |
32 | 35 | ||
33 | /// Dummy engine to get periodic updates | 36 | /// Dummy engine to get periodic updates |
@@ -79,7 +82,9 @@ struct InputSubsystem::Impl { | |||
79 | RegisterEngine("cemuhookudp", udp_client); | 82 | RegisterEngine("cemuhookudp", udp_client); |
80 | RegisterEngine("tas", tas_input); | 83 | RegisterEngine("tas", tas_input); |
81 | RegisterEngine("camera", camera); | 84 | RegisterEngine("camera", camera); |
85 | #ifdef ANDROID | ||
82 | RegisterEngine("android", android); | 86 | RegisterEngine("android", android); |
87 | #endif | ||
83 | RegisterEngine("virtual_amiibo", virtual_amiibo); | 88 | RegisterEngine("virtual_amiibo", virtual_amiibo); |
84 | RegisterEngine("virtual_gamepad", virtual_gamepad); | 89 | RegisterEngine("virtual_gamepad", virtual_gamepad); |
85 | #ifdef HAVE_SDL2 | 90 | #ifdef HAVE_SDL2 |
@@ -111,7 +116,9 @@ struct InputSubsystem::Impl { | |||
111 | UnregisterEngine(udp_client); | 116 | UnregisterEngine(udp_client); |
112 | UnregisterEngine(tas_input); | 117 | UnregisterEngine(tas_input); |
113 | UnregisterEngine(camera); | 118 | UnregisterEngine(camera); |
119 | #ifdef ANDROID | ||
114 | UnregisterEngine(android); | 120 | UnregisterEngine(android); |
121 | #endif | ||
115 | UnregisterEngine(virtual_amiibo); | 122 | UnregisterEngine(virtual_amiibo); |
116 | UnregisterEngine(virtual_gamepad); | 123 | UnregisterEngine(virtual_gamepad); |
117 | #ifdef HAVE_SDL2 | 124 | #ifdef HAVE_SDL2 |
@@ -128,12 +135,16 @@ struct InputSubsystem::Impl { | |||
128 | Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, | 135 | Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, |
129 | }; | 136 | }; |
130 | 137 | ||
138 | #ifndef ANDROID | ||
131 | auto keyboard_devices = keyboard->GetInputDevices(); | 139 | auto keyboard_devices = keyboard->GetInputDevices(); |
132 | devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); | 140 | devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); |
133 | auto mouse_devices = mouse->GetInputDevices(); | 141 | auto mouse_devices = mouse->GetInputDevices(); |
134 | devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); | 142 | devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); |
143 | #endif | ||
144 | #ifdef ANDROID | ||
135 | auto android_devices = android->GetInputDevices(); | 145 | auto android_devices = android->GetInputDevices(); |
136 | devices.insert(devices.end(), android_devices.begin(), android_devices.end()); | 146 | devices.insert(devices.end(), android_devices.begin(), android_devices.end()); |
147 | #endif | ||
137 | #ifdef HAVE_LIBUSB | 148 | #ifdef HAVE_LIBUSB |
138 | auto gcadapter_devices = gcadapter->GetInputDevices(); | 149 | auto gcadapter_devices = gcadapter->GetInputDevices(); |
139 | devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end()); | 150 | devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end()); |
@@ -162,9 +173,11 @@ struct InputSubsystem::Impl { | |||
162 | if (engine == mouse->GetEngineName()) { | 173 | if (engine == mouse->GetEngineName()) { |
163 | return mouse; | 174 | return mouse; |
164 | } | 175 | } |
176 | #ifdef ANDROID | ||
165 | if (engine == android->GetEngineName()) { | 177 | if (engine == android->GetEngineName()) { |
166 | return android; | 178 | return android; |
167 | } | 179 | } |
180 | #endif | ||
168 | #ifdef HAVE_LIBUSB | 181 | #ifdef HAVE_LIBUSB |
169 | if (engine == gcadapter->GetEngineName()) { | 182 | if (engine == gcadapter->GetEngineName()) { |
170 | return gcadapter; | 183 | return gcadapter; |
@@ -245,9 +258,11 @@ struct InputSubsystem::Impl { | |||
245 | if (engine == mouse->GetEngineName()) { | 258 | if (engine == mouse->GetEngineName()) { |
246 | return true; | 259 | return true; |
247 | } | 260 | } |
261 | #ifdef ANDROID | ||
248 | if (engine == android->GetEngineName()) { | 262 | if (engine == android->GetEngineName()) { |
249 | return true; | 263 | return true; |
250 | } | 264 | } |
265 | #endif | ||
251 | #ifdef HAVE_LIBUSB | 266 | #ifdef HAVE_LIBUSB |
252 | if (engine == gcadapter->GetEngineName()) { | 267 | if (engine == gcadapter->GetEngineName()) { |
253 | return true; | 268 | return true; |
@@ -276,7 +291,9 @@ struct InputSubsystem::Impl { | |||
276 | void BeginConfiguration() { | 291 | void BeginConfiguration() { |
277 | keyboard->BeginConfiguration(); | 292 | keyboard->BeginConfiguration(); |
278 | mouse->BeginConfiguration(); | 293 | mouse->BeginConfiguration(); |
294 | #ifdef ANDROID | ||
279 | android->BeginConfiguration(); | 295 | android->BeginConfiguration(); |
296 | #endif | ||
280 | #ifdef HAVE_LIBUSB | 297 | #ifdef HAVE_LIBUSB |
281 | gcadapter->BeginConfiguration(); | 298 | gcadapter->BeginConfiguration(); |
282 | #endif | 299 | #endif |
@@ -290,7 +307,9 @@ struct InputSubsystem::Impl { | |||
290 | void EndConfiguration() { | 307 | void EndConfiguration() { |
291 | keyboard->EndConfiguration(); | 308 | keyboard->EndConfiguration(); |
292 | mouse->EndConfiguration(); | 309 | mouse->EndConfiguration(); |
310 | #ifdef ANDROID | ||
293 | android->EndConfiguration(); | 311 | android->EndConfiguration(); |
312 | #endif | ||
294 | #ifdef HAVE_LIBUSB | 313 | #ifdef HAVE_LIBUSB |
295 | gcadapter->EndConfiguration(); | 314 | gcadapter->EndConfiguration(); |
296 | #endif | 315 | #endif |
@@ -321,7 +340,6 @@ struct InputSubsystem::Impl { | |||
321 | std::shared_ptr<TasInput::Tas> tas_input; | 340 | std::shared_ptr<TasInput::Tas> tas_input; |
322 | std::shared_ptr<CemuhookUDP::UDPClient> udp_client; | 341 | std::shared_ptr<CemuhookUDP::UDPClient> udp_client; |
323 | std::shared_ptr<Camera> camera; | 342 | std::shared_ptr<Camera> camera; |
324 | std::shared_ptr<Android> android; | ||
325 | std::shared_ptr<VirtualAmiibo> virtual_amiibo; | 343 | std::shared_ptr<VirtualAmiibo> virtual_amiibo; |
326 | std::shared_ptr<VirtualGamepad> virtual_gamepad; | 344 | std::shared_ptr<VirtualGamepad> virtual_gamepad; |
327 | 345 | ||
@@ -333,6 +351,10 @@ struct InputSubsystem::Impl { | |||
333 | std::shared_ptr<SDLDriver> sdl; | 351 | std::shared_ptr<SDLDriver> sdl; |
334 | std::shared_ptr<Joycons> joycon; | 352 | std::shared_ptr<Joycons> joycon; |
335 | #endif | 353 | #endif |
354 | |||
355 | #ifdef ANDROID | ||
356 | std::shared_ptr<Android> android; | ||
357 | #endif | ||
336 | }; | 358 | }; |
337 | 359 | ||
338 | InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} | 360 | InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} |
@@ -387,6 +409,7 @@ const Camera* InputSubsystem::GetCamera() const { | |||
387 | return impl->camera.get(); | 409 | return impl->camera.get(); |
388 | } | 410 | } |
389 | 411 | ||
412 | #ifdef ANDROID | ||
390 | Android* InputSubsystem::GetAndroid() { | 413 | Android* InputSubsystem::GetAndroid() { |
391 | return impl->android.get(); | 414 | return impl->android.get(); |
392 | } | 415 | } |
@@ -394,6 +417,7 @@ Android* InputSubsystem::GetAndroid() { | |||
394 | const Android* InputSubsystem::GetAndroid() const { | 417 | const Android* InputSubsystem::GetAndroid() const { |
395 | return impl->android.get(); | 418 | return impl->android.get(); |
396 | } | 419 | } |
420 | #endif | ||
397 | 421 | ||
398 | VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() { | 422 | VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() { |
399 | return impl->virtual_amiibo.get(); | 423 | return impl->virtual_amiibo.get(); |
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp index 1051031f2..37951b9c8 100755 --- a/src/yuzu/configuration/qt_config.cpp +++ b/src/yuzu/configuration/qt_config.cpp | |||
@@ -90,6 +90,7 @@ void QtConfig::ReadQtPlayerValues(const std::size_t player_index) { | |||
90 | if (profile_name.empty()) { | 90 | if (profile_name.empty()) { |
91 | // Use the global input config | 91 | // Use the global input config |
92 | player = Settings::values.players.GetValue(true)[player_index]; | 92 | player = Settings::values.players.GetValue(true)[player_index]; |
93 | player.profile_name = ""; | ||
93 | return; | 94 | return; |
94 | } | 95 | } |
95 | } | 96 | } |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 97814836f..5fcd9f9f3 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
@@ -1603,6 +1603,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
1603 | // Help | 1603 | // Help |
1604 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); | 1604 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); |
1605 | connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); | 1605 | connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); |
1606 | connect_menu(ui->action_Install_Firmware, &GMainWindow::OnInstallFirmware); | ||
1606 | connect_menu(ui->action_About, &GMainWindow::OnAbout); | 1607 | connect_menu(ui->action_About, &GMainWindow::OnAbout); |
1607 | } | 1608 | } |
1608 | 1609 | ||
@@ -1631,6 +1632,8 @@ void GMainWindow::UpdateMenuState() { | |||
1631 | action->setEnabled(emulation_running); | 1632 | action->setEnabled(emulation_running); |
1632 | } | 1633 | } |
1633 | 1634 | ||
1635 | ui->action_Install_Firmware->setEnabled(!emulation_running); | ||
1636 | |||
1634 | for (QAction* action : applet_actions) { | 1637 | for (QAction* action : applet_actions) { |
1635 | action->setEnabled(is_firmware_available && !emulation_running); | 1638 | action->setEnabled(is_firmware_available && !emulation_running); |
1636 | } | 1639 | } |
@@ -4150,6 +4153,146 @@ void GMainWindow::OnVerifyInstalledContents() { | |||
4150 | } | 4153 | } |
4151 | } | 4154 | } |
4152 | 4155 | ||
4156 | void GMainWindow::OnInstallFirmware() { | ||
4157 | // Don't do this while emulation is running, that'd probably be a bad idea. | ||
4158 | if (emu_thread != nullptr && emu_thread->IsRunning()) { | ||
4159 | return; | ||
4160 | } | ||
4161 | |||
4162 | // Check for installed keys, error out, suggest restart? | ||
4163 | if (!ContentManager::AreKeysPresent()) { | ||
4164 | QMessageBox::information( | ||
4165 | this, tr("Keys not installed"), | ||
4166 | tr("Install decryption keys and restart yuzu before attempting to install firmware.")); | ||
4167 | return; | ||
4168 | } | ||
4169 | |||
4170 | QString firmware_source_location = | ||
4171 | QFileDialog::getExistingDirectory(this, tr("Select Dumped Firmware Source Location"), | ||
4172 | QString::fromStdString(""), QFileDialog::ShowDirsOnly); | ||
4173 | if (firmware_source_location.isEmpty()) { | ||
4174 | return; | ||
4175 | } | ||
4176 | |||
4177 | QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); | ||
4178 | progress.setWindowModality(Qt::WindowModal); | ||
4179 | progress.setMinimumDuration(100); | ||
4180 | progress.setAutoClose(false); | ||
4181 | progress.setAutoReset(false); | ||
4182 | progress.show(); | ||
4183 | |||
4184 | // Declare progress callback. | ||
4185 | auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { | ||
4186 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||
4187 | return progress.wasCanceled(); | ||
4188 | }; | ||
4189 | |||
4190 | LOG_INFO(Frontend, "Installing firmware from {}", firmware_source_location.toStdString()); | ||
4191 | |||
4192 | // Check for a reasonable number of .nca files (don't hardcode them, just see if there's some in | ||
4193 | // there.) | ||
4194 | std::filesystem::path firmware_source_path = firmware_source_location.toStdString(); | ||
4195 | if (!Common::FS::IsDir(firmware_source_path)) { | ||
4196 | progress.close(); | ||
4197 | return; | ||
4198 | } | ||
4199 | |||
4200 | std::vector<std::filesystem::path> out; | ||
4201 | const Common::FS::DirEntryCallable callback = | ||
4202 | [&out](const std::filesystem::directory_entry& entry) { | ||
4203 | if (entry.path().has_extension() && entry.path().extension() == ".nca") | ||
4204 | out.emplace_back(entry.path()); | ||
4205 | |||
4206 | return true; | ||
4207 | }; | ||
4208 | |||
4209 | QtProgressCallback(100, 10); | ||
4210 | |||
4211 | Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); | ||
4212 | if (out.size() <= 0) { | ||
4213 | progress.close(); | ||
4214 | QMessageBox::warning(this, tr("Firmware install failed"), | ||
4215 | tr("Unable to locate potential firmware NCA files")); | ||
4216 | return; | ||
4217 | } | ||
4218 | |||
4219 | // Locate and erase the content of nand/system/Content/registered/*.nca, if any. | ||
4220 | auto sysnand_content_vdir = system->GetFileSystemController().GetSystemNANDContentDirectory(); | ||
4221 | if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { | ||
4222 | progress.close(); | ||
4223 | QMessageBox::critical(this, tr("Firmware install failed"), | ||
4224 | tr("Failed to delete one or more firmware file.")); | ||
4225 | return; | ||
4226 | } | ||
4227 | |||
4228 | LOG_INFO(Frontend, | ||
4229 | "Cleaned nand/system/Content/registered folder in preparation for new firmware."); | ||
4230 | |||
4231 | QtProgressCallback(100, 20); | ||
4232 | |||
4233 | auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); | ||
4234 | |||
4235 | bool success = true; | ||
4236 | bool cancelled = false; | ||
4237 | int i = 0; | ||
4238 | for (const auto& firmware_src_path : out) { | ||
4239 | i++; | ||
4240 | auto firmware_src_vfile = | ||
4241 | vfs->OpenFile(firmware_src_path.generic_string(), FileSys::OpenMode::Read); | ||
4242 | auto firmware_dst_vfile = | ||
4243 | firmware_vdir->CreateFileRelative(firmware_src_path.filename().string()); | ||
4244 | |||
4245 | if (!VfsRawCopy(firmware_src_vfile, firmware_dst_vfile)) { | ||
4246 | LOG_ERROR(Frontend, "Failed to copy firmware file {} to {} in registered folder!", | ||
4247 | firmware_src_path.generic_string(), firmware_src_path.filename().string()); | ||
4248 | success = false; | ||
4249 | } | ||
4250 | |||
4251 | if (QtProgressCallback(100, 20 + (int)(((float)(i) / (float)out.size()) * 70.0))) { | ||
4252 | success = false; | ||
4253 | cancelled = true; | ||
4254 | break; | ||
4255 | } | ||
4256 | } | ||
4257 | |||
4258 | if (!success && !cancelled) { | ||
4259 | progress.close(); | ||
4260 | QMessageBox::critical(this, tr("Firmware install failed"), | ||
4261 | tr("One or more firmware files failed to copy into NAND.")); | ||
4262 | return; | ||
4263 | } else if (cancelled) { | ||
4264 | progress.close(); | ||
4265 | QMessageBox::warning(this, tr("Firmware install failed"), | ||
4266 | tr("Firmware installation cancelled, firmware may be in bad state, " | ||
4267 | "restart yuzu or re-install firmware.")); | ||
4268 | return; | ||
4269 | } | ||
4270 | |||
4271 | // Re-scan VFS for the newly placed firmware files. | ||
4272 | system->GetFileSystemController().CreateFactories(*vfs); | ||
4273 | |||
4274 | auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { | ||
4275 | progress.setValue(90 + static_cast<int>((processed_size * 10) / total_size)); | ||
4276 | return progress.wasCanceled(); | ||
4277 | }; | ||
4278 | |||
4279 | auto result = | ||
4280 | ContentManager::VerifyInstalledContents(*system, *provider, VerifyFirmwareCallback, true); | ||
4281 | |||
4282 | if (result.size() > 0) { | ||
4283 | const auto failed_names = | ||
4284 | QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); | ||
4285 | progress.close(); | ||
4286 | QMessageBox::critical( | ||
4287 | this, tr("Firmware integrity verification failed!"), | ||
4288 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | ||
4289 | return; | ||
4290 | } | ||
4291 | |||
4292 | progress.close(); | ||
4293 | OnCheckFirmwareDecryption(); | ||
4294 | } | ||
4295 | |||
4153 | void GMainWindow::OnAbout() { | 4296 | void GMainWindow::OnAbout() { |
4154 | AboutDialog aboutDialog(this); | 4297 | AboutDialog aboutDialog(this); |
4155 | aboutDialog.exec(); | 4298 | aboutDialog.exec(); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index f1376e3a3..8510f0035 100755 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
@@ -380,6 +380,7 @@ private slots: | |||
380 | void OnLoadAmiibo(); | 380 | void OnLoadAmiibo(); |
381 | void OnOpenYuzuFolder(); | 381 | void OnOpenYuzuFolder(); |
382 | void OnVerifyInstalledContents(); | 382 | void OnVerifyInstalledContents(); |
383 | void OnInstallFirmware(); | ||
383 | void OnAbout(); | 384 | void OnAbout(); |
384 | void OnToggleFilterBar(); | 385 | void OnToggleFilterBar(); |
385 | void OnToggleStatusBar(); | 386 | void OnToggleStatusBar(); |
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 7a5d1e10d..656c3e8ef 100755 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
@@ -25,7 +25,16 @@ | |||
25 | </property> | 25 | </property> |
26 | <widget class="QWidget" name="centralwidget"> | 26 | <widget class="QWidget" name="centralwidget"> |
27 | <layout class="QHBoxLayout" name="horizontalLayout"> | 27 | <layout class="QHBoxLayout" name="horizontalLayout"> |
28 | <property name="margin" stdset="0"> | 28 | <property name="leftMargin"> |
29 | <number>0</number> | ||
30 | </property> | ||
31 | <property name="topMargin"> | ||
32 | <number>0</number> | ||
33 | </property> | ||
34 | <property name="rightMargin"> | ||
35 | <number>0</number> | ||
36 | </property> | ||
37 | <property name="bottomMargin"> | ||
29 | <number>0</number> | 38 | <number>0</number> |
30 | </property> | 39 | </property> |
31 | </layout> | 40 | </layout> |
@@ -156,8 +165,8 @@ | |||
156 | <addaction name="separator"/> | 165 | <addaction name="separator"/> |
157 | <addaction name="action_Configure_Tas"/> | 166 | <addaction name="action_Configure_Tas"/> |
158 | </widget> | 167 | </widget> |
159 | <addaction name="action_Rederive"/> | ||
160 | <addaction name="action_Verify_installed_contents"/> | 168 | <addaction name="action_Verify_installed_contents"/> |
169 | <addaction name="action_Install_Firmware"/> | ||
161 | <addaction name="separator"/> | 170 | <addaction name="separator"/> |
162 | <addaction name="menu_cabinet_applet"/> | 171 | <addaction name="menu_cabinet_applet"/> |
163 | <addaction name="action_Load_Album"/> | 172 | <addaction name="action_Load_Album"/> |
@@ -455,6 +464,11 @@ | |||
455 | <string>Open &Controller Menu</string> | 464 | <string>Open &Controller Menu</string> |
456 | </property> | 465 | </property> |
457 | </action> | 466 | </action> |
467 | <action name="action_Install_Firmware"> | ||
468 | <property name="text"> | ||
469 | <string>Install Firmware</string> | ||
470 | </property> | ||
471 | </action> | ||
458 | </widget> | 472 | </widget> |
459 | <resources> | 473 | <resources> |
460 | <include location="yuzu.qrc"/> | 474 | <include location="yuzu.qrc"/> |
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp index 995114510..6e0f254b6 100755 --- a/src/yuzu_cmd/sdl_config.cpp +++ b/src/yuzu_cmd/sdl_config.cpp | |||
@@ -103,6 +103,7 @@ void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) { | |||
103 | if (profile_name.empty()) { | 103 | if (profile_name.empty()) { |
104 | // Use the global input config | 104 | // Use the global input config |
105 | player = Settings::values.players.GetValue(true)[player_index]; | 105 | player = Settings::values.players.GetValue(true)[player_index]; |
106 | player.profile_name = ""; | ||
106 | return; | 107 | return; |
107 | } | 108 | } |
108 | } | 109 | } |