Compare commits
12 Commits
master
...
home_menu4
Author | SHA1 | Date | |
---|---|---|---|
c54b12181d | |||
26c0ba122f | |||
905356ccfc | |||
09ababeaad | |||
e58881cb9c | |||
f5bf73069d | |||
8e3cd0a5b0 | |||
10e54243e3 | |||
9052a9b8ee | |||
e2fe0c49b2 | |||
39af5495f4 | |||
c3db1c4681 |
3
.github/workflows/verify.yml
vendored
3
.github/workflows/verify.yml
vendored
@ -81,7 +81,8 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
brew install autoconf automake boost ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd
|
# workaround for https://github.com/actions/setup-python/issues/577
|
||||||
|
brew install autoconf automake boost@1.83 ccache ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@5 sdl2 speexdsp zlib zlib zstd || brew link --overwrite python@3.12
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
|
@ -121,7 +121,6 @@ else()
|
|||||||
-Wno-attributes
|
-Wno-attributes
|
||||||
-Wno-invalid-offsetof
|
-Wno-invalid-offsetof
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
-Wno-missing-field-initializers
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
|
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
|
||||||
|
@ -14,7 +14,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
||||||
|
@ -3,21 +3,24 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu
|
package org.yuzu.yuzu_emu
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment
|
|
||||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||||
import org.yuzu.yuzu_emu.model.InstallResult
|
import org.yuzu.yuzu_emu.model.InstallResult
|
||||||
import org.yuzu.yuzu_emu.model.Patch
|
import org.yuzu.yuzu_emu.model.Patch
|
||||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||||
@ -27,6 +30,34 @@ import org.yuzu.yuzu_emu.model.GameVerificationResult
|
|||||||
* with the native side of the Yuzu code.
|
* with the native side of the Yuzu code.
|
||||||
*/
|
*/
|
||||||
object NativeLibrary {
|
object NativeLibrary {
|
||||||
|
/**
|
||||||
|
* Default controller id for each device
|
||||||
|
*/
|
||||||
|
const val Player1Device = 0
|
||||||
|
const val Player2Device = 1
|
||||||
|
const val Player3Device = 2
|
||||||
|
const val Player4Device = 3
|
||||||
|
const val Player5Device = 4
|
||||||
|
const val Player6Device = 5
|
||||||
|
const val Player7Device = 6
|
||||||
|
const val Player8Device = 7
|
||||||
|
const val ConsoleDevice = 8
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller type for each device
|
||||||
|
*/
|
||||||
|
const val ProController = 3
|
||||||
|
const val Handheld = 4
|
||||||
|
const val JoyconDual = 5
|
||||||
|
const val JoyconLeft = 6
|
||||||
|
const val JoyconRight = 7
|
||||||
|
const val GameCube = 8
|
||||||
|
const val Pokeball = 9
|
||||||
|
const val NES = 10
|
||||||
|
const val SNES = 11
|
||||||
|
const val N64 = 12
|
||||||
|
const val SegaGenesis = 13
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||||
|
|
||||||
@ -96,6 +127,112 @@ object NativeLibrary {
|
|||||||
FileUtil.getFilename(Uri.parse(path))
|
FileUtil.getFilename(Uri.parse(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if pro controller isn't available and handheld is
|
||||||
|
*/
|
||||||
|
external fun isHandheldOnly(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes controller type for a specific device.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
* @param Type The NpadStyleIndex of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun setDeviceType(Device: Int, Type: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles event when a gamepad is connected.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun onGamePadConnectEvent(Device: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles event when a gamepad is disconnected.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
*/
|
||||||
|
external fun onGamePadDisconnectEvent(Device: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles button press events for a gamepad.
|
||||||
|
*
|
||||||
|
* @param Device The input descriptor of the gamepad.
|
||||||
|
* @param Button Key code identifying which button was pressed.
|
||||||
|
* @param Action Mask identifying which action is happening (button pressed down, or button released).
|
||||||
|
* @return If we handled the button press.
|
||||||
|
*/
|
||||||
|
external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles joystick movement events.
|
||||||
|
*
|
||||||
|
* @param Device The device ID of the gamepad.
|
||||||
|
* @param Axis The axis ID
|
||||||
|
* @param x_axis The value of the x-axis represented by the given ID.
|
||||||
|
* @param y_axis The value of the y-axis represented by the given ID.
|
||||||
|
*/
|
||||||
|
external fun onGamePadJoystickEvent(
|
||||||
|
Device: Int,
|
||||||
|
Axis: Int,
|
||||||
|
x_axis: Float,
|
||||||
|
y_axis: Float
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles motion events.
|
||||||
|
*
|
||||||
|
* @param delta_timestamp The finger id corresponding to this event
|
||||||
|
* @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor.
|
||||||
|
* @param accel_x,accel_y,accel_z The value of the y-axis
|
||||||
|
*/
|
||||||
|
external fun onGamePadMotionEvent(
|
||||||
|
Device: Int,
|
||||||
|
delta_timestamp: Long,
|
||||||
|
gyro_x: Float,
|
||||||
|
gyro_y: Float,
|
||||||
|
gyro_z: Float,
|
||||||
|
accel_x: Float,
|
||||||
|
accel_y: Float,
|
||||||
|
accel_z: Float
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals and load a nfc tag
|
||||||
|
*
|
||||||
|
* @param data Byte array containing all the data from a nfc tag
|
||||||
|
*/
|
||||||
|
external fun onReadNfcTag(data: ByteArray?): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes current loaded nfc tag
|
||||||
|
*/
|
||||||
|
external fun onRemoveNfcTag(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch press events.
|
||||||
|
*
|
||||||
|
* @param finger_id The finger id corresponding to this event
|
||||||
|
* @param x_axis The value of the x-axis.
|
||||||
|
* @param y_axis The value of the y-axis.
|
||||||
|
*/
|
||||||
|
external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch movement.
|
||||||
|
*
|
||||||
|
* @param x_axis The value of the instantaneous x-axis.
|
||||||
|
* @param y_axis The value of the instantaneous y-axis.
|
||||||
|
*/
|
||||||
|
external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles touch release events.
|
||||||
|
*
|
||||||
|
* @param finger_id The finger id corresponding to this event
|
||||||
|
*/
|
||||||
|
external fun onTouchReleased(finger_id: Int)
|
||||||
|
|
||||||
external fun setAppDirectory(directory: String)
|
external fun setAppDirectory(directory: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -181,13 +318,46 @@ object NativeLibrary {
|
|||||||
ErrorUnknown
|
ErrorUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
var coreErrorAlertResult = false
|
private var coreErrorAlertResult = false
|
||||||
val coreErrorAlertLock = Object()
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
|
class CoreErrorDialogFragment : DialogFragment() {
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val title = requireArguments().serializable<String>("title")
|
||||||
|
val message = requireArguments().serializable<String>("message")
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireActivity())
|
||||||
|
.setTitle(title)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(R.string.continue_button, null)
|
||||||
|
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||||
|
coreErrorAlertResult = false
|
||||||
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
|
coreErrorAlertResult = true
|
||||||
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(title: String?, message: String?): CoreErrorDialogFragment {
|
||||||
|
val frag = CoreErrorDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putString("title", title)
|
||||||
|
args.putString("message", message)
|
||||||
|
frag.arguments = args
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onCoreErrorImpl(title: String, message: String) {
|
private fun onCoreErrorImpl(title: String, message: String) {
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
error("[NativeLibrary] EmulationActivity not present")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +373,7 @@ object NativeLibrary {
|
|||||||
fun onCoreError(error: CoreError?, details: String): Boolean {
|
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
error("[NativeLibrary] EmulationActivity not present")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +404,7 @@ object NativeLibrary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread.
|
// Show the AlertDialog on the main thread.
|
||||||
emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
|
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete.
|
// Wait for the lock to notify that it is complete.
|
||||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
||||||
@ -459,4 +629,46 @@ object NativeLibrary {
|
|||||||
* Checks if all necessary keys are present for decryption
|
* Checks if all necessary keys are present for decryption
|
||||||
*/
|
*/
|
||||||
external fun areKeysPresent(): Boolean
|
external fun areKeysPresent(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button type for use in onTouchEvent
|
||||||
|
*/
|
||||||
|
object ButtonType {
|
||||||
|
const val BUTTON_A = 0
|
||||||
|
const val BUTTON_B = 1
|
||||||
|
const val BUTTON_X = 2
|
||||||
|
const val BUTTON_Y = 3
|
||||||
|
const val STICK_L = 4
|
||||||
|
const val STICK_R = 5
|
||||||
|
const val TRIGGER_L = 6
|
||||||
|
const val TRIGGER_R = 7
|
||||||
|
const val TRIGGER_ZL = 8
|
||||||
|
const val TRIGGER_ZR = 9
|
||||||
|
const val BUTTON_PLUS = 10
|
||||||
|
const val BUTTON_MINUS = 11
|
||||||
|
const val DPAD_LEFT = 12
|
||||||
|
const val DPAD_UP = 13
|
||||||
|
const val DPAD_RIGHT = 14
|
||||||
|
const val DPAD_DOWN = 15
|
||||||
|
const val BUTTON_SL = 16
|
||||||
|
const val BUTTON_SR = 17
|
||||||
|
const val BUTTON_HOME = 18
|
||||||
|
const val BUTTON_CAPTURE = 19
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stick type for use in onTouchEvent
|
||||||
|
*/
|
||||||
|
object StickType {
|
||||||
|
const val STICK_L = 0
|
||||||
|
const val STICK_R = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button states
|
||||||
|
*/
|
||||||
|
object ButtonState {
|
||||||
|
const val RELEASED = 0
|
||||||
|
const val PRESSED = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import android.app.Application
|
|||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||||
@ -38,7 +37,6 @@ class YuzuApplication : Application() {
|
|||||||
documentsTree = DocumentsTree()
|
documentsTree = DocumentsTree()
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
GpuDriverHelper.initializeDriverParameters()
|
GpuDriverHelper.initializeDriverParameters()
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
Log.logDeviceInfo()
|
Log.logDeviceInfo()
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
@ -48,9 +47,7 @@ import org.yuzu.yuzu_emu.model.Game
|
|||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -66,6 +63,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
private var motionTimestamp: Long = 0
|
private var motionTimestamp: Long = 0
|
||||||
private var flipMotionOrientation: Boolean = false
|
private var flipMotionOrientation: Boolean = false
|
||||||
|
|
||||||
|
private var controllerIds = InputHandler.getGameControllerIds()
|
||||||
|
|
||||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||||
@ -79,33 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
InputHandler.updateControllerData()
|
|
||||||
val players = NativeConfig.getInputSettings(true)
|
|
||||||
var hasConfiguredControllers = false
|
|
||||||
players.forEach {
|
|
||||||
if (it.hasMapping()) {
|
|
||||||
hasConfiguredControllers = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasConfiguredControllers && InputHandler.androidControllers.isNotEmpty()) {
|
|
||||||
var params: ParamPackage? = null
|
|
||||||
for (controller in InputHandler.registeredControllers) {
|
|
||||||
if (controller.get("port", -1) == 0) {
|
|
||||||
params = controller
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params != null) {
|
|
||||||
NativeInput.updateMappingsWithDefault(
|
|
||||||
0,
|
|
||||||
params,
|
|
||||||
params.get("display", getString(R.string.unknown))
|
|
||||||
)
|
|
||||||
NativeConfig.saveGlobalConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@ -123,6 +95,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
nfcReader = NfcReader(this)
|
nfcReader = NfcReader(this)
|
||||||
nfcReader.initialize()
|
nfcReader.initialize()
|
||||||
|
|
||||||
|
InputHandler.initialize()
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||||
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
||||||
@ -173,7 +147,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
nfcReader.startScanning()
|
nfcReader.startScanning()
|
||||||
startMotionSensorListener()
|
startMotionSensorListener()
|
||||||
InputHandler.updateControllerData()
|
InputHandler.updateControllerIds()
|
||||||
|
|
||||||
buildPictureInPictureParams()
|
buildPictureInPictureParams()
|
||||||
}
|
}
|
||||||
@ -198,7 +172,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
nfcReader.onNewIntent(intent)
|
nfcReader.onNewIntent(intent)
|
||||||
InputHandler.updateControllerData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
@ -271,8 +244,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
}
|
}
|
||||||
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
|
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
|
||||||
motionTimestamp = event.timestamp
|
motionTimestamp = event.timestamp
|
||||||
NativeInput.onDeviceMotionEvent(
|
NativeLibrary.onGamePadMotionEvent(
|
||||||
NativeInput.Player1Device,
|
NativeLibrary.Player1Device,
|
||||||
deltaTimestamp,
|
deltaTimestamp,
|
||||||
gyro[0],
|
gyro[0],
|
||||||
gyro[1],
|
gyro[1],
|
||||||
@ -281,8 +254,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
accel[1],
|
accel[1],
|
||||||
accel[2]
|
accel[2]
|
||||||
)
|
)
|
||||||
NativeInput.onDeviceMotionEvent(
|
NativeLibrary.onGamePadMotionEvent(
|
||||||
NativeInput.ConsoleDevice,
|
NativeLibrary.ConsoleDevice,
|
||||||
deltaTimestamp,
|
deltaTimestamp,
|
||||||
gyro[0],
|
gyro[0],
|
||||||
gyro[1],
|
gyro[1],
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||||
import org.yuzu.yuzu_emu.model.Driver
|
import org.yuzu.yuzu_emu.model.Driver
|
||||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||||
@ -44,15 +44,25 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delay marquee by 3s
|
// Delay marquee by 3s
|
||||||
title.marquee()
|
title.postDelayed(
|
||||||
version.marquee()
|
{
|
||||||
description.marquee()
|
title.isSelected = true
|
||||||
|
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
version.isSelected = true
|
||||||
|
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
description.isSelected = true
|
||||||
|
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
title.text = model.title
|
title.text = model.title
|
||||||
version.text = model.version
|
version.text = model.version
|
||||||
description.text = model.description
|
description.text = model.description
|
||||||
buttonDelete.setVisible(
|
if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) {
|
||||||
model.title != binding.root.context.getString(R.string.system_gpu_driver)
|
buttonDelete.visibility = View.VISIBLE
|
||||||
)
|
} else {
|
||||||
|
buttonDelete.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
@ -11,7 +12,6 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
|||||||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
||||||
@ -29,7 +29,13 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
|||||||
override fun bind(model: GameDir) {
|
override fun bind(model: GameDir) {
|
||||||
binding.apply {
|
binding.apply {
|
||||||
path.text = Uri.parse(model.uriString).path
|
path.text = Uri.parse(model.uriString).path
|
||||||
path.marquee()
|
path.postDelayed(
|
||||||
|
{
|
||||||
|
path.isSelected = true
|
||||||
|
path.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
buttonEdit.setOnClickListener {
|
buttonEdit.setOnClickListener {
|
||||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
GameFolderPropertiesDialogFragment.newInstance(model)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@ -26,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
|||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class GameAdapter(private val activity: AppCompatActivity) :
|
class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
@ -44,7 +44,14 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
|||||||
|
|
||||||
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||||
|
|
||||||
binding.textGameTitle.marquee()
|
binding.textGameTitle.postDelayed(
|
||||||
|
{
|
||||||
|
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.textGameTitle.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
binding.cardGame.setOnClickListener { onClick(model) }
|
binding.cardGame.setOnClickListener { onClick(model) }
|
||||||
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,21 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding
|
||||||
import org.yuzu.yuzu_emu.model.GameProperty
|
import org.yuzu.yuzu_emu.model.GameProperty
|
||||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||||
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
import org.yuzu.yuzu_emu.model.SubmenuProperty
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class GamePropertiesAdapter(
|
class GamePropertiesAdapter(
|
||||||
@ -73,15 +76,23 @@ class GamePropertiesAdapter(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.details.marquee()
|
binding.details.postDelayed({
|
||||||
|
binding.details.isSelected = true
|
||||||
|
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
}, 3000)
|
||||||
|
|
||||||
if (submenuProperty.details != null) {
|
if (submenuProperty.details != null) {
|
||||||
binding.details.setVisible(true)
|
binding.details.visibility = View.VISIBLE
|
||||||
binding.details.text = submenuProperty.details.invoke()
|
binding.details.text = submenuProperty.details.invoke()
|
||||||
} else if (submenuProperty.detailsFlow != null) {
|
} else if (submenuProperty.detailsFlow != null) {
|
||||||
binding.details.setVisible(true)
|
binding.details.visibility = View.VISIBLE
|
||||||
submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
|
viewLifecycle.lifecycleScope.launch {
|
||||||
|
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
submenuProperty.detailsFlow.collect { binding.details.text = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.details.setVisible(false)
|
binding.details.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,10 +112,14 @@ class GamePropertiesAdapter(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.buttonInstall.setVisible(installableProperty.install != null)
|
if (installableProperty.install != null) {
|
||||||
binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
|
binding.buttonInstall.visibility = View.VISIBLE
|
||||||
binding.buttonExport.setVisible(installableProperty.export != null)
|
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
|
||||||
binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
|
}
|
||||||
|
if (installableProperty.export != null) {
|
||||||
|
binding.buttonExport.visibility = View.VISIBLE
|
||||||
|
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,22 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class HomeSettingAdapter(
|
class HomeSettingAdapter(
|
||||||
@ -56,8 +59,18 @@ class HomeSettingAdapter(
|
|||||||
binding.optionIcon.alpha = 0.5f
|
binding.optionIcon.alpha = 0.5f
|
||||||
}
|
}
|
||||||
|
|
||||||
model.details.collect(viewLifecycle) { updateOptionDetails(it) }
|
viewLifecycle.lifecycleScope.launch {
|
||||||
binding.optionDetail.marquee()
|
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
model.details.collect { updateOptionDetails(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.optionDetail.postDelayed(
|
||||||
|
{
|
||||||
|
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.optionDetail.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
binding.root.setOnClickListener { onClick(model) }
|
binding.root.setOnClickListener { onClick(model) }
|
||||||
}
|
}
|
||||||
@ -77,7 +90,7 @@ class HomeSettingAdapter(
|
|||||||
private fun updateOptionDetails(detailString: String) {
|
private fun updateOptionDetails(detailString: String) {
|
||||||
if (detailString.isNotEmpty()) {
|
if (detailString.isNotEmpty()) {
|
||||||
binding.optionDetail.text = detailString
|
binding.optionDetail.text = detailString
|
||||||
binding.optionDetail.setVisible(true)
|
binding.optionDetail.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||||
import org.yuzu.yuzu_emu.model.Installable
|
import org.yuzu.yuzu_emu.model.Installable
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class InstallableAdapter(installables: List<Installable>) :
|
class InstallableAdapter(installables: List<Installable>) :
|
||||||
@ -26,10 +26,14 @@ class InstallableAdapter(installables: List<Installable>) :
|
|||||||
binding.title.setText(model.titleId)
|
binding.title.setText(model.titleId)
|
||||||
binding.description.setText(model.descriptionId)
|
binding.description.setText(model.descriptionId)
|
||||||
|
|
||||||
binding.buttonInstall.setVisible(model.install != null)
|
if (model.install != null) {
|
||||||
binding.buttonInstall.setOnClickListener { model.install?.invoke() }
|
binding.buttonInstall.visibility = View.VISIBLE
|
||||||
binding.buttonExport.setVisible(model.export != null)
|
binding.buttonInstall.setOnClickListener { model.install.invoke() }
|
||||||
binding.buttonExport.setOnClickListener { model.export?.invoke() }
|
}
|
||||||
|
if (model.export != null) {
|
||||||
|
binding.buttonExport.visibility = View.VISIBLE
|
||||||
|
binding.buttonExport.setOnClickListener { model.export.invoke() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
||||||
import org.yuzu.yuzu_emu.model.License
|
import org.yuzu.yuzu_emu.model.License
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
||||||
@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic
|
|||||||
binding.apply {
|
binding.apply {
|
||||||
textSettingName.text = root.context.getString(model.titleId)
|
textSettingName.text = root.context.getString(model.titleId)
|
||||||
textSettingDescription.text = root.context.getString(model.descriptionId)
|
textSettingDescription.text = root.context.getString(model.descriptionId)
|
||||||
textSettingValue.setVisible(false)
|
textSettingValue.visibility = View.GONE
|
||||||
|
|
||||||
root.setOnClickListener { onClick(model) }
|
root.setOnClickListener { onClick(model) }
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.adapters
|
|||||||
|
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
@ -16,7 +17,6 @@ import org.yuzu.yuzu_emu.model.SetupCallback
|
|||||||
import org.yuzu.yuzu_emu.model.SetupPage
|
import org.yuzu.yuzu_emu.model.SetupPage
|
||||||
import org.yuzu.yuzu_emu.model.StepState
|
import org.yuzu.yuzu_emu.model.StepState
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||||
@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
|||||||
AbstractViewHolder<SetupPage>(binding), SetupCallback {
|
AbstractViewHolder<SetupPage>(binding), SetupCallback {
|
||||||
override fun bind(model: SetupPage) {
|
override fun bind(model: SetupPage) {
|
||||||
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
|
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
|
||||||
binding.buttonAction.setVisible(visible = false, gone = false)
|
binding.buttonAction.visibility = View.INVISIBLE
|
||||||
binding.textConfirmation.setVisible(true)
|
binding.textConfirmation.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.icon.setImageDrawable(
|
binding.icon.setImageDrawable(
|
||||||
|
@ -1,416 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.ButtonName
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import android.view.InputDevice
|
|
||||||
|
|
||||||
object NativeInput {
|
|
||||||
/**
|
|
||||||
* Default controller id for each device
|
|
||||||
*/
|
|
||||||
const val Player1Device = 0
|
|
||||||
const val Player2Device = 1
|
|
||||||
const val Player3Device = 2
|
|
||||||
const val Player4Device = 3
|
|
||||||
const val Player5Device = 4
|
|
||||||
const val Player6Device = 5
|
|
||||||
const val Player7Device = 6
|
|
||||||
const val Player8Device = 7
|
|
||||||
const val ConsoleDevice = 8
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Button states
|
|
||||||
*/
|
|
||||||
object ButtonState {
|
|
||||||
const val RELEASED = 0
|
|
||||||
const val PRESSED = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if pro controller isn't available and handheld is.
|
|
||||||
* Intended to check where the input overlay should direct its inputs.
|
|
||||||
*/
|
|
||||||
external fun isHandheldOnly(): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles button press events for a gamepad.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param buttonId The Android Keycode corresponding to this event.
|
|
||||||
* @param action Mask identifying which action is happening (button pressed down, or button released).
|
|
||||||
*/
|
|
||||||
external fun onGamePadButtonEvent(
|
|
||||||
guid: String,
|
|
||||||
port: Int,
|
|
||||||
buttonId: Int,
|
|
||||||
action: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles axis movement events.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param axis The axis ID.
|
|
||||||
* @param value Value along the given axis.
|
|
||||||
*/
|
|
||||||
external fun onGamePadAxisEvent(guid: String, port: Int, axis: Int, value: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles motion events.
|
|
||||||
* @param guid 32 character hexadecimal string consisting of the controller's PID+VID.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param deltaTimestamp The finger id corresponding to this event.
|
|
||||||
* @param xGyro The value of the x-axis for the gyroscope.
|
|
||||||
* @param yGyro The value of the y-axis for the gyroscope.
|
|
||||||
* @param zGyro The value of the z-axis for the gyroscope.
|
|
||||||
* @param xAccel The value of the x-axis for the accelerometer.
|
|
||||||
* @param yAccel The value of the y-axis for the accelerometer.
|
|
||||||
* @param zAccel The value of the z-axis for the accelerometer.
|
|
||||||
*/
|
|
||||||
external fun onGamePadMotionEvent(
|
|
||||||
guid: String,
|
|
||||||
port: Int,
|
|
||||||
deltaTimestamp: Long,
|
|
||||||
xGyro: Float,
|
|
||||||
yGyro: Float,
|
|
||||||
zGyro: Float,
|
|
||||||
xAccel: Float,
|
|
||||||
yAccel: Float,
|
|
||||||
zAccel: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals and load a nfc tag
|
|
||||||
* @param data Byte array containing all the data from a nfc tag.
|
|
||||||
*/
|
|
||||||
external fun onReadNfcTag(data: ByteArray?)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes current loaded nfc tag.
|
|
||||||
*/
|
|
||||||
external fun onRemoveNfcTag()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch press events.
|
|
||||||
* @param fingerId The finger id corresponding to this event.
|
|
||||||
* @param xAxis The value of the x-axis on the touchscreen.
|
|
||||||
* @param yAxis The value of the y-axis on the touchscreen.
|
|
||||||
*/
|
|
||||||
external fun onTouchPressed(fingerId: Int, xAxis: Float, yAxis: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch movement.
|
|
||||||
* @param fingerId The finger id corresponding to this event.
|
|
||||||
* @param xAxis The value of the x-axis on the touchscreen.
|
|
||||||
* @param yAxis The value of the y-axis on the touchscreen.
|
|
||||||
*/
|
|
||||||
external fun onTouchMoved(fingerId: Int, xAxis: Float, yAxis: Float)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles touch release events.
|
|
||||||
* @param fingerId The finger id corresponding to this event
|
|
||||||
*/
|
|
||||||
external fun onTouchReleased(fingerId: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a button input to the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param button The [NativeButton] corresponding to this event.
|
|
||||||
* @param action Mask identifying which action is happening (button pressed down, or button released).
|
|
||||||
*/
|
|
||||||
fun onOverlayButtonEvent(port: Int, button: NativeButton, action: Int) =
|
|
||||||
onOverlayButtonEventImpl(port, button.int, action)
|
|
||||||
|
|
||||||
private external fun onOverlayButtonEventImpl(port: Int, buttonId: Int, action: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a joystick input to the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order.
|
|
||||||
* @param stick The [NativeAnalog] corresponding to this event.
|
|
||||||
* @param xAxis Value along the X axis.
|
|
||||||
* @param yAxis Value along the Y axis.
|
|
||||||
*/
|
|
||||||
fun onOverlayJoystickEvent(port: Int, stick: NativeAnalog, xAxis: Float, yAxis: Float) =
|
|
||||||
onOverlayJoystickEventImpl(port, stick.int, xAxis, yAxis)
|
|
||||||
|
|
||||||
private external fun onOverlayJoystickEventImpl(
|
|
||||||
port: Int,
|
|
||||||
stickId: Int,
|
|
||||||
xAxis: Float,
|
|
||||||
yAxis: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles motion events for the global virtual controllers.
|
|
||||||
* @param port Port determined by controller connection order
|
|
||||||
* @param deltaTimestamp The finger id corresponding to this event.
|
|
||||||
* @param xGyro The value of the x-axis for the gyroscope.
|
|
||||||
* @param yGyro The value of the y-axis for the gyroscope.
|
|
||||||
* @param zGyro The value of the z-axis for the gyroscope.
|
|
||||||
* @param xAccel The value of the x-axis for the accelerometer.
|
|
||||||
* @param yAccel The value of the y-axis for the accelerometer.
|
|
||||||
* @param zAccel The value of the z-axis for the accelerometer.
|
|
||||||
*/
|
|
||||||
external fun onDeviceMotionEvent(
|
|
||||||
port: Int,
|
|
||||||
deltaTimestamp: Long,
|
|
||||||
xGyro: Float,
|
|
||||||
yGyro: Float,
|
|
||||||
zGyro: Float,
|
|
||||||
xAccel: Float,
|
|
||||||
yAccel: Float,
|
|
||||||
zAccel: Float
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads all input devices from the currently loaded Settings::values.players into HID Core
|
|
||||||
*/
|
|
||||||
external fun reloadInputDevices()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a controller to be used with mapping
|
|
||||||
* @param device An [InputDevice] or the input overlay wrapped with [YuzuInputDevice]
|
|
||||||
*/
|
|
||||||
external fun registerController(device: YuzuInputDevice)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the names of input devices that have been registered with the input subsystem via [registerController]
|
|
||||||
*/
|
|
||||||
external fun getInputDevices(): Array<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all input profiles from disk. Must be called before creating a profile picker.
|
|
||||||
*/
|
|
||||||
external fun loadInputProfiles()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the names of each available input profile.
|
|
||||||
*/
|
|
||||||
external fun getInputProfileNames(): Array<String>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the user-provided name for an input profile is valid.
|
|
||||||
* @param name User-provided name for an input profile.
|
|
||||||
* @return Whether [name] is valid or not.
|
|
||||||
*/
|
|
||||||
external fun isProfileNameValid(name: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new input profile.
|
|
||||||
* @param name The new profile's name.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to write the profile
|
|
||||||
* name to this player's config.
|
|
||||||
* @return Whether creating the profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun createProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an input profile.
|
|
||||||
* @param name Name of the profile to delete.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to remove the profile
|
|
||||||
* name from this player's config if they have it loaded.
|
|
||||||
* @return Whether deleting this profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun deleteProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads an input profile.
|
|
||||||
* @param name Name of the input profile to load.
|
|
||||||
* @param playerIndex Index of the player that will have this profile loaded.
|
|
||||||
* @return Whether loading this profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun loadProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves an input profile.
|
|
||||||
* @param name Name of the profile to save.
|
|
||||||
* @param playerIndex Index of the player that's currently being edited. Used to write the profile
|
|
||||||
* name to this player's config.
|
|
||||||
* @return Whether saving the profile was successful or not.
|
|
||||||
*/
|
|
||||||
external fun saveProfile(name: String, playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intended to be used immediately before a call to [NativeConfig.saveControlPlayerValues]
|
|
||||||
* Must be used while per-game config is loaded.
|
|
||||||
*/
|
|
||||||
external fun loadPerGameConfiguration(
|
|
||||||
playerIndex: Int,
|
|
||||||
selectedIndex: Int,
|
|
||||||
selectedProfileName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells the input subsystem to start listening for inputs to map.
|
|
||||||
* @param type Type of input to map as shown by the int property in each [InputType].
|
|
||||||
*/
|
|
||||||
external fun beginMapping(type: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets an input's [ParamPackage] as a serialized string. Used for input verification before mapping.
|
|
||||||
* Must be run after [beginMapping] and before [stopMapping].
|
|
||||||
*/
|
|
||||||
external fun getNextInput(): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells the input subsystem to stop listening for inputs to map.
|
|
||||||
*/
|
|
||||||
external fun stopMapping()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a controller's mappings with auto-mapping params.
|
|
||||||
* @param playerIndex Index of the player to auto-map.
|
|
||||||
* @param deviceParams [ParamPackage] representing the device to auto-map as received
|
|
||||||
* from [getInputDevices].
|
|
||||||
* @param displayName Name of the device to auto-map as received from the "display" param in [deviceParams].
|
|
||||||
* Intended to be a way to provide a default name for a controller if the "display" param is empty.
|
|
||||||
*/
|
|
||||||
fun updateMappingsWithDefault(
|
|
||||||
playerIndex: Int,
|
|
||||||
deviceParams: ParamPackage,
|
|
||||||
displayName: String
|
|
||||||
) = updateMappingsWithDefaultImpl(playerIndex, deviceParams.serialize(), displayName)
|
|
||||||
|
|
||||||
private external fun updateMappingsWithDefaultImpl(
|
|
||||||
playerIndex: Int,
|
|
||||||
deviceParams: String,
|
|
||||||
displayName: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the params for a specific button.
|
|
||||||
* @param playerIndex Index of the player to get params from.
|
|
||||||
* @param button The [NativeButton] to get params for.
|
|
||||||
* @return A [ParamPackage] representing a player's specific button.
|
|
||||||
*/
|
|
||||||
fun getButtonParam(playerIndex: Int, button: NativeButton): ParamPackage =
|
|
||||||
ParamPackage(getButtonParamImpl(playerIndex, button.int))
|
|
||||||
|
|
||||||
private external fun getButtonParamImpl(playerIndex: Int, buttonId: Int): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the params for a specific button.
|
|
||||||
* @param playerIndex Index of the player to set params for.
|
|
||||||
* @param button The [NativeButton] to set params for.
|
|
||||||
* @param param A [ParamPackage] to set.
|
|
||||||
*/
|
|
||||||
fun setButtonParam(playerIndex: Int, button: NativeButton, param: ParamPackage) =
|
|
||||||
setButtonParamImpl(playerIndex, button.int, param.serialize())
|
|
||||||
|
|
||||||
private external fun setButtonParamImpl(playerIndex: Int, buttonId: Int, param: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the params for a specific stick.
|
|
||||||
* @param playerIndex Index of the player to get params from.
|
|
||||||
* @param stick The [NativeAnalog] to get params for.
|
|
||||||
* @return A [ParamPackage] representing a player's specific stick.
|
|
||||||
*/
|
|
||||||
fun getStickParam(playerIndex: Int, stick: NativeAnalog): ParamPackage =
|
|
||||||
ParamPackage(getStickParamImpl(playerIndex, stick.int))
|
|
||||||
|
|
||||||
private external fun getStickParamImpl(playerIndex: Int, stickId: Int): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the params for a specific stick.
|
|
||||||
* @param playerIndex Index of the player to set params for.
|
|
||||||
* @param stick The [NativeAnalog] to set params for.
|
|
||||||
* @param param A [ParamPackage] to set.
|
|
||||||
*/
|
|
||||||
fun setStickParam(playerIndex: Int, stick: NativeAnalog, param: ParamPackage) =
|
|
||||||
setStickParamImpl(playerIndex, stick.int, param.serialize())
|
|
||||||
|
|
||||||
private external fun setStickParamImpl(playerIndex: Int, stickId: Int, param: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the int representation of a [ButtonName]. Tells you what to show as the mapped input for
|
|
||||||
* a button/analog/other.
|
|
||||||
* @param param A [ParamPackage] that represents a specific button's params.
|
|
||||||
* @return The [ButtonName] for [param].
|
|
||||||
*/
|
|
||||||
fun getButtonName(param: ParamPackage): ButtonName =
|
|
||||||
ButtonName.from(getButtonNameImpl(param.serialize()))
|
|
||||||
|
|
||||||
private external fun getButtonNameImpl(param: String): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets each supported [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to get supported indexes for.
|
|
||||||
* @return List of each supported [NpadStyleIndex].
|
|
||||||
*/
|
|
||||||
fun getSupportedStyleTags(playerIndex: Int): List<NpadStyleIndex> =
|
|
||||||
getSupportedStyleTagsImpl(playerIndex).map { NpadStyleIndex.from(it) }
|
|
||||||
|
|
||||||
private external fun getSupportedStyleTagsImpl(playerIndex: Int): IntArray
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to get an [NpadStyleIndex] from.
|
|
||||||
* @return The [NpadStyleIndex] for a given player.
|
|
||||||
*/
|
|
||||||
fun getStyleIndex(playerIndex: Int): NpadStyleIndex =
|
|
||||||
NpadStyleIndex.from(getStyleIndexImpl(playerIndex))
|
|
||||||
|
|
||||||
private external fun getStyleIndexImpl(playerIndex: Int): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the [NpadStyleIndex] for a given player.
|
|
||||||
* @param playerIndex Index of the player to change.
|
|
||||||
* @param style The new style to set.
|
|
||||||
*/
|
|
||||||
fun setStyleIndex(playerIndex: Int, style: NpadStyleIndex) =
|
|
||||||
setStyleIndexImpl(playerIndex, style.int)
|
|
||||||
|
|
||||||
private external fun setStyleIndexImpl(playerIndex: Int, styleIndex: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a device is a controller.
|
|
||||||
* @param params [ParamPackage] for an input device retrieved from [getInputDevices]
|
|
||||||
* @return Whether the device is a controller or not.
|
|
||||||
*/
|
|
||||||
fun isController(params: ParamPackage): Boolean = isControllerImpl(params.serialize())
|
|
||||||
|
|
||||||
private external fun isControllerImpl(params: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a controller is connected
|
|
||||||
* @param playerIndex Index of the player to check.
|
|
||||||
* @return Whether the player is connected or not.
|
|
||||||
*/
|
|
||||||
external fun getIsConnected(playerIndex: Int): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects/disconnects a controller and ensures that connection order stays in-tact.
|
|
||||||
* @param playerIndex Index of the player to connect/disconnect.
|
|
||||||
* @param connected Whether to connect or disconnect this controller.
|
|
||||||
*/
|
|
||||||
fun connectControllers(playerIndex: Int, connected: Boolean = true) {
|
|
||||||
val connectedControllers = mutableListOf<Boolean>().apply {
|
|
||||||
if (connected) {
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
add(i <= playerIndex)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
add(i < playerIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectControllersImpl(connectedControllers.toBooleanArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun connectControllersImpl(connected: BooleanArray)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets all of the button and analog mappings for a player.
|
|
||||||
* @param playerIndex Index of the player that will have its mappings reset.
|
|
||||||
*/
|
|
||||||
external fun resetControllerMappings(playerIndex: Int)
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import android.view.InputDevice
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler.getGUID
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
interface YuzuInputDevice {
|
|
||||||
fun getName(): String
|
|
||||||
|
|
||||||
fun getGUID(): String
|
|
||||||
|
|
||||||
fun getPort(): Int
|
|
||||||
|
|
||||||
fun getSupportsVibration(): Boolean
|
|
||||||
|
|
||||||
fun vibrate(intensity: Float)
|
|
||||||
|
|
||||||
fun getAxes(): Array<Int> = arrayOf()
|
|
||||||
fun hasKeys(keys: IntArray): BooleanArray = BooleanArray(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuPhysicalDevice(
|
|
||||||
private val device: InputDevice,
|
|
||||||
private val port: Int,
|
|
||||||
useSystemVibrator: Boolean
|
|
||||||
) : YuzuInputDevice {
|
|
||||||
private val vibrator = if (useSystemVibrator) {
|
|
||||||
YuzuVibrator.getSystemVibrator()
|
|
||||||
} else {
|
|
||||||
YuzuVibrator.getControllerVibrator(device)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getName(): String {
|
|
||||||
return device.name
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getGUID(): String {
|
|
||||||
return device.getGUID()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPort(): Int {
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportsVibration(): Boolean {
|
|
||||||
return vibrator.supportsVibration()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
vibrator.vibrate(intensity)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAxes(): Array<Int> = device.motionRanges.map { it.axis }.toTypedArray()
|
|
||||||
override fun hasKeys(keys: IntArray): BooleanArray = device.hasKeys(*keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuInputOverlayDevice(
|
|
||||||
private val vibration: Boolean,
|
|
||||||
private val port: Int
|
|
||||||
) : YuzuInputDevice {
|
|
||||||
private val vibrator = YuzuVibrator.getSystemVibrator()
|
|
||||||
|
|
||||||
override fun getName(): String {
|
|
||||||
return YuzuApplication.appContext.getString(R.string.input_overlay)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getGUID(): String {
|
|
||||||
return "00000000000000000000000000000000"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPort(): Int {
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSupportsVibration(): Boolean {
|
|
||||||
if (vibration) {
|
|
||||||
return vibrator.supportsVibration()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
if (vibration) {
|
|
||||||
vibrator.vibrate(intensity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.CombinedVibration
|
|
||||||
import android.os.VibrationEffect
|
|
||||||
import android.os.Vibrator
|
|
||||||
import android.os.VibratorManager
|
|
||||||
import android.view.InputDevice
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
interface YuzuVibrator {
|
|
||||||
fun supportsVibration(): Boolean
|
|
||||||
|
|
||||||
fun vibrate(intensity: Float)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getControllerVibrator(device: InputDevice): YuzuVibrator =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
YuzuVibratorManager(device.vibratorManager)
|
|
||||||
} else {
|
|
||||||
YuzuVibratorManagerCompat(device.vibrator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSystemVibrator(): YuzuVibrator =
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val vibratorManager = YuzuApplication.appContext
|
|
||||||
.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
|
||||||
YuzuVibratorManager(vibratorManager)
|
|
||||||
} else {
|
|
||||||
val vibrator = YuzuApplication.appContext
|
|
||||||
.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
|
||||||
YuzuVibratorManagerCompat(vibrator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getVibrationEffect(intensity: Float): VibrationEffect? {
|
|
||||||
if (intensity > 0f) {
|
|
||||||
return VibrationEffect.createOneShot(
|
|
||||||
50,
|
|
||||||
(255.0 * intensity).toInt().coerceIn(1, 255)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.S)
|
|
||||||
class YuzuVibratorManager(private val vibratorManager: VibratorManager) : YuzuVibrator {
|
|
||||||
override fun supportsVibration(): Boolean {
|
|
||||||
return vibratorManager.vibratorIds.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
|
|
||||||
vibratorManager.vibrate(CombinedVibration.createParallel(vibration))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class YuzuVibratorManagerCompat(private val vibrator: Vibrator) : YuzuVibrator {
|
|
||||||
override fun supportsVibration(): Boolean {
|
|
||||||
return vibrator.hasVibrator()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun vibrate(intensity: Float) {
|
|
||||||
val vibration = YuzuVibrator.getVibrationEffect(intensity) ?: return
|
|
||||||
vibrator.vibrate(vibration)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
enum class AnalogDirection(val int: Int, val param: String) {
|
|
||||||
Up(0, "up"),
|
|
||||||
Down(1, "down"),
|
|
||||||
Left(2, "left"),
|
|
||||||
Right(3, "right")
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Loosely matches the enum in common/input.h
|
|
||||||
enum class ButtonName(val int: Int) {
|
|
||||||
Invalid(1),
|
|
||||||
|
|
||||||
// This will display the engine name instead of the button name
|
|
||||||
Engine(2),
|
|
||||||
|
|
||||||
// This will display the button by value instead of the button name
|
|
||||||
Value(3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): ButtonName = entries.firstOrNull { it.int == int } ?: Invalid
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match the corresponding enum in input_common/main.h
|
|
||||||
enum class InputType(val int: Int) {
|
|
||||||
None(0),
|
|
||||||
Button(1),
|
|
||||||
Stick(2),
|
|
||||||
Motion(3),
|
|
||||||
Touch(4)
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeAnalog(val int: Int) {
|
|
||||||
LStick(0),
|
|
||||||
RStick(1);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NativeAnalog = entries.firstOrNull { it.int == int } ?: LStick
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeButton(val int: Int) {
|
|
||||||
A(0),
|
|
||||||
B(1),
|
|
||||||
X(2),
|
|
||||||
Y(3),
|
|
||||||
LStick(4),
|
|
||||||
RStick(5),
|
|
||||||
L(6),
|
|
||||||
R(7),
|
|
||||||
ZL(8),
|
|
||||||
ZR(9),
|
|
||||||
Plus(10),
|
|
||||||
Minus(11),
|
|
||||||
|
|
||||||
DLeft(12),
|
|
||||||
DUp(13),
|
|
||||||
DRight(14),
|
|
||||||
DDown(15),
|
|
||||||
|
|
||||||
SLLeft(16),
|
|
||||||
SRLeft(17),
|
|
||||||
|
|
||||||
Home(18),
|
|
||||||
Capture(19),
|
|
||||||
|
|
||||||
SLRight(20),
|
|
||||||
SRRight(21);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NativeButton = entries.firstOrNull { it.int == int } ?: A
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
// Must match enum in src/common/settings_input.h
|
|
||||||
enum class NativeTrigger(val int: Int) {
|
|
||||||
LTrigger(0),
|
|
||||||
RTrigger(1)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
// Must match enum in src/core/hid/hid_types.h
|
|
||||||
enum class NpadStyleIndex(val int: Int, @StringRes val nameId: Int = 0) {
|
|
||||||
None(0),
|
|
||||||
Fullkey(3, R.string.pro_controller),
|
|
||||||
Handheld(4, R.string.handheld),
|
|
||||||
HandheldNES(4),
|
|
||||||
JoyconDual(5, R.string.dual_joycons),
|
|
||||||
JoyconLeft(6, R.string.left_joycon),
|
|
||||||
JoyconRight(7, R.string.right_joycon),
|
|
||||||
GameCube(8, R.string.gamecube_controller),
|
|
||||||
Pokeball(9),
|
|
||||||
NES(10),
|
|
||||||
SNES(12),
|
|
||||||
N64(13),
|
|
||||||
SegaGenesis(14),
|
|
||||||
SystemExt(32),
|
|
||||||
System(33);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): NpadStyleIndex = entries.firstOrNull { it.int == int } ?: None
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.input.model
|
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
data class PlayerInput(
|
|
||||||
var connected: Boolean,
|
|
||||||
var buttons: Array<String>,
|
|
||||||
var analogs: Array<String>,
|
|
||||||
var motions: Array<String>,
|
|
||||||
|
|
||||||
var vibrationEnabled: Boolean,
|
|
||||||
var vibrationStrength: Int,
|
|
||||||
|
|
||||||
var bodyColorLeft: Long,
|
|
||||||
var bodyColorRight: Long,
|
|
||||||
var buttonColorLeft: Long,
|
|
||||||
var buttonColorRight: Long,
|
|
||||||
var profileName: String,
|
|
||||||
|
|
||||||
var useSystemVibrator: Boolean
|
|
||||||
) {
|
|
||||||
// It's recommended to use the generated equals() and hashCode() methods
|
|
||||||
// when using arrays in a data class
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as PlayerInput
|
|
||||||
|
|
||||||
if (connected != other.connected) return false
|
|
||||||
if (!buttons.contentEquals(other.buttons)) return false
|
|
||||||
if (!analogs.contentEquals(other.analogs)) return false
|
|
||||||
if (!motions.contentEquals(other.motions)) return false
|
|
||||||
if (vibrationEnabled != other.vibrationEnabled) return false
|
|
||||||
if (vibrationStrength != other.vibrationStrength) return false
|
|
||||||
if (bodyColorLeft != other.bodyColorLeft) return false
|
|
||||||
if (bodyColorRight != other.bodyColorRight) return false
|
|
||||||
if (buttonColorLeft != other.buttonColorLeft) return false
|
|
||||||
if (buttonColorRight != other.buttonColorRight) return false
|
|
||||||
if (profileName != other.profileName) return false
|
|
||||||
return useSystemVibrator == other.useSystemVibrator
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = connected.hashCode()
|
|
||||||
result = 31 * result + buttons.contentHashCode()
|
|
||||||
result = 31 * result + analogs.contentHashCode()
|
|
||||||
result = 31 * result + motions.contentHashCode()
|
|
||||||
result = 31 * result + vibrationEnabled.hashCode()
|
|
||||||
result = 31 * result + vibrationStrength
|
|
||||||
result = 31 * result + bodyColorLeft.hashCode()
|
|
||||||
result = 31 * result + bodyColorRight.hashCode()
|
|
||||||
result = 31 * result + buttonColorLeft.hashCode()
|
|
||||||
result = 31 * result + buttonColorRight.hashCode()
|
|
||||||
result = 31 * result + profileName.hashCode()
|
|
||||||
result = 31 * result + useSystemVibrator.hashCode()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hasMapping(): Boolean {
|
|
||||||
var hasMapping = false
|
|
||||||
buttons.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
analogs.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
motions.forEach {
|
|
||||||
if (it != "[empty]" && it.isNotEmpty()) {
|
|
||||||
hasMapping = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasMapping
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,9 +24,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||||||
THEME_MODE("theme_mode"),
|
THEME_MODE("theme_mode"),
|
||||||
OVERLAY_SCALE("control_scale"),
|
OVERLAY_SCALE("control_scale"),
|
||||||
OVERLAY_OPACITY("control_opacity"),
|
OVERLAY_OPACITY("control_opacity"),
|
||||||
LOCK_DRAWER("lock_drawer"),
|
LOCK_DRAWER("lock_drawer");
|
||||||
VERTICAL_ALIGNMENT("vertical_alignment"),
|
|
||||||
FSR_SHARPENING_SLIDER("fsr_sharpening_slider");
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||||
|
|
||||||
|
@ -4,30 +4,17 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.model
|
package org.yuzu.yuzu_emu.features.settings.model
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
|
|
||||||
object Settings {
|
object Settings {
|
||||||
enum class MenuTag(val titleId: Int = 0) {
|
enum class MenuTag(val titleId: Int) {
|
||||||
SECTION_ROOT(R.string.advanced_settings),
|
SECTION_ROOT(R.string.advanced_settings),
|
||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_AUDIO(R.string.preferences_audio),
|
SECTION_AUDIO(R.string.preferences_audio),
|
||||||
SECTION_INPUT(R.string.preferences_controls),
|
|
||||||
SECTION_INPUT_PLAYER_ONE,
|
|
||||||
SECTION_INPUT_PLAYER_TWO,
|
|
||||||
SECTION_INPUT_PLAYER_THREE,
|
|
||||||
SECTION_INPUT_PLAYER_FOUR,
|
|
||||||
SECTION_INPUT_PLAYER_FIVE,
|
|
||||||
SECTION_INPUT_PLAYER_SIX,
|
|
||||||
SECTION_INPUT_PLAYER_SEVEN,
|
|
||||||
SECTION_INPUT_PLAYER_EIGHT,
|
|
||||||
SECTION_THEME(R.string.preferences_theme),
|
SECTION_THEME(R.string.preferences_theme),
|
||||||
SECTION_DEBUG(R.string.preferences_debug);
|
SECTION_DEBUG(R.string.preferences_debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlayerString(player: Int): String =
|
|
||||||
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
|
||||||
|
|
||||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||||
|
|
||||||
@ -106,15 +93,4 @@ object Settings {
|
|||||||
entries.firstOrNull { it.int == int } ?: Unspecified
|
entries.firstOrNull { it.int == int } ?: Unspecified
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EmulationVerticalAlignment(val int: Int) {
|
|
||||||
Top(1),
|
|
||||||
Center(0),
|
|
||||||
Bottom(2);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun from(int: Int): EmulationVerticalAlignment =
|
|
||||||
entries.firstOrNull { it.int == int } ?: Center
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,7 @@ package org.yuzu.yuzu_emu.features.settings.model
|
|||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
enum class StringSetting(override val key: String) : AbstractStringSetting {
|
||||||
DRIVER_PATH("driver_path"),
|
DRIVER_PATH("driver_path");
|
||||||
DEVICE_NAME("device_name");
|
|
||||||
|
|
||||||
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal)
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class AnalogInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeAnalog: NativeAnalog,
|
|
||||||
val analogDirection: AnalogDirection,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
override val inputType = InputType.Stick
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val params = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
val analog = analogToText(params, analogDirection.param)
|
|
||||||
return getDisplayString(params, analog)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) =
|
|
||||||
NativeInput.setStickParam(playerIndex, nativeAnalog, param)
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
|
|
||||||
class ButtonInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeButton: NativeButton,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
override val inputType = InputType.Button
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val params = NativeInput.getButtonParam(playerIndex, nativeButton)
|
|
||||||
val button = buttonToText(params)
|
|
||||||
return getDisplayString(params, button)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) =
|
|
||||||
NativeInput.setButtonParam(playerIndex, nativeButton, param)
|
|
||||||
}
|
|
@ -3,16 +3,13 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
||||||
|
|
||||||
class DateTimeSetting(
|
class DateTimeSetting(
|
||||||
private val longSetting: AbstractLongSetting,
|
private val longSetting: AbstractLongSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int
|
||||||
@StringRes descriptionId: Int = 0,
|
) : SettingsItem(longSetting, titleId, descriptionId) {
|
||||||
descriptionString: String = ""
|
|
||||||
) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_DATETIME_SETTING
|
override val type = TYPE_DATETIME_SETTING
|
||||||
|
|
||||||
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
|
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
|
||||||
|
@ -3,11 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class HeaderSetting(
|
class HeaderSetting(
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int
|
||||||
titleString: String = ""
|
) : SettingsItem(emptySetting, titleId, 0) {
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
|
|
||||||
override val type = TYPE_HEADER
|
override val type = TYPE_HEADER
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
|
||||||
|
|
||||||
class InputProfileSetting(private val playerIndex: Int) :
|
|
||||||
SettingsItem(emptySetting, R.string.profile, "", 0, "") {
|
|
||||||
override val type = TYPE_INPUT_PROFILE
|
|
||||||
|
|
||||||
fun getCurrentProfile(): String =
|
|
||||||
NativeConfig.getInputSettings(true)[playerIndex].profileName
|
|
||||||
|
|
||||||
fun getProfileNames(): Array<String> = NativeInput.getInputProfileNames()
|
|
||||||
|
|
||||||
fun isProfileNameValid(name: String): Boolean = NativeInput.isProfileNameValid(name)
|
|
||||||
|
|
||||||
fun createProfile(name: String): Boolean = NativeInput.createProfile(name, playerIndex)
|
|
||||||
|
|
||||||
fun deleteProfile(name: String): Boolean = NativeInput.deleteProfile(name, playerIndex)
|
|
||||||
|
|
||||||
fun loadProfile(name: String): Boolean {
|
|
||||||
val result = NativeInput.loadProfile(name, playerIndex)
|
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveProfile(name: String): Boolean = NativeInput.saveProfile(name, playerIndex)
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.ButtonName
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
sealed class InputSetting(
|
|
||||||
@StringRes titleId: Int,
|
|
||||||
titleString: String
|
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
|
|
||||||
override val type = TYPE_INPUT
|
|
||||||
abstract val inputType: InputType
|
|
||||||
abstract val playerIndex: Int
|
|
||||||
|
|
||||||
protected val context get() = YuzuApplication.appContext
|
|
||||||
|
|
||||||
abstract fun getSelectedValue(): String
|
|
||||||
|
|
||||||
abstract fun setSelectedValue(param: ParamPackage)
|
|
||||||
|
|
||||||
protected fun getDisplayString(params: ParamPackage, control: String): String {
|
|
||||||
val deviceName = params.get("display", "")
|
|
||||||
deviceName.ifEmpty {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
return "$deviceName: $control"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDirectionName(direction: String): String =
|
|
||||||
when (direction) {
|
|
||||||
"up" -> context.getString(R.string.up)
|
|
||||||
"down" -> context.getString(R.string.down)
|
|
||||||
"left" -> context.getString(R.string.left)
|
|
||||||
"right" -> context.getString(R.string.right)
|
|
||||||
else -> direction
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun buttonToText(param: ParamPackage): String {
|
|
||||||
if (!param.has("engine")) {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
val toggle = if (param.get("toggle", false)) "~" else ""
|
|
||||||
val inverted = if (param.get("inverted", false)) "!" else ""
|
|
||||||
val invert = if (param.get("invert", "+") == "-") "-" else ""
|
|
||||||
val turbo = if (param.get("turbo", false)) "$" else ""
|
|
||||||
val commonButtonName = NativeInput.getButtonName(param)
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Invalid) {
|
|
||||||
return context.getString(R.string.invalid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Engine) {
|
|
||||||
return param.get("engine", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commonButtonName == ButtonName.Value) {
|
|
||||||
if (param.has("hat")) {
|
|
||||||
val hat = getDirectionName(param.get("direction", ""))
|
|
||||||
return context.getString(R.string.qualified_hat, turbo, toggle, inverted, hat)
|
|
||||||
}
|
|
||||||
if (param.has("axis")) {
|
|
||||||
val axis = param.get("axis", "")
|
|
||||||
return context.getString(
|
|
||||||
R.string.qualified_button_stick_axis,
|
|
||||||
toggle,
|
|
||||||
inverted,
|
|
||||||
invert,
|
|
||||||
axis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (param.has("button")) {
|
|
||||||
val button = param.get("button", "")
|
|
||||||
return context.getString(R.string.qualified_button, turbo, toggle, inverted, button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun analogToText(param: ParamPackage, direction: String): String {
|
|
||||||
if (!param.has("engine")) {
|
|
||||||
return context.getString(R.string.not_set)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param.get("engine", "") == "analog_from_button") {
|
|
||||||
return buttonToText(ParamPackage(param.get(direction, "")))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!param.has("axis_x") || !param.has("axis_y")) {
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
val xAxis = param.get("axis_x", "")
|
|
||||||
val yAxis = param.get("axis_y", "")
|
|
||||||
val xInvert = param.get("invert_x", "+") == "-"
|
|
||||||
val yInvert = param.get("invert_y", "+") == "-"
|
|
||||||
|
|
||||||
if (direction == "modifier") {
|
|
||||||
return context.getString(R.string.unused)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (direction) {
|
|
||||||
"up" -> {
|
|
||||||
val yInvertString = if (yInvert) "+" else "-"
|
|
||||||
return context.getString(R.string.qualified_axis, yAxis, yInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"down" -> {
|
|
||||||
val yInvertString = if (yInvert) "-" else "+"
|
|
||||||
return context.getString(R.string.qualified_axis, yAxis, yInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"left" -> {
|
|
||||||
val xInvertString = if (xInvert) "+" else "-"
|
|
||||||
return context.getString(R.string.qualified_axis, xAxis, xInvertString)
|
|
||||||
}
|
|
||||||
|
|
||||||
"right" -> {
|
|
||||||
val xInvertString = if (xInvert) "-" else "+"
|
|
||||||
return context.getString(R.string.qualified_axis, xAxis, xInvertString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.getString(R.string.unknown)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
|
||||||
|
|
||||||
class IntSingleChoiceSetting(
|
|
||||||
private val intSetting: AbstractIntSetting,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = "",
|
|
||||||
@StringRes descriptionId: Int = 0,
|
|
||||||
descriptionString: String = "",
|
|
||||||
val choices: Array<String>,
|
|
||||||
val values: Array<Int>
|
|
||||||
) : SettingsItem(intSetting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_INT_SINGLE_CHOICE
|
|
||||||
|
|
||||||
fun getValueAt(index: Int): Int =
|
|
||||||
if (values.indices.contains(index)) values[index] else -1
|
|
||||||
|
|
||||||
fun getChoiceAt(index: Int): String =
|
|
||||||
if (choices.indices.contains(index)) choices[index] else ""
|
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) = intSetting.getInt(needsGlobal)
|
|
||||||
fun setSelectedValue(value: Int) = intSetting.setInt(value)
|
|
||||||
|
|
||||||
val selectedValueIndex: Int
|
|
||||||
get() {
|
|
||||||
for (i in values.indices) {
|
|
||||||
if (values[i] == getSelectedValue()) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.InputType
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class ModifierInputSetting(
|
|
||||||
override val playerIndex: Int,
|
|
||||||
val nativeAnalog: NativeAnalog,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = ""
|
|
||||||
) : InputSetting(titleId, titleString) {
|
|
||||||
override val inputType = InputType.Button
|
|
||||||
|
|
||||||
override fun getSelectedValue(): String {
|
|
||||||
val analogParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
val modifierParam = ParamPackage(analogParam.get("modifier", ""))
|
|
||||||
return buttonToText(modifierParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setSelectedValue(param: ParamPackage) {
|
|
||||||
val newParam = NativeInput.getStickParam(playerIndex, nativeAnalog)
|
|
||||||
newParam.set("modifier", param.serialize())
|
|
||||||
NativeInput.setStickParam(playerIndex, nativeAnalog, newParam)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,16 +4,13 @@
|
|||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
|
||||||
|
|
||||||
class RunnableSetting(
|
class RunnableSetting(
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
val isRuntimeRunnable: Boolean,
|
||||||
descriptionString: String = "",
|
|
||||||
val isRunnable: Boolean,
|
|
||||||
@DrawableRes val iconId: Int = 0,
|
@DrawableRes val iconId: Int = 0,
|
||||||
val runnable: () -> Unit
|
val runnable: () -> Unit
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_RUNNABLE
|
override val type = TYPE_RUNNABLE
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
@ -16,7 +12,6 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,34 +23,13 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||||||
*/
|
*/
|
||||||
abstract class SettingsItem(
|
abstract class SettingsItem(
|
||||||
val setting: AbstractSetting,
|
val setting: AbstractSetting,
|
||||||
@StringRes val titleId: Int,
|
val nameId: Int,
|
||||||
val titleString: String,
|
val descriptionId: Int
|
||||||
@StringRes val descriptionId: Int,
|
|
||||||
val descriptionString: String
|
|
||||||
) {
|
) {
|
||||||
abstract val type: Int
|
abstract val type: Int
|
||||||
|
|
||||||
val title: String by lazy {
|
|
||||||
if (titleId != 0) {
|
|
||||||
return@lazy YuzuApplication.appContext.getString(titleId)
|
|
||||||
}
|
|
||||||
return@lazy titleString
|
|
||||||
}
|
|
||||||
|
|
||||||
val description: String by lazy {
|
|
||||||
if (descriptionId != 0) {
|
|
||||||
return@lazy YuzuApplication.appContext.getString(descriptionId)
|
|
||||||
}
|
|
||||||
return@lazy descriptionString
|
|
||||||
}
|
|
||||||
|
|
||||||
val isEditable: Boolean
|
val isEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
// Can't change docked mode toggle when using handheld mode
|
|
||||||
if (setting.key == BooleanSetting.USE_DOCKED_MODE.key) {
|
|
||||||
return NativeInput.getStyleIndex(0) != NpadStyleIndex.Handheld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
// Can't edit settings that aren't saveable in per-game config even if they are switchable
|
||||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||||
return false
|
return false
|
||||||
@ -76,9 +50,6 @@ abstract class SettingsItem(
|
|||||||
get() = NativeLibrary.isRunning() && !setting.global &&
|
get() = NativeLibrary.isRunning() && !setting.global &&
|
||||||
!NativeConfig.isPerGameConfigLoaded()
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
|
||||||
val clearable: Boolean
|
|
||||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_HEADER = 0
|
const val TYPE_HEADER = 0
|
||||||
const val TYPE_SWITCH = 1
|
const val TYPE_SWITCH = 1
|
||||||
@ -88,10 +59,6 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_STRING_SINGLE_CHOICE = 5
|
const val TYPE_STRING_SINGLE_CHOICE = 5
|
||||||
const val TYPE_DATETIME_SETTING = 6
|
const val TYPE_DATETIME_SETTING = 6
|
||||||
const val TYPE_RUNNABLE = 7
|
const val TYPE_RUNNABLE = 7
|
||||||
const val TYPE_INPUT = 8
|
|
||||||
const val TYPE_INT_SINGLE_CHOICE = 9
|
|
||||||
const val TYPE_INPUT_PROFILE = 10
|
|
||||||
const val TYPE_STRING_INPUT = 11
|
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
|
|
||||||
@ -110,246 +77,221 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
// List of all general
|
// List of all general
|
||||||
val settingsItems = HashMap<String, SettingsItem>().apply {
|
val settingsItems = HashMap<String, SettingsItem>().apply {
|
||||||
put(StringInputSetting(StringSetting.DEVICE_NAME, titleId = R.string.device_name))
|
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||||
titleId = R.string.frame_limit_enable,
|
R.string.frame_limit_enable,
|
||||||
descriptionId = R.string.frame_limit_enable_description
|
R.string.frame_limit_enable_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||||
titleId = R.string.frame_limit_slider,
|
R.string.frame_limit_slider,
|
||||||
descriptionId = R.string.frame_limit_slider_description,
|
R.string.frame_limit_slider_description,
|
||||||
min = 1,
|
1,
|
||||||
max = 400,
|
400,
|
||||||
units = "%"
|
"%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.CPU_BACKEND,
|
IntSetting.CPU_BACKEND,
|
||||||
titleId = R.string.cpu_backend,
|
R.string.cpu_backend,
|
||||||
choicesId = R.array.cpuBackendArm64Names,
|
0,
|
||||||
valuesId = R.array.cpuBackendArm64Values
|
R.array.cpuBackendArm64Names,
|
||||||
|
R.array.cpuBackendArm64Values
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.CPU_ACCURACY,
|
IntSetting.CPU_ACCURACY,
|
||||||
titleId = R.string.cpu_accuracy,
|
R.string.cpu_accuracy,
|
||||||
choicesId = R.array.cpuAccuracyNames,
|
0,
|
||||||
valuesId = R.array.cpuAccuracyValues
|
R.array.cpuAccuracyNames,
|
||||||
|
R.array.cpuAccuracyValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.PICTURE_IN_PICTURE,
|
BooleanSetting.PICTURE_IN_PICTURE,
|
||||||
titleId = R.string.picture_in_picture,
|
R.string.picture_in_picture,
|
||||||
descriptionId = R.string.picture_in_picture_description
|
R.string.picture_in_picture_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val dockedModeSetting = object : AbstractBooleanSetting {
|
|
||||||
override val key = BooleanSetting.USE_DOCKED_MODE.key
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean {
|
|
||||||
if (NativeInput.getStyleIndex(0) == NpadStyleIndex.Handheld) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return BooleanSetting.USE_DOCKED_MODE.getBoolean(needsGlobal)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) =
|
|
||||||
BooleanSetting.USE_DOCKED_MODE.setBoolean(value)
|
|
||||||
|
|
||||||
override val defaultValue = BooleanSetting.USE_DOCKED_MODE.defaultValue
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
BooleanSetting.USE_DOCKED_MODE.getValueAsString(needsGlobal)
|
|
||||||
|
|
||||||
override fun reset() = BooleanSetting.USE_DOCKED_MODE.reset()
|
|
||||||
}
|
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
dockedModeSetting,
|
BooleanSetting.USE_DOCKED_MODE,
|
||||||
titleId = R.string.use_docked_mode,
|
R.string.use_docked_mode,
|
||||||
descriptionId = R.string.use_docked_mode_description
|
R.string.use_docked_mode_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.REGION_INDEX,
|
IntSetting.REGION_INDEX,
|
||||||
titleId = R.string.emulated_region,
|
R.string.emulated_region,
|
||||||
choicesId = R.array.regionNames,
|
0,
|
||||||
valuesId = R.array.regionValues
|
R.array.regionNames,
|
||||||
|
R.array.regionValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.LANGUAGE_INDEX,
|
IntSetting.LANGUAGE_INDEX,
|
||||||
titleId = R.string.emulated_language,
|
R.string.emulated_language,
|
||||||
choicesId = R.array.languageNames,
|
0,
|
||||||
valuesId = R.array.languageValues
|
R.array.languageNames,
|
||||||
|
R.array.languageValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.USE_CUSTOM_RTC,
|
BooleanSetting.USE_CUSTOM_RTC,
|
||||||
titleId = R.string.use_custom_rtc,
|
R.string.use_custom_rtc,
|
||||||
descriptionId = R.string.use_custom_rtc_description
|
R.string.use_custom_rtc_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
|
put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ACCURACY,
|
IntSetting.RENDERER_ACCURACY,
|
||||||
titleId = R.string.renderer_accuracy,
|
R.string.renderer_accuracy,
|
||||||
choicesId = R.array.rendererAccuracyNames,
|
0,
|
||||||
valuesId = R.array.rendererAccuracyValues
|
R.array.rendererAccuracyNames,
|
||||||
|
R.array.rendererAccuracyValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_RESOLUTION,
|
IntSetting.RENDERER_RESOLUTION,
|
||||||
titleId = R.string.renderer_resolution,
|
R.string.renderer_resolution,
|
||||||
choicesId = R.array.rendererResolutionNames,
|
0,
|
||||||
valuesId = R.array.rendererResolutionValues
|
R.array.rendererResolutionNames,
|
||||||
|
R.array.rendererResolutionValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_VSYNC,
|
IntSetting.RENDERER_VSYNC,
|
||||||
titleId = R.string.renderer_vsync,
|
R.string.renderer_vsync,
|
||||||
choicesId = R.array.rendererVSyncNames,
|
0,
|
||||||
valuesId = R.array.rendererVSyncValues
|
R.array.rendererVSyncNames,
|
||||||
|
R.array.rendererVSyncValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCALING_FILTER,
|
IntSetting.RENDERER_SCALING_FILTER,
|
||||||
titleId = R.string.renderer_scaling_filter,
|
R.string.renderer_scaling_filter,
|
||||||
choicesId = R.array.rendererScalingFilterNames,
|
0,
|
||||||
valuesId = R.array.rendererScalingFilterValues
|
R.array.rendererScalingFilterNames,
|
||||||
)
|
R.array.rendererScalingFilterValues
|
||||||
)
|
|
||||||
put(
|
|
||||||
SliderSetting(
|
|
||||||
IntSetting.FSR_SHARPENING_SLIDER,
|
|
||||||
titleId = R.string.fsr_sharpness,
|
|
||||||
descriptionId = R.string.fsr_sharpness_description,
|
|
||||||
units = "%"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ANTI_ALIASING,
|
IntSetting.RENDERER_ANTI_ALIASING,
|
||||||
titleId = R.string.renderer_anti_aliasing,
|
R.string.renderer_anti_aliasing,
|
||||||
choicesId = R.array.rendererAntiAliasingNames,
|
0,
|
||||||
valuesId = R.array.rendererAntiAliasingValues
|
R.array.rendererAntiAliasingNames,
|
||||||
|
R.array.rendererAntiAliasingValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||||
titleId = R.string.renderer_screen_layout,
|
R.string.renderer_screen_layout,
|
||||||
choicesId = R.array.rendererScreenLayoutNames,
|
0,
|
||||||
valuesId = R.array.rendererScreenLayoutValues
|
R.array.rendererScreenLayoutNames,
|
||||||
|
R.array.rendererScreenLayoutValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_ASPECT_RATIO,
|
IntSetting.RENDERER_ASPECT_RATIO,
|
||||||
titleId = R.string.renderer_aspect_ratio,
|
R.string.renderer_aspect_ratio,
|
||||||
choicesId = R.array.rendererAspectRatioNames,
|
0,
|
||||||
valuesId = R.array.rendererAspectRatioValues
|
R.array.rendererAspectRatioNames,
|
||||||
)
|
R.array.rendererAspectRatioValues
|
||||||
)
|
|
||||||
put(
|
|
||||||
SingleChoiceSetting(
|
|
||||||
IntSetting.VERTICAL_ALIGNMENT,
|
|
||||||
titleId = R.string.vertical_alignment,
|
|
||||||
descriptionId = 0,
|
|
||||||
choicesId = R.array.verticalAlignmentEntries,
|
|
||||||
valuesId = R.array.verticalAlignmentValues
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||||
titleId = R.string.use_disk_shader_cache,
|
R.string.use_disk_shader_cache,
|
||||||
descriptionId = R.string.use_disk_shader_cache_description
|
R.string.use_disk_shader_cache_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||||
titleId = R.string.renderer_force_max_clock,
|
R.string.renderer_force_max_clock,
|
||||||
descriptionId = R.string.renderer_force_max_clock_description
|
R.string.renderer_force_max_clock_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||||
titleId = R.string.renderer_asynchronous_shaders,
|
R.string.renderer_asynchronous_shaders,
|
||||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
R.string.renderer_asynchronous_shaders_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
||||||
titleId = R.string.renderer_reactive_flushing,
|
R.string.renderer_reactive_flushing,
|
||||||
descriptionId = R.string.renderer_reactive_flushing_description
|
R.string.renderer_reactive_flushing_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.MAX_ANISOTROPY,
|
IntSetting.MAX_ANISOTROPY,
|
||||||
titleId = R.string.anisotropic_filtering,
|
R.string.anisotropic_filtering,
|
||||||
descriptionId = R.string.anisotropic_filtering_description,
|
R.string.anisotropic_filtering_description,
|
||||||
choicesId = R.array.anisoEntries,
|
R.array.anisoEntries,
|
||||||
valuesId = R.array.anisoValues
|
R.array.anisoValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.AUDIO_OUTPUT_ENGINE,
|
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||||
titleId = R.string.audio_output_engine,
|
R.string.audio_output_engine,
|
||||||
choicesId = R.array.outputEngineEntries,
|
0,
|
||||||
valuesId = R.array.outputEngineValues
|
R.array.outputEngineEntries,
|
||||||
|
R.array.outputEngineValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SliderSetting(
|
SliderSetting(
|
||||||
ByteSetting.AUDIO_VOLUME,
|
ByteSetting.AUDIO_VOLUME,
|
||||||
titleId = R.string.audio_volume,
|
R.string.audio_volume,
|
||||||
descriptionId = R.string.audio_volume_description,
|
R.string.audio_volume_description,
|
||||||
units = "%"
|
0,
|
||||||
|
100,
|
||||||
|
"%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_BACKEND,
|
IntSetting.RENDERER_BACKEND,
|
||||||
titleId = R.string.renderer_api,
|
R.string.renderer_api,
|
||||||
choicesId = R.array.rendererApiNames,
|
0,
|
||||||
valuesId = R.array.rendererApiValues
|
R.array.rendererApiNames,
|
||||||
|
R.array.rendererApiValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.RENDERER_DEBUG,
|
BooleanSetting.RENDERER_DEBUG,
|
||||||
titleId = R.string.renderer_debug,
|
R.string.renderer_debug,
|
||||||
descriptionId = R.string.renderer_debug_description
|
R.string.renderer_debug_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
BooleanSetting.CPU_DEBUG_MODE,
|
BooleanSetting.CPU_DEBUG_MODE,
|
||||||
titleId = R.string.cpu_debug_mode,
|
R.string.cpu_debug_mode,
|
||||||
descriptionId = R.string.cpu_debug_mode_description
|
R.string.cpu_debug_mode_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -385,7 +327,7 @@ abstract class SettingsItem(
|
|||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
override fun reset() = setBoolean(defaultValue)
|
||||||
}
|
}
|
||||||
put(SwitchSetting(fastmem, R.string.fastmem))
|
put(SwitchSetting(fastmem, R.string.fastmem, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,16 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.ArrayRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
|
|
||||||
class SingleChoiceSetting(
|
class SingleChoiceSetting(
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
val choicesId: Int,
|
||||||
descriptionString: String = "",
|
val valuesId: Int
|
||||||
@ArrayRes val choicesId: Int,
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
@ArrayRes val valuesId: Int
|
|
||||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_SINGLE_CHOICE
|
override val type = TYPE_SINGLE_CHOICE
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) =
|
fun getSelectedValue(needsGlobal: Boolean = false) =
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
@ -13,14 +12,12 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class SliderSetting(
|
class SliderSetting(
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
val min: Int,
|
||||||
descriptionString: String = "",
|
val max: Int,
|
||||||
val min: Int = 0,
|
val units: String
|
||||||
val max: Int = 100,
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
val units: String = ""
|
|
||||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_SLIDER
|
override val type = TYPE_SLIDER
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) =
|
fun getSelectedValue(needsGlobal: Boolean = false) =
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
|
||||||
|
|
||||||
class StringInputSetting(
|
|
||||||
setting: AbstractStringSetting,
|
|
||||||
@StringRes titleId: Int = 0,
|
|
||||||
titleString: String = "",
|
|
||||||
@StringRes descriptionId: Int = 0,
|
|
||||||
descriptionString: String = ""
|
|
||||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_STRING_INPUT
|
|
||||||
|
|
||||||
fun getSelectedValue(needsGlobal: Boolean = false) = setting.getValueAsString(needsGlobal)
|
|
||||||
|
|
||||||
fun setSelectedValue(selection: String) =
|
|
||||||
(setting as AbstractStringSetting).setString(selection)
|
|
||||||
}
|
|
@ -3,18 +3,15 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||||
|
|
||||||
class StringSingleChoiceSetting(
|
class StringSingleChoiceSetting(
|
||||||
private val stringSetting: AbstractStringSetting,
|
private val stringSetting: AbstractStringSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
|
||||||
descriptionString: String = "",
|
|
||||||
val choices: Array<String>,
|
val choices: Array<String>,
|
||||||
val values: Array<String>
|
val values: Array<String>
|
||||||
) : SettingsItem(stringSetting, titleId, titleString, descriptionId, descriptionString) {
|
) : SettingsItem(stringSetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||||
|
|
||||||
fun getValueAt(index: Int): String =
|
fun getValueAt(index: Int): String =
|
||||||
@ -23,7 +20,7 @@ class StringSingleChoiceSetting(
|
|||||||
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
|
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
|
||||||
fun setSelectedValue(value: String) = stringSetting.setString(value)
|
fun setSelectedValue(value: String) = stringSetting.setString(value)
|
||||||
|
|
||||||
val selectedValueIndex: Int
|
val selectValueIndex: Int
|
||||||
get() {
|
get() {
|
||||||
for (i in values.indices) {
|
for (i in values.indices) {
|
||||||
if (values[i] == getSelectedValue()) {
|
if (values[i] == getSelectedValue()) {
|
||||||
|
@ -8,12 +8,10 @@ import androidx.annotation.StringRes
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
|
||||||
class SubmenuSetting(
|
class SubmenuSetting(
|
||||||
@StringRes titleId: Int = 0,
|
@StringRes titleId: Int,
|
||||||
titleString: String = "",
|
@StringRes descriptionId: Int,
|
||||||
@StringRes descriptionId: Int = 0,
|
@DrawableRes val iconId: Int,
|
||||||
descriptionString: String = "",
|
|
||||||
@DrawableRes val iconId: Int = 0,
|
|
||||||
val menuKey: Settings.MenuTag
|
val menuKey: Settings.MenuTag
|
||||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||||
override val type = TYPE_SUBMENU
|
override val type = TYPE_SUBMENU
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,15 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||||
|
|
||||||
class SwitchSetting(
|
class SwitchSetting(
|
||||||
setting: AbstractSetting,
|
setting: AbstractSetting,
|
||||||
@StringRes titleId: Int = 0,
|
titleId: Int,
|
||||||
titleString: String = "",
|
descriptionId: Int
|
||||||
@StringRes descriptionId: Int = 0,
|
) : SettingsItem(setting, titleId, descriptionId) {
|
||||||
descriptionString: String = ""
|
|
||||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
|
||||||
override val type = TYPE_SWITCH
|
override val type = TYPE_SWITCH
|
||||||
|
|
||||||
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
|
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
|
||||||
|
@ -1,300 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.graphics.drawable.Animatable2
|
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.InputDevice
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogMappingBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class InputDialogFragment : DialogFragment() {
|
|
||||||
private var inputAccepted = false
|
|
||||||
|
|
||||||
private var position: Int = 0
|
|
||||||
|
|
||||||
private lateinit var inputSetting: InputSetting
|
|
||||||
|
|
||||||
private lateinit var binding: DialogMappingBinding
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
if (settingsViewModel.clickedItem == null) dismiss()
|
|
||||||
|
|
||||||
position = requireArguments().getInt(POSITION)
|
|
||||||
|
|
||||||
InputHandler.updateControllerData()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
inputSetting = settingsViewModel.clickedItem as InputSetting
|
|
||||||
binding = DialogMappingBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setPositiveButton(android.R.string.cancel) { _, _ ->
|
|
||||||
NativeInput.stopMapping()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
.setView(binding.root)
|
|
||||||
|
|
||||||
val playButtonMapAnimation = { twoDirections: Boolean ->
|
|
||||||
val stickAnimation: AnimatedVectorDrawable
|
|
||||||
val buttonAnimation: AnimatedVectorDrawable
|
|
||||||
binding.imageStickAnimation.apply {
|
|
||||||
val anim = if (twoDirections) {
|
|
||||||
R.drawable.stick_two_direction_anim
|
|
||||||
} else {
|
|
||||||
R.drawable.stick_one_direction_anim
|
|
||||||
}
|
|
||||||
setBackgroundResource(anim)
|
|
||||||
stickAnimation = background as AnimatedVectorDrawable
|
|
||||||
}
|
|
||||||
binding.imageButtonAnimation.apply {
|
|
||||||
setBackgroundResource(R.drawable.button_anim)
|
|
||||||
buttonAnimation = background as AnimatedVectorDrawable
|
|
||||||
}
|
|
||||||
stickAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() {
|
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
|
||||||
buttonAnimation.start()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
buttonAnimation.registerAnimationCallback(object : Animatable2.AnimationCallback() {
|
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
|
||||||
stickAnimation.start()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
stickAnimation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
when (val setting = inputSetting) {
|
|
||||||
is AnalogInputSetting -> {
|
|
||||||
when (setting.nativeAnalog) {
|
|
||||||
NativeAnalog.LStick -> builder.setTitle(
|
|
||||||
getString(R.string.map_control, getString(R.string.left_stick))
|
|
||||||
)
|
|
||||||
|
|
||||||
NativeAnalog.RStick -> builder.setTitle(
|
|
||||||
getString(R.string.map_control, getString(R.string.right_stick))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.setMessage(R.string.stick_map_description)
|
|
||||||
|
|
||||||
playButtonMapAnimation.invoke(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ModifierInputSetting -> {
|
|
||||||
builder.setTitle(getString(R.string.map_control, setting.title))
|
|
||||||
.setMessage(R.string.button_map_description)
|
|
||||||
playButtonMapAnimation.invoke(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ButtonInputSetting -> {
|
|
||||||
if (setting.nativeButton == NativeButton.DUp ||
|
|
||||||
setting.nativeButton == NativeButton.DDown ||
|
|
||||||
setting.nativeButton == NativeButton.DLeft ||
|
|
||||||
setting.nativeButton == NativeButton.DRight
|
|
||||||
) {
|
|
||||||
builder.setTitle(getString(R.string.map_dpad_direction, setting.title))
|
|
||||||
} else {
|
|
||||||
builder.setTitle(getString(R.string.map_control, setting.title))
|
|
||||||
}
|
|
||||||
builder.setMessage(R.string.button_map_description)
|
|
||||||
playButtonMapAnimation.invoke(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
view.requestFocus()
|
|
||||||
view.setOnFocusChangeListener { v, hasFocus -> if (!hasFocus) v.requestFocus() }
|
|
||||||
dialog?.setOnKeyListener { _, _, keyEvent -> onKeyEvent(keyEvent) }
|
|
||||||
binding.root.setOnGenericMotionListener { _, motionEvent -> onMotionEvent(motionEvent) }
|
|
||||||
NativeInput.beginMapping(inputSetting.inputType.int)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onKeyEvent(event: KeyEvent): Boolean {
|
|
||||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
|
||||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val action = when (event.action) {
|
|
||||||
KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED
|
|
||||||
KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED
|
|
||||||
else -> return false
|
|
||||||
}
|
|
||||||
val controllerData =
|
|
||||||
InputHandler.androidControllers[event.device.controllerNumber] ?: return false
|
|
||||||
NativeInput.onGamePadButtonEvent(
|
|
||||||
controllerData.getGUID(),
|
|
||||||
controllerData.getPort(),
|
|
||||||
event.keyCode,
|
|
||||||
action
|
|
||||||
)
|
|
||||||
onInputReceived(event.device)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onMotionEvent(event: MotionEvent): Boolean {
|
|
||||||
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
|
|
||||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temp workaround for DPads that give both axis and button input. The input system can't
|
|
||||||
// take in a specific axis direction for a binding so you lose half of the directions for a DPad.
|
|
||||||
|
|
||||||
val controllerData =
|
|
||||||
InputHandler.androidControllers[event.device.controllerNumber] ?: return false
|
|
||||||
event.device.motionRanges.forEach {
|
|
||||||
NativeInput.onGamePadAxisEvent(
|
|
||||||
controllerData.getGUID(),
|
|
||||||
controllerData.getPort(),
|
|
||||||
it.axis,
|
|
||||||
event.getAxisValue(it.axis)
|
|
||||||
)
|
|
||||||
onInputReceived(event.device)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onInputReceived(device: InputDevice) {
|
|
||||||
val params = ParamPackage(NativeInput.getNextInput())
|
|
||||||
if (params.has("engine") && isInputAcceptable(params) && !inputAccepted) {
|
|
||||||
inputAccepted = true
|
|
||||||
setResult(params, device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setResult(params: ParamPackage, device: InputDevice) {
|
|
||||||
NativeInput.stopMapping()
|
|
||||||
params.set("display", "${device.name} ${params.get("port", 0)}")
|
|
||||||
when (val item = settingsViewModel.clickedItem as InputSetting) {
|
|
||||||
is ModifierInputSetting,
|
|
||||||
is ButtonInputSetting -> {
|
|
||||||
// Invert DPad up and left bindings by default
|
|
||||||
val tempSetting = inputSetting as? ButtonInputSetting
|
|
||||||
if (tempSetting != null) {
|
|
||||||
if (tempSetting.nativeButton == NativeButton.DUp ||
|
|
||||||
tempSetting.nativeButton == NativeButton.DLeft &&
|
|
||||||
params.has("axis")
|
|
||||||
) {
|
|
||||||
params.set("invert", "-")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item.setSelectedValue(params)
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
is AnalogInputSetting -> {
|
|
||||||
var analogParam = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
|
||||||
analogParam = adjustAnalogParam(params, analogParam, item.analogDirection.param)
|
|
||||||
|
|
||||||
// Invert Y-Axis by default
|
|
||||||
analogParam.set("invert_y", "-")
|
|
||||||
|
|
||||||
item.setSelectedValue(analogParam)
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun adjustAnalogParam(
|
|
||||||
inputParam: ParamPackage,
|
|
||||||
analogParam: ParamPackage,
|
|
||||||
buttonName: String
|
|
||||||
): ParamPackage {
|
|
||||||
// The poller returned a complete axis, so set all the buttons
|
|
||||||
if (inputParam.has("axis_x") && inputParam.has("axis_y")) {
|
|
||||||
return inputParam
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the current configuration has either no engine or an axis binding.
|
|
||||||
// Clears out the old binding and adds one with analog_from_button.
|
|
||||||
if (!analogParam.has("engine") || analogParam.has("axis_x") || analogParam.has("axis_y")) {
|
|
||||||
analogParam.clear()
|
|
||||||
analogParam.set("engine", "analog_from_button")
|
|
||||||
}
|
|
||||||
analogParam.set(buttonName, inputParam.serialize())
|
|
||||||
return analogParam
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isInputAcceptable(params: ParamPackage): Boolean {
|
|
||||||
if (InputHandler.registeredControllers.size == 1) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.has("motion")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentDevice = settingsViewModel.getCurrentDeviceParams(params)
|
|
||||||
if (currentDevice.get("engine", "any") == "any") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val guidMatch = params.get("guid", "") == currentDevice.get("guid", "") ||
|
|
||||||
params.get("guid", "") == currentDevice.get("guid2", "")
|
|
||||||
return params.get("engine", "") == currentDevice.get("engine", "") &&
|
|
||||||
guidMatch &&
|
|
||||||
params.get("port", 0) == currentDevice.get("port", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "InputDialogFragment"
|
|
||||||
|
|
||||||
const val POSITION = "Position"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
inputMappingViewModel: SettingsViewModel,
|
|
||||||
setting: InputSetting,
|
|
||||||
position: Int
|
|
||||||
): InputDialogFragment {
|
|
||||||
inputMappingViewModel.clickedItem = setting
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(POSITION, position)
|
|
||||||
val fragment = InputDialogFragment()
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.adapters.AbstractListAdapter
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemInputProfileBinding
|
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
class InputProfileAdapter(options: List<ProfileItem>) :
|
|
||||||
AbstractListAdapter<ProfileItem, AbstractViewHolder<ProfileItem>>(options) {
|
|
||||||
override fun onCreateViewHolder(
|
|
||||||
parent: ViewGroup,
|
|
||||||
viewType: Int
|
|
||||||
): AbstractViewHolder<ProfileItem> {
|
|
||||||
ListItemInputProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
|
||||||
.also { return InputProfileViewHolder(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class InputProfileViewHolder(val binding: ListItemInputProfileBinding) :
|
|
||||||
AbstractViewHolder<ProfileItem>(binding) {
|
|
||||||
override fun bind(model: ProfileItem) {
|
|
||||||
when (model) {
|
|
||||||
is ExistingProfileItem -> {
|
|
||||||
binding.title.text = model.name
|
|
||||||
binding.buttonNew.visibility = View.GONE
|
|
||||||
binding.buttonDelete.visibility = View.VISIBLE
|
|
||||||
binding.buttonDelete.setOnClickListener { model.deleteProfile.invoke() }
|
|
||||||
binding.buttonSave.visibility = View.VISIBLE
|
|
||||||
binding.buttonSave.setOnClickListener { model.saveProfile.invoke() }
|
|
||||||
binding.buttonLoad.visibility = View.VISIBLE
|
|
||||||
binding.buttonLoad.setOnClickListener { model.loadProfile.invoke() }
|
|
||||||
}
|
|
||||||
|
|
||||||
is NewProfileItem -> {
|
|
||||||
binding.title.text = model.name
|
|
||||||
binding.buttonNew.visibility = View.VISIBLE
|
|
||||||
binding.buttonNew.setOnClickListener { model.createNewProfile.invoke() }
|
|
||||||
binding.buttonSave.visibility = View.GONE
|
|
||||||
binding.buttonDelete.visibility = View.GONE
|
|
||||||
binding.buttonLoad.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface ProfileItem {
|
|
||||||
val name: String
|
|
||||||
}
|
|
||||||
|
|
||||||
data class NewProfileItem(
|
|
||||||
val createNewProfile: () -> Unit
|
|
||||||
) : ProfileItem {
|
|
||||||
override val name: String = YuzuApplication.appContext.getString(R.string.create_new_profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ExistingProfileItem(
|
|
||||||
override val name: String,
|
|
||||||
val deleteProfile: () -> Unit,
|
|
||||||
val saveProfile: () -> Unit,
|
|
||||||
val loadProfile: () -> Unit
|
|
||||||
) : ProfileItem
|
|
@ -1,148 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogInputProfilesBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
|
||||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class InputProfileDialogFragment : DialogFragment() {
|
|
||||||
private var position = 0
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private lateinit var binding: DialogInputProfilesBinding
|
|
||||||
|
|
||||||
private lateinit var setting: InputProfileSetting
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
position = requireArguments().getInt(POSITION)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
binding = DialogInputProfilesBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
setting = settingsViewModel.clickedItem as InputProfileSetting
|
|
||||||
val options = mutableListOf<ProfileItem>().apply {
|
|
||||||
add(
|
|
||||||
NewProfileItem(
|
|
||||||
createNewProfile = {
|
|
||||||
NewInputProfileDialogFragment.newInstance(
|
|
||||||
settingsViewModel,
|
|
||||||
setting,
|
|
||||||
position
|
|
||||||
).show(parentFragmentManager, NewInputProfileDialogFragment.TAG)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val onActionDismiss = {
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
setting.getProfileNames().forEach {
|
|
||||||
add(
|
|
||||||
ExistingProfileItem(
|
|
||||||
it,
|
|
||||||
deleteProfile = {
|
|
||||||
settingsViewModel.setShouldShowDeleteProfileDialog(it)
|
|
||||||
},
|
|
||||||
saveProfile = {
|
|
||||||
if (!setting.saveProfile(it)) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.failed_to_save_profile,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
onActionDismiss.invoke()
|
|
||||||
},
|
|
||||||
loadProfile = {
|
|
||||||
if (!setting.loadProfile(it)) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.failed_to_load_profile,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
onActionDismiss.invoke()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.listProfiles.apply {
|
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
|
||||||
adapter = InputProfileAdapter(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setView(binding.root)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
settingsViewModel.shouldShowDeleteProfileDialog.collect(viewLifecycleOwner) {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
MessageDialogFragment.newInstance(
|
|
||||||
activity = requireActivity(),
|
|
||||||
titleId = R.string.delete_input_profile,
|
|
||||||
descriptionId = R.string.delete_input_profile_description,
|
|
||||||
positiveAction = {
|
|
||||||
setting.deleteProfile(it)
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
negativeAction = {},
|
|
||||||
negativeButtonTitleId = android.R.string.cancel
|
|
||||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
settingsViewModel.setShouldShowDeleteProfileDialog("")
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "InputProfileDialogFragment"
|
|
||||||
|
|
||||||
const val POSITION = "Position"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
settingsViewModel: SettingsViewModel,
|
|
||||||
profileSetting: InputProfileSetting,
|
|
||||||
position: Int
|
|
||||||
): InputProfileDialogFragment {
|
|
||||||
settingsViewModel.clickedItem = profileSetting
|
|
||||||
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(POSITION, position)
|
|
||||||
val fragment = InputProfileDialogFragment()
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
class NewInputProfileDialogFragment : DialogFragment() {
|
|
||||||
private var position = 0
|
|
||||||
|
|
||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private lateinit var binding: DialogEditTextBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
position = requireArguments().getInt(POSITION)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
binding = DialogEditTextBinding.inflate(layoutInflater)
|
|
||||||
|
|
||||||
val setting = settingsViewModel.clickedItem as InputProfileSetting
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.enter_profile_name)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val profileName = binding.editText.text.toString()
|
|
||||||
if (!setting.isProfileNameValid(profileName)) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.invalid_profile_name,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
return@setPositiveButton
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!setting.createProfile(profileName)) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.profile_name_already_exists,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setView(binding.root)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "NewInputProfileDialogFragment"
|
|
||||||
|
|
||||||
const val POSITION = "Position"
|
|
||||||
|
|
||||||
fun newInstance(
|
|
||||||
settingsViewModel: SettingsViewModel,
|
|
||||||
profileSetting: InputProfileSetting,
|
|
||||||
position: Int
|
|
||||||
): NewInputProfileDialogFragment {
|
|
||||||
settingsViewModel.clickedItem = profileSetting
|
|
||||||
|
|
||||||
val args = Bundle()
|
|
||||||
args.putInt(POSITION, position)
|
|
||||||
val fragment = NewInputProfileDialogFragment()
|
|
||||||
fragment.arguments = args
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,16 +13,21 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.navArgs
|
import androidx.navigation.navArgs
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
|
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
@ -65,25 +70,41 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsViewModel.shouldRecreate.collect(
|
lifecycleScope.apply {
|
||||||
this,
|
launch {
|
||||||
resetState = { settingsViewModel.setShouldRecreate(false) }
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
) { if (it) recreate() }
|
settingsViewModel.shouldRecreate.collectLatest {
|
||||||
settingsViewModel.shouldNavigateBack.collect(
|
|
||||||
this,
|
|
||||||
resetState = { settingsViewModel.setShouldNavigateBack(false) }
|
|
||||||
) { if (it) navigateBack() }
|
|
||||||
settingsViewModel.shouldShowResetSettingsDialog.collect(
|
|
||||||
this,
|
|
||||||
resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) }
|
|
||||||
) {
|
|
||||||
if (it) {
|
if (it) {
|
||||||
|
settingsViewModel.setShouldRecreate(false)
|
||||||
|
recreate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.shouldNavigateBack.collectLatest {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldNavigateBack(false)
|
||||||
|
navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
|
||||||
|
if (it) {
|
||||||
|
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||||
ResetSettingsDialogFragment().show(
|
ResetSettingsDialogFragment().show(
|
||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
ResetSettingsDialogFragment.TAG
|
ResetSettingsDialogFragment.TAG
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(
|
onBackPressedDispatcher.addCallback(
|
||||||
this,
|
this,
|
||||||
@ -116,7 +137,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||||
if (isFinishing) {
|
if (isFinishing) {
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
NativeLibrary.applySettings()
|
NativeLibrary.applySettings()
|
||||||
if (args.game == null) {
|
if (args.game == null) {
|
||||||
NativeConfig.saveGlobalConfig()
|
NativeConfig.saveGlobalConfig()
|
||||||
|
@ -8,11 +8,12 @@ import android.icu.util.Calendar
|
|||||||
import android.icu.util.TimeZone
|
import android.icu.util.TimeZone
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.PopupMenu
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
@ -20,18 +21,16 @@ import androidx.recyclerview.widget.ListAdapter
|
|||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.google.android.material.datepicker.MaterialDatePicker
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
import com.google.android.material.timepicker.TimeFormat
|
import com.google.android.material.timepicker.TimeFormat
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
|
|
||||||
class SettingsAdapter(
|
class SettingsAdapter(
|
||||||
private val fragment: Fragment,
|
private val fragment: Fragment,
|
||||||
@ -42,6 +41,19 @@ class SettingsAdapter(
|
|||||||
private val settingsViewModel: SettingsViewModel
|
private val settingsViewModel: SettingsViewModel
|
||||||
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
|
get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
|
||||||
|
|
||||||
|
init {
|
||||||
|
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
settingsViewModel.adapterItemChanged.collect {
|
||||||
|
if (it != -1) {
|
||||||
|
notifyItemChanged(it)
|
||||||
|
settingsViewModel.setAdapterItemChanged(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
@ -73,23 +85,8 @@ class SettingsAdapter(
|
|||||||
RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
RunnableViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_INPUT -> {
|
|
||||||
InputViewHolder(ListItemSettingInputBinding.inflate(inflater), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_INT_SINGLE_CHOICE -> {
|
|
||||||
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_INPUT_PROFILE -> {
|
|
||||||
InputProfileViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_INPUT -> {
|
|
||||||
StringInputViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
// TODO: Create an error view since we can't return null now
|
||||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,15 +126,6 @@ class SettingsAdapter(
|
|||||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIntSingleChoiceClick(item: IntSingleChoiceSetting, position: Int) {
|
|
||||||
SettingsDialogFragment.newInstance(
|
|
||||||
settingsViewModel,
|
|
||||||
item,
|
|
||||||
SettingsItem.TYPE_INT_SINGLE_CHOICE,
|
|
||||||
position
|
|
||||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
fun onDateTimeClick(item: DateTimeSetting, position: Int) {
|
||||||
val storedTime = item.getValue() * 1000
|
val storedTime = item.getValue() * 1000
|
||||||
|
|
||||||
@ -197,214 +185,6 @@ class SettingsAdapter(
|
|||||||
fragment.view?.findNavController()?.navigate(action)
|
fragment.view?.findNavController()?.navigate(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onInputProfileClick(item: InputProfileSetting, position: Int) {
|
|
||||||
InputProfileDialogFragment.newInstance(
|
|
||||||
settingsViewModel,
|
|
||||||
item,
|
|
||||||
position
|
|
||||||
).show(fragment.childFragmentManager, InputProfileDialogFragment.TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onInputClick(item: InputSetting, position: Int) {
|
|
||||||
InputDialogFragment.newInstance(
|
|
||||||
settingsViewModel,
|
|
||||||
item,
|
|
||||||
position
|
|
||||||
).show(fragment.childFragmentManager, InputDialogFragment.TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onInputOptionsClick(anchor: View, item: InputSetting, position: Int) {
|
|
||||||
val popup = PopupMenu(context, anchor)
|
|
||||||
popup.menuInflater.inflate(R.menu.menu_input_options, popup.menu)
|
|
||||||
|
|
||||||
popup.menu.apply {
|
|
||||||
val invertAxis = findItem(R.id.invert_axis)
|
|
||||||
val invertButton = findItem(R.id.invert_button)
|
|
||||||
val toggleButton = findItem(R.id.toggle_button)
|
|
||||||
val turboButton = findItem(R.id.turbo_button)
|
|
||||||
val setThreshold = findItem(R.id.set_threshold)
|
|
||||||
val toggleAxis = findItem(R.id.toggle_axis)
|
|
||||||
when (item) {
|
|
||||||
is AnalogInputSetting -> {
|
|
||||||
val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
|
||||||
|
|
||||||
invertAxis.isVisible = true
|
|
||||||
invertAxis.isCheckable = true
|
|
||||||
invertAxis.isChecked = when (item.analogDirection) {
|
|
||||||
AnalogDirection.Left, AnalogDirection.Right -> {
|
|
||||||
params.get("invert_x", "+") == "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
AnalogDirection.Up, AnalogDirection.Down -> {
|
|
||||||
params.get("invert_y", "+") == "-"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invertAxis.setOnMenuItemClickListener {
|
|
||||||
if (item.analogDirection == AnalogDirection.Left ||
|
|
||||||
item.analogDirection == AnalogDirection.Right
|
|
||||||
) {
|
|
||||||
val invertValue = params.get("invert_x", "+") == "-"
|
|
||||||
val invertString = if (invertValue) "+" else "-"
|
|
||||||
params.set("invert_x", invertString)
|
|
||||||
} else if (
|
|
||||||
item.analogDirection == AnalogDirection.Up ||
|
|
||||||
item.analogDirection == AnalogDirection.Down
|
|
||||||
) {
|
|
||||||
val invertValue = params.get("invert_y", "+") == "-"
|
|
||||||
val invertString = if (invertValue) "+" else "-"
|
|
||||||
params.set("invert_y", invertString)
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.setOnDismissListener {
|
|
||||||
NativeInput.setStickParam(item.playerIndex, item.nativeAnalog, params)
|
|
||||||
settingsViewModel.setDatasetChanged(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is ButtonInputSetting -> {
|
|
||||||
val params = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
|
|
||||||
if (params.has("code") || params.has("button") || params.has("hat")) {
|
|
||||||
val buttonInvert = params.get("inverted", false)
|
|
||||||
invertButton.isVisible = true
|
|
||||||
invertButton.isCheckable = true
|
|
||||||
invertButton.isChecked = buttonInvert
|
|
||||||
invertButton.setOnMenuItemClickListener {
|
|
||||||
params.set("inverted", !buttonInvert)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val toggle = params.get("toggle", false)
|
|
||||||
toggleButton.isVisible = true
|
|
||||||
toggleButton.isCheckable = true
|
|
||||||
toggleButton.isChecked = toggle
|
|
||||||
toggleButton.setOnMenuItemClickListener {
|
|
||||||
params.set("toggle", !toggle)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val turbo = params.get("turbo", false)
|
|
||||||
turboButton.isVisible = true
|
|
||||||
turboButton.isCheckable = true
|
|
||||||
turboButton.isChecked = turbo
|
|
||||||
turboButton.setOnMenuItemClickListener {
|
|
||||||
params.set("turbo", !turbo)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else if (params.has("axis")) {
|
|
||||||
val axisInvert = params.get("invert", "+") == "-"
|
|
||||||
invertAxis.isVisible = true
|
|
||||||
invertAxis.isCheckable = true
|
|
||||||
invertAxis.isChecked = axisInvert
|
|
||||||
invertAxis.setOnMenuItemClickListener {
|
|
||||||
params.set("invert", if (!axisInvert) "-" else "+")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val buttonInvert = params.get("inverted", false)
|
|
||||||
invertButton.isVisible = true
|
|
||||||
invertButton.isCheckable = true
|
|
||||||
invertButton.isChecked = buttonInvert
|
|
||||||
invertButton.setOnMenuItemClickListener {
|
|
||||||
params.set("inverted", !buttonInvert)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
setThreshold.isVisible = true
|
|
||||||
val thresholdSetting = object : AbstractIntSetting {
|
|
||||||
override val key = ""
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int =
|
|
||||||
(params.get("threshold", 0.5f) * 100).toInt()
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
params.set("threshold", value.toFloat() / 100)
|
|
||||||
NativeInput.setButtonParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeButton,
|
|
||||||
params
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = 50
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getInt(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
}
|
|
||||||
setThreshold.setOnMenuItemClickListener {
|
|
||||||
onSliderClick(
|
|
||||||
SliderSetting(thresholdSetting, R.string.set_threshold),
|
|
||||||
position
|
|
||||||
)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val axisToggle = params.get("toggle", false)
|
|
||||||
toggleAxis.isVisible = true
|
|
||||||
toggleAxis.isCheckable = true
|
|
||||||
toggleAxis.isChecked = axisToggle
|
|
||||||
toggleAxis.setOnMenuItemClickListener {
|
|
||||||
params.set("toggle", !axisToggle)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.setOnDismissListener {
|
|
||||||
NativeInput.setButtonParam(item.playerIndex, item.nativeButton, params)
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is ModifierInputSetting -> {
|
|
||||||
val stickParams = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
|
||||||
val modifierParams = ParamPackage(stickParams.get("modifier", ""))
|
|
||||||
|
|
||||||
val invert = modifierParams.get("inverted", false)
|
|
||||||
invertButton.isVisible = true
|
|
||||||
invertButton.isCheckable = true
|
|
||||||
invertButton.isChecked = invert
|
|
||||||
invertButton.setOnMenuItemClickListener {
|
|
||||||
modifierParams.set("inverted", !invert)
|
|
||||||
stickParams.set("modifier", modifierParams.serialize())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val toggle = modifierParams.get("toggle", false)
|
|
||||||
toggleButton.isVisible = true
|
|
||||||
toggleButton.isCheckable = true
|
|
||||||
toggleButton.isChecked = toggle
|
|
||||||
toggleButton.setOnMenuItemClickListener {
|
|
||||||
modifierParams.set("toggle", !toggle)
|
|
||||||
stickParams.set("modifier", modifierParams.serialize())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.setOnDismissListener {
|
|
||||||
NativeInput.setStickParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeAnalog,
|
|
||||||
stickParams
|
|
||||||
)
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
popup.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onStringInputClick(item: StringInputSetting, position: Int) {
|
|
||||||
SettingsDialogFragment.newInstance(
|
|
||||||
settingsViewModel,
|
|
||||||
item,
|
|
||||||
SettingsItem.TYPE_STRING_INPUT,
|
|
||||||
position
|
|
||||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onLongClick(item: SettingsItem, position: Int): Boolean {
|
fun onLongClick(item: SettingsItem, position: Int): Boolean {
|
||||||
SettingsDialogFragment.newInstance(
|
SettingsDialogFragment.newInstance(
|
||||||
settingsViewModel,
|
settingsViewModel,
|
||||||
|
@ -13,17 +13,20 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class SettingsFragment : Fragment() {
|
class SettingsFragment : Fragment() {
|
||||||
private lateinit var presenter: SettingsFragmentPresenter
|
private lateinit var presenter: SettingsFragmentPresenter
|
||||||
@ -42,12 +45,6 @@ class SettingsFragment : Fragment() {
|
|||||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
|
|
||||||
val playerIndex = getPlayerIndex()
|
|
||||||
if (playerIndex != -1) {
|
|
||||||
NativeInput.loadInputProfiles()
|
|
||||||
NativeInput.reloadInputDevices()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -59,9 +56,9 @@ class SettingsFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||||
presenter = SettingsFragmentPresenter(
|
presenter = SettingsFragmentPresenter(
|
||||||
settingsViewModel,
|
settingsViewModel,
|
||||||
@ -74,17 +71,7 @@ class SettingsFragment : Fragment() {
|
|||||||
) {
|
) {
|
||||||
args.game!!.title
|
args.game!!.title
|
||||||
} else {
|
} else {
|
||||||
when (args.menuTag) {
|
getString(args.menuTag.titleId)
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> Settings.getPlayerString(1)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> Settings.getPlayerString(2)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> Settings.getPlayerString(3)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> Settings.getPlayerString(4)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> Settings.getPlayerString(5)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> Settings.getPlayerString(6)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> Settings.getPlayerString(7)
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> Settings.getPlayerString(8)
|
|
||||||
else -> getString(args.menuTag.titleId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
binding.listSettings.apply {
|
binding.listSettings.apply {
|
||||||
adapter = settingsAdapter
|
adapter = settingsAdapter
|
||||||
@ -95,37 +82,16 @@ class SettingsFragment : Fragment() {
|
|||||||
settingsViewModel.setShouldNavigateBack(true)
|
settingsViewModel.setShouldNavigateBack(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsViewModel.shouldReloadSettingsList.collect(
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
viewLifecycleOwner,
|
launch {
|
||||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
) { if (it) presenter.loadSettingsList() }
|
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||||
settingsViewModel.adapterItemChanged.collect(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
resetState = { settingsViewModel.setAdapterItemChanged(-1) }
|
|
||||||
) { if (it != -1) settingsAdapter?.notifyItemChanged(it) }
|
|
||||||
settingsViewModel.datasetChanged.collect(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
resetState = { settingsViewModel.setDatasetChanged(false) }
|
|
||||||
) { if (it) settingsAdapter?.notifyDataSetChanged() }
|
|
||||||
settingsViewModel.reloadListAndNotifyDataset.collect(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
resetState = { settingsViewModel.setReloadListAndNotifyDataset(false) }
|
|
||||||
) { if (it) presenter.loadSettingsList(true) }
|
|
||||||
settingsViewModel.shouldShowResetInputDialog.collect(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
resetState = { settingsViewModel.setShouldShowResetInputDialog(false) }
|
|
||||||
) {
|
|
||||||
if (it) {
|
if (it) {
|
||||||
MessageDialogFragment.newInstance(
|
settingsViewModel.setShouldReloadSettingsList(false)
|
||||||
activity = requireActivity(),
|
presenter.loadSettingsList()
|
||||||
titleId = R.string.reset_mapping,
|
}
|
||||||
descriptionId = R.string.reset_mapping_description,
|
}
|
||||||
positiveAction = {
|
}
|
||||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
},
|
|
||||||
negativeAction = {}
|
|
||||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,19 +115,6 @@ class SettingsFragment : Fragment() {
|
|||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlayerIndex(): Int =
|
|
||||||
when (args.menuTag) {
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_ONE -> 0
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_TWO -> 1
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_THREE -> 2
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_FOUR -> 3
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_FIVE -> 4
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_SIX -> 5
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_SEVEN -> 6
|
|
||||||
Settings.MenuTag.SECTION_INPUT_PLAYER_EIGHT -> 7
|
|
||||||
else -> -1
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setInsets() {
|
private fun setInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(
|
ViewCompat.setOnApplyWindowInsetsListener(
|
||||||
binding.root
|
binding.root
|
||||||
|
@ -3,17 +3,11 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
@ -21,22 +15,18 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SettingsFragmentPresenter(
|
class SettingsFragmentPresenter(
|
||||||
private val settingsViewModel: SettingsViewModel,
|
private val settingsViewModel: SettingsViewModel,
|
||||||
private val adapter: SettingsAdapter,
|
private val adapter: SettingsAdapter,
|
||||||
private var menuTag: MenuTag
|
private var menuTag: Settings.MenuTag
|
||||||
) {
|
) {
|
||||||
private var settingsList = ArrayList<SettingsItem>()
|
private var settingsList = ArrayList<SettingsItem>()
|
||||||
|
|
||||||
private val context get() = YuzuApplication.appContext
|
|
||||||
|
|
||||||
// Extension for altering settings list based on each setting's properties
|
// Extension for altering settings list based on each setting's properties
|
||||||
fun ArrayList<SettingsItem>.add(key: String) {
|
fun ArrayList<SettingsItem>.add(key: String) {
|
||||||
val item = SettingsItem.settingsItems[key]!!
|
val item = SettingsItem.settingsItems[key]!!
|
||||||
@ -63,90 +53,73 @@ class SettingsFragmentPresenter(
|
|||||||
add(item)
|
add(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allows you to show/hide abstract settings based on the paired setting key
|
|
||||||
fun ArrayList<SettingsItem>.addAbstract(item: SettingsItem) {
|
|
||||||
val pairedSettingKey = item.setting.pairedSettingKey
|
|
||||||
if (pairedSettingKey.isNotEmpty()) {
|
|
||||||
val pairedSettingsItem =
|
|
||||||
this.firstOrNull { it.setting.key == pairedSettingKey } ?: return
|
|
||||||
val pairedSetting = pairedSettingsItem.setting as AbstractBooleanSetting
|
|
||||||
if (!pairedSetting.getBoolean(!NativeConfig.isPerGameConfigLoaded())) return
|
|
||||||
}
|
|
||||||
add(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onViewCreated() {
|
fun onViewCreated() {
|
||||||
loadSettingsList()
|
loadSettingsList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
fun loadSettingsList() {
|
||||||
fun loadSettingsList(notifyDataSetChanged: Boolean = false) {
|
|
||||||
val sl = ArrayList<SettingsItem>()
|
val sl = ArrayList<SettingsItem>()
|
||||||
when (menuTag) {
|
when (menuTag) {
|
||||||
MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||||
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||||
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||||
MenuTag.SECTION_INPUT_PLAYER_TWO -> addInputPlayer(sl, 1)
|
else -> {
|
||||||
MenuTag.SECTION_INPUT_PLAYER_THREE -> addInputPlayer(sl, 2)
|
val context = YuzuApplication.appContext
|
||||||
MenuTag.SECTION_INPUT_PLAYER_FOUR -> addInputPlayer(sl, 3)
|
Toast.makeText(
|
||||||
MenuTag.SECTION_INPUT_PLAYER_FIVE -> addInputPlayer(sl, 4)
|
context,
|
||||||
MenuTag.SECTION_INPUT_PLAYER_SIX -> addInputPlayer(sl, 5)
|
context.getString(R.string.unimplemented_menu),
|
||||||
MenuTag.SECTION_INPUT_PLAYER_SEVEN -> addInputPlayer(sl, 6)
|
Toast.LENGTH_SHORT
|
||||||
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
).show()
|
||||||
MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
return
|
||||||
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
}
|
||||||
}
|
}
|
||||||
settingsList = sl
|
settingsList = sl
|
||||||
adapter.submitList(settingsList) {
|
adapter.submitList(settingsList)
|
||||||
if (notifyDataSetChanged) {
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
titleId = R.string.preferences_system,
|
R.string.preferences_system,
|
||||||
descriptionId = R.string.preferences_system_description,
|
R.string.preferences_system_description,
|
||||||
iconId = R.drawable.ic_system_settings,
|
R.drawable.ic_system_settings,
|
||||||
menuKey = MenuTag.SECTION_SYSTEM
|
Settings.MenuTag.SECTION_SYSTEM
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
titleId = R.string.preferences_graphics,
|
R.string.preferences_graphics,
|
||||||
descriptionId = R.string.preferences_graphics_description,
|
R.string.preferences_graphics_description,
|
||||||
iconId = R.drawable.ic_graphics,
|
R.drawable.ic_graphics,
|
||||||
menuKey = MenuTag.SECTION_RENDERER
|
Settings.MenuTag.SECTION_RENDERER
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
titleId = R.string.preferences_audio,
|
R.string.preferences_audio,
|
||||||
descriptionId = R.string.preferences_audio_description,
|
R.string.preferences_audio_description,
|
||||||
iconId = R.drawable.ic_audio,
|
R.drawable.ic_audio,
|
||||||
menuKey = MenuTag.SECTION_AUDIO
|
Settings.MenuTag.SECTION_AUDIO
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
SubmenuSetting(
|
SubmenuSetting(
|
||||||
titleId = R.string.preferences_debug,
|
R.string.preferences_debug,
|
||||||
descriptionId = R.string.preferences_debug_description,
|
R.string.preferences_debug_description,
|
||||||
iconId = R.drawable.ic_code,
|
R.drawable.ic_code,
|
||||||
menuKey = MenuTag.SECTION_DEBUG
|
Settings.MenuTag.SECTION_DEBUG
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
RunnableSetting(
|
RunnableSetting(
|
||||||
titleId = R.string.reset_to_default,
|
R.string.reset_to_default,
|
||||||
descriptionId = R.string.reset_to_default_description,
|
R.string.reset_to_default_description,
|
||||||
isRunnable = !NativeLibrary.isRunning(),
|
false,
|
||||||
iconId = R.drawable.ic_restore
|
R.drawable.ic_restore
|
||||||
) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
|
) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -154,7 +127,6 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(StringSetting.DEVICE_NAME.key)
|
|
||||||
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
||||||
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
||||||
add(BooleanSetting.USE_DOCKED_MODE.key)
|
add(BooleanSetting.USE_DOCKED_MODE.key)
|
||||||
@ -171,12 +143,10 @@ class SettingsFragmentPresenter(
|
|||||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||||
add(IntSetting.RENDERER_VSYNC.key)
|
add(IntSetting.RENDERER_VSYNC.key)
|
||||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||||
add(IntSetting.FSR_SHARPENING_SLIDER.key)
|
|
||||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||||
add(IntSetting.MAX_ANISOTROPY.key)
|
add(IntSetting.MAX_ANISOTROPY.key)
|
||||||
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
|
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
|
||||||
add(IntSetting.RENDERER_ASPECT_RATIO.key)
|
add(IntSetting.RENDERER_ASPECT_RATIO.key)
|
||||||
add(IntSetting.VERTICAL_ALIGNMENT.key)
|
|
||||||
add(BooleanSetting.PICTURE_IN_PICTURE.key)
|
add(BooleanSetting.PICTURE_IN_PICTURE.key)
|
||||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||||
@ -192,671 +162,6 @@ class SettingsFragmentPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addInputSettings(sl: ArrayList<SettingsItem>) {
|
|
||||||
settingsViewModel.currentDevice = 0
|
|
||||||
|
|
||||||
if (NativeConfig.isPerGameConfigLoaded()) {
|
|
||||||
NativeInput.loadInputProfiles()
|
|
||||||
val profiles = NativeInput.getInputProfileNames().toMutableList()
|
|
||||||
profiles.add(0, "")
|
|
||||||
val prettyProfiles = profiles.toTypedArray()
|
|
||||||
prettyProfiles[0] =
|
|
||||||
context.getString(R.string.use_global_input_configuration)
|
|
||||||
sl.apply {
|
|
||||||
for (i in 0 until 8) {
|
|
||||||
add(
|
|
||||||
IntSingleChoiceSetting(
|
|
||||||
getPerGameProfileSetting(profiles, i),
|
|
||||||
titleString = getPlayerProfileString(i + 1),
|
|
||||||
choices = prettyProfiles,
|
|
||||||
values = IntArray(profiles.size) { it }.toTypedArray()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val getConnectedIcon: (Int) -> Int = { playerIndex: Int ->
|
|
||||||
if (NativeInput.getIsConnected(playerIndex)) {
|
|
||||||
R.drawable.ic_controller
|
|
||||||
} else {
|
|
||||||
R.drawable.ic_controller_disconnected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inputSettings = NativeConfig.getInputSettings(true)
|
|
||||||
sl.apply {
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(1),
|
|
||||||
descriptionString = inputSettings[0].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_ONE,
|
|
||||||
iconId = getConnectedIcon(0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(2),
|
|
||||||
descriptionString = inputSettings[1].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_TWO,
|
|
||||||
iconId = getConnectedIcon(1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(3),
|
|
||||||
descriptionString = inputSettings[2].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_THREE,
|
|
||||||
iconId = getConnectedIcon(2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(4),
|
|
||||||
descriptionString = inputSettings[3].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_FOUR,
|
|
||||||
iconId = getConnectedIcon(3)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(5),
|
|
||||||
descriptionString = inputSettings[4].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_FIVE,
|
|
||||||
iconId = getConnectedIcon(4)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(6),
|
|
||||||
descriptionString = inputSettings[5].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_SIX,
|
|
||||||
iconId = getConnectedIcon(5)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(7),
|
|
||||||
descriptionString = inputSettings[6].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_SEVEN,
|
|
||||||
iconId = getConnectedIcon(6)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
SubmenuSetting(
|
|
||||||
titleString = Settings.getPlayerString(8),
|
|
||||||
descriptionString = inputSettings[7].profileName,
|
|
||||||
menuKey = MenuTag.SECTION_INPUT_PLAYER_EIGHT,
|
|
||||||
iconId = getConnectedIcon(7)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPlayerProfileString(player: Int): String =
|
|
||||||
context.getString(R.string.player_num_profile, player)
|
|
||||||
|
|
||||||
private fun getPerGameProfileSetting(
|
|
||||||
profiles: List<String>,
|
|
||||||
playerIndex: Int
|
|
||||||
): AbstractIntSetting {
|
|
||||||
return object : AbstractIntSetting {
|
|
||||||
private val players
|
|
||||||
get() = NativeConfig.getInputSettings(false)
|
|
||||||
|
|
||||||
override val key = ""
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int {
|
|
||||||
val currentProfile = players[playerIndex].profileName
|
|
||||||
profiles.forEachIndexed { i, profile ->
|
|
||||||
if (profile == currentProfile) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
NativeInput.loadPerGameConfiguration(playerIndex, value, profiles[value])
|
|
||||||
NativeInput.connectControllers(playerIndex)
|
|
||||||
NativeConfig.saveControlPlayerValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = 0
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
|
|
||||||
override var global = true
|
|
||||||
|
|
||||||
override val isRuntimeModifiable = true
|
|
||||||
|
|
||||||
override val isSaveable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addInputPlayer(sl: ArrayList<SettingsItem>, playerIndex: Int) {
|
|
||||||
sl.apply {
|
|
||||||
val connectedSetting = object : AbstractBooleanSetting {
|
|
||||||
override val key = "connected"
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
|
||||||
NativeInput.getIsConnected(playerIndex)
|
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) =
|
|
||||||
NativeInput.connectControllers(playerIndex, value)
|
|
||||||
|
|
||||||
override val defaultValue = playerIndex == 0
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getBoolean(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
|
||||||
}
|
|
||||||
add(SwitchSetting(connectedSetting, R.string.connected))
|
|
||||||
|
|
||||||
val styleTags = NativeInput.getSupportedStyleTags(playerIndex)
|
|
||||||
val npadType = object : AbstractIntSetting {
|
|
||||||
override val key = "npad_type"
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int {
|
|
||||||
val styleIndex = NativeInput.getStyleIndex(playerIndex)
|
|
||||||
return styleTags.indexOfFirst { it == styleIndex }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
NativeInput.setStyleIndex(playerIndex, styleTags[value])
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = NpadStyleIndex.Fullkey.int
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
override val pairedSettingKey: String = "connected"
|
|
||||||
}
|
|
||||||
addAbstract(
|
|
||||||
IntSingleChoiceSetting(
|
|
||||||
npadType,
|
|
||||||
titleId = R.string.controller_type,
|
|
||||||
choices = styleTags.map { context.getString(it.nameId) }
|
|
||||||
.toTypedArray(),
|
|
||||||
values = IntArray(styleTags.size) { it }.toTypedArray()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
InputHandler.updateControllerData()
|
|
||||||
|
|
||||||
val autoMappingSetting = object : AbstractIntSetting {
|
|
||||||
override val key = "auto_mapping_device"
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int = -1
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
val registeredController = InputHandler.registeredControllers[value + 1]
|
|
||||||
val displayName = registeredController.get(
|
|
||||||
"display",
|
|
||||||
context.getString(R.string.unknown)
|
|
||||||
)
|
|
||||||
NativeInput.updateMappingsWithDefault(
|
|
||||||
playerIndex,
|
|
||||||
registeredController,
|
|
||||||
displayName
|
|
||||||
)
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.attempted_auto_map, displayName),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = -1
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean) = getInt().toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
|
|
||||||
override val isRuntimeModifiable: Boolean = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val unknownString = context.getString(R.string.unknown)
|
|
||||||
val prettyAutoMappingControllerList = InputHandler.registeredControllers.mapNotNull {
|
|
||||||
val port = it.get("port", -1)
|
|
||||||
return@mapNotNull if (port == 100 || port == -1) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
it.get("display", unknownString)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
add(
|
|
||||||
IntSingleChoiceSetting(
|
|
||||||
autoMappingSetting,
|
|
||||||
titleId = R.string.auto_map,
|
|
||||||
descriptionId = R.string.auto_map_description,
|
|
||||||
choices = prettyAutoMappingControllerList,
|
|
||||||
values = IntArray(prettyAutoMappingControllerList.size) { it }.toTypedArray()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val mappingFilterSetting = object : AbstractIntSetting {
|
|
||||||
override val key = "mapping_filter"
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int = settingsViewModel.currentDevice
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
settingsViewModel.currentDevice = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = 0
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean) = getInt().toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
|
|
||||||
override val isRuntimeModifiable: Boolean = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val prettyControllerList = InputHandler.registeredControllers.mapNotNull {
|
|
||||||
return@mapNotNull if (it.get("port", 0) == 100) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
it.get("display", unknownString)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
add(
|
|
||||||
IntSingleChoiceSetting(
|
|
||||||
mappingFilterSetting,
|
|
||||||
titleId = R.string.input_mapping_filter,
|
|
||||||
descriptionId = R.string.input_mapping_filter_description,
|
|
||||||
choices = prettyControllerList,
|
|
||||||
values = IntArray(prettyControllerList.size) { it }.toTypedArray()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
add(InputProfileSetting(playerIndex))
|
|
||||||
add(
|
|
||||||
RunnableSetting(titleId = R.string.reset_to_default, isRunnable = true) {
|
|
||||||
settingsViewModel.setShouldShowResetInputDialog(true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val styleIndex = NativeInput.getStyleIndex(playerIndex)
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
when (styleIndex) {
|
|
||||||
NpadStyleIndex.Fullkey,
|
|
||||||
NpadStyleIndex.Handheld,
|
|
||||||
NpadStyleIndex.JoyconDual -> {
|
|
||||||
add(HeaderSetting(R.string.buttons))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home))
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.Capture,
|
|
||||||
R.string.button_capture
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.JoyconLeft -> {
|
|
||||||
add(HeaderSetting(R.string.buttons))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Minus, R.string.button_minus))
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.Capture,
|
|
||||||
R.string.button_capture
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.JoyconRight -> {
|
|
||||||
add(HeaderSetting(R.string.buttons))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.button_plus))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Home, R.string.button_home))
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.GameCube -> {
|
|
||||||
add(HeaderSetting(R.string.buttons))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.A, R.string.button_a))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.B, R.string.button_b))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.X, R.string.button_x))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Y, R.string.button_y))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.Plus, R.string.start_pause))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (styleIndex) {
|
|
||||||
NpadStyleIndex.Fullkey,
|
|
||||||
NpadStyleIndex.Handheld,
|
|
||||||
NpadStyleIndex.JoyconDual,
|
|
||||||
NpadStyleIndex.JoyconLeft -> {
|
|
||||||
add(HeaderSetting(R.string.dpad))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.DUp, R.string.up))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.DDown, R.string.down))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.DLeft, R.string.left))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.DRight, R.string.right))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left stick
|
|
||||||
when (styleIndex) {
|
|
||||||
NpadStyleIndex.Fullkey,
|
|
||||||
NpadStyleIndex.Handheld,
|
|
||||||
NpadStyleIndex.JoyconDual,
|
|
||||||
NpadStyleIndex.JoyconLeft -> {
|
|
||||||
add(HeaderSetting(R.string.left_stick))
|
|
||||||
addAll(getStickDirections(playerIndex, NativeAnalog.LStick))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.LStick, R.string.pressed))
|
|
||||||
addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick))
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.GameCube -> {
|
|
||||||
add(HeaderSetting(R.string.control_stick))
|
|
||||||
addAll(getStickDirections(playerIndex, NativeAnalog.LStick))
|
|
||||||
addAll(getExtraStickSettings(playerIndex, NativeAnalog.LStick))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right stick
|
|
||||||
when (styleIndex) {
|
|
||||||
NpadStyleIndex.Fullkey,
|
|
||||||
NpadStyleIndex.Handheld,
|
|
||||||
NpadStyleIndex.JoyconDual,
|
|
||||||
NpadStyleIndex.JoyconRight -> {
|
|
||||||
add(HeaderSetting(R.string.right_stick))
|
|
||||||
addAll(getStickDirections(playerIndex, NativeAnalog.RStick))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.RStick, R.string.pressed))
|
|
||||||
addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick))
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.GameCube -> {
|
|
||||||
add(HeaderSetting(R.string.c_stick))
|
|
||||||
addAll(getStickDirections(playerIndex, NativeAnalog.RStick))
|
|
||||||
addAll(getExtraStickSettings(playerIndex, NativeAnalog.RStick))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// L/R, ZL/ZR, and SL/SR
|
|
||||||
when (styleIndex) {
|
|
||||||
NpadStyleIndex.Fullkey,
|
|
||||||
NpadStyleIndex.Handheld -> {
|
|
||||||
add(HeaderSetting(R.string.triggers))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.JoyconDual -> {
|
|
||||||
add(HeaderSetting(R.string.triggers))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SLLeft,
|
|
||||||
R.string.button_sl_left
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SRLeft,
|
|
||||||
R.string.button_sr_left
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SLRight,
|
|
||||||
R.string.button_sl_right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SRRight,
|
|
||||||
R.string.button_sr_right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.JoyconLeft -> {
|
|
||||||
add(HeaderSetting(R.string.triggers))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.L, R.string.button_l))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_zl))
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SLLeft,
|
|
||||||
R.string.button_sl_left
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SRLeft,
|
|
||||||
R.string.button_sr_left
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.JoyconRight -> {
|
|
||||||
add(HeaderSetting(R.string.triggers))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_r))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_zr))
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SLRight,
|
|
||||||
R.string.button_sl_right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
|
||||||
ButtonInputSetting(
|
|
||||||
playerIndex,
|
|
||||||
NativeButton.SRRight,
|
|
||||||
R.string.button_sr_right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NpadStyleIndex.GameCube -> {
|
|
||||||
add(HeaderSetting(R.string.triggers))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.R, R.string.button_z))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZL, R.string.button_l))
|
|
||||||
add(ButtonInputSetting(playerIndex, NativeButton.ZR, R.string.button_r))
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
// No-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add(HeaderSetting(R.string.vibration))
|
|
||||||
val vibrationEnabledSetting = object : AbstractBooleanSetting {
|
|
||||||
override val key = "vibration"
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
|
||||||
NativeConfig.getInputSettings(true)[playerIndex].vibrationEnabled
|
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) {
|
|
||||||
val settings = NativeConfig.getInputSettings(true)
|
|
||||||
settings[playerIndex].vibrationEnabled = value
|
|
||||||
NativeConfig.setInputSettings(settings, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = true
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getBoolean(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
|
||||||
}
|
|
||||||
add(SwitchSetting(vibrationEnabledSetting, R.string.vibration))
|
|
||||||
|
|
||||||
val useSystemVibratorSetting = object : AbstractBooleanSetting {
|
|
||||||
override val key = ""
|
|
||||||
|
|
||||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
|
||||||
NativeConfig.getInputSettings(true)[playerIndex].useSystemVibrator
|
|
||||||
|
|
||||||
override fun setBoolean(value: Boolean) {
|
|
||||||
val settings = NativeConfig.getInputSettings(true)
|
|
||||||
settings[playerIndex].useSystemVibrator = value
|
|
||||||
NativeConfig.setInputSettings(settings, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = playerIndex == 0
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getBoolean(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setBoolean(defaultValue)
|
|
||||||
|
|
||||||
override val pairedSettingKey: String = "vibration"
|
|
||||||
}
|
|
||||||
addAbstract(SwitchSetting(useSystemVibratorSetting, R.string.use_system_vibrator))
|
|
||||||
|
|
||||||
val vibrationStrengthSetting = object : AbstractIntSetting {
|
|
||||||
override val key = ""
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int =
|
|
||||||
NativeConfig.getInputSettings(true)[playerIndex].vibrationStrength
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
val settings = NativeConfig.getInputSettings(true)
|
|
||||||
settings[playerIndex].vibrationStrength = value
|
|
||||||
NativeConfig.setInputSettings(settings, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = 100
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getInt(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(defaultValue)
|
|
||||||
|
|
||||||
override val pairedSettingKey: String = "vibration"
|
|
||||||
}
|
|
||||||
addAbstract(
|
|
||||||
SliderSetting(vibrationStrengthSetting, R.string.vibration_strength, units = "%")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function for creating AbstractIntSettings for modifier range/stick range/stick deadzones
|
|
||||||
private fun getStickIntSettingFromParam(
|
|
||||||
playerIndex: Int,
|
|
||||||
paramName: String,
|
|
||||||
stick: NativeAnalog,
|
|
||||||
defaultValue: Float
|
|
||||||
): AbstractIntSetting =
|
|
||||||
object : AbstractIntSetting {
|
|
||||||
val params get() = NativeInput.getStickParam(playerIndex, stick)
|
|
||||||
|
|
||||||
override val key = ""
|
|
||||||
|
|
||||||
override fun getInt(needsGlobal: Boolean): Int =
|
|
||||||
(params.get(paramName, defaultValue) * 100).toInt()
|
|
||||||
|
|
||||||
override fun setInt(value: Int) {
|
|
||||||
val tempParams = params
|
|
||||||
tempParams.set(paramName, value.toFloat() / 100)
|
|
||||||
NativeInput.setStickParam(playerIndex, stick, tempParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val defaultValue = (defaultValue * 100).toInt()
|
|
||||||
|
|
||||||
override fun getValueAsString(needsGlobal: Boolean): String =
|
|
||||||
getInt(needsGlobal).toString()
|
|
||||||
|
|
||||||
override fun reset() = setInt(this.defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getExtraStickSettings(
|
|
||||||
playerIndex: Int,
|
|
||||||
nativeAnalog: NativeAnalog
|
|
||||||
): List<SettingsItem> {
|
|
||||||
val stickIsController =
|
|
||||||
NativeInput.isController(NativeInput.getStickParam(playerIndex, nativeAnalog))
|
|
||||||
val modifierRangeSetting =
|
|
||||||
getStickIntSettingFromParam(playerIndex, "modifier_scale", nativeAnalog, 0.5f)
|
|
||||||
val stickRangeSetting =
|
|
||||||
getStickIntSettingFromParam(playerIndex, "range", nativeAnalog, 0.95f)
|
|
||||||
val stickDeadzoneSetting =
|
|
||||||
getStickIntSettingFromParam(playerIndex, "deadzone", nativeAnalog, 0.15f)
|
|
||||||
|
|
||||||
val out = mutableListOf<SettingsItem>().apply {
|
|
||||||
if (stickIsController) {
|
|
||||||
add(SliderSetting(stickRangeSetting, titleId = R.string.range, min = 25, max = 150))
|
|
||||||
add(SliderSetting(stickDeadzoneSetting, R.string.deadzone))
|
|
||||||
} else {
|
|
||||||
add(ModifierInputSetting(playerIndex, NativeAnalog.LStick, R.string.modifier))
|
|
||||||
add(SliderSetting(modifierRangeSetting, R.string.modifier_range))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getStickDirections(player: Int, stick: NativeAnalog): List<AnalogInputSetting> =
|
|
||||||
listOf(
|
|
||||||
AnalogInputSetting(
|
|
||||||
player,
|
|
||||||
stick,
|
|
||||||
AnalogDirection.Up,
|
|
||||||
R.string.up
|
|
||||||
),
|
|
||||||
AnalogInputSetting(
|
|
||||||
player,
|
|
||||||
stick,
|
|
||||||
AnalogDirection.Down,
|
|
||||||
R.string.down
|
|
||||||
),
|
|
||||||
AnalogInputSetting(
|
|
||||||
player,
|
|
||||||
stick,
|
|
||||||
AnalogDirection.Left,
|
|
||||||
R.string.left
|
|
||||||
),
|
|
||||||
AnalogInputSetting(
|
|
||||||
player,
|
|
||||||
stick,
|
|
||||||
AnalogDirection.Right,
|
|
||||||
R.string.right
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||||
@ -879,18 +184,20 @@ class SettingsFragmentPresenter(
|
|||||||
add(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
theme,
|
theme,
|
||||||
titleId = R.string.change_app_theme,
|
R.string.change_app_theme,
|
||||||
choicesId = R.array.themeEntriesA12,
|
0,
|
||||||
valuesId = R.array.themeValuesA12
|
R.array.themeEntriesA12,
|
||||||
|
R.array.themeValuesA12
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
add(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
theme,
|
theme,
|
||||||
titleId = R.string.change_app_theme,
|
R.string.change_app_theme,
|
||||||
choicesId = R.array.themeEntries,
|
0,
|
||||||
valuesId = R.array.themeValues
|
R.array.themeEntries,
|
||||||
|
R.array.themeValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -919,9 +226,10 @@ class SettingsFragmentPresenter(
|
|||||||
add(
|
add(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
themeMode,
|
themeMode,
|
||||||
titleId = R.string.change_theme_mode,
|
R.string.change_theme_mode,
|
||||||
choicesId = R.array.themeModeEntries,
|
0,
|
||||||
valuesId = R.array.themeModeValues
|
R.array.themeModeEntries,
|
||||||
|
R.array.themeModeValues
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -952,8 +260,8 @@ class SettingsFragmentPresenter(
|
|||||||
add(
|
add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
blackBackgrounds,
|
blackBackgrounds,
|
||||||
titleId = R.string.use_black_backgrounds,
|
R.string.use_black_backgrounds,
|
||||||
descriptionId = R.string.use_black_backgrounds_description
|
R.string.use_black_backgrounds_description
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -21,17 +21,28 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as DateTimeSetting
|
setting = item as DateTimeSetting
|
||||||
binding.textSettingName.text = item.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = item.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
binding.textSettingValue.setVisible(true)
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.textSettingValue.visibility = View.VISIBLE
|
||||||
val epochTime = setting.getValue()
|
val epochTime = setting.getValue()
|
||||||
val instant = Instant.ofEpochMilli(epochTime * 1000)
|
val instant = Instant.ofEpochMilli(epochTime * 1000)
|
||||||
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
|
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
|
||||||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||||
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
||||||
|
|
||||||
binding.buttonClear.setVisible(setting.clearable)
|
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||||
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
binding.buttonClear.setOnClickListener {
|
binding.buttonClear.setOnClickListener {
|
||||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
binding.textHeaderName.text = item.title
|
binding.textHeaderName.setText(item.nameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputProfileSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class InputProfileViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
|
||||||
SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: InputProfileSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as InputProfileSetting
|
|
||||||
binding.textSettingName.text = setting.title
|
|
||||||
binding.textSettingValue.text =
|
|
||||||
setting.getCurrentProfile().ifEmpty { binding.root.context.getString(R.string.not_set) }
|
|
||||||
|
|
||||||
binding.textSettingDescription.setVisible(false)
|
|
||||||
binding.buttonClear.setVisible(false)
|
|
||||||
binding.icon.setVisible(false)
|
|
||||||
binding.buttonClear.setVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) =
|
|
||||||
adapter.onInputProfileClick(setting, bindingAdapterPosition)
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean = false
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.InputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.ModifierInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class InputViewHolder(val binding: ListItemSettingInputBinding, adapter: SettingsAdapter) :
|
|
||||||
SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: InputSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as InputSetting
|
|
||||||
binding.textSettingName.text = setting.title
|
|
||||||
binding.textSettingValue.text = setting.getSelectedValue()
|
|
||||||
|
|
||||||
when (item) {
|
|
||||||
is AnalogInputSetting -> {
|
|
||||||
val param = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
|
||||||
binding.buttonOptions.setVisible(
|
|
||||||
param.get("engine", "") == "analog_from_button" ||
|
|
||||||
param.has("axis_x") || param.has("axis_y")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ButtonInputSetting -> {
|
|
||||||
val param = NativeInput.getButtonParam(item.playerIndex, item.nativeButton)
|
|
||||||
binding.buttonOptions.setVisible(
|
|
||||||
param.has("code") || param.has("button") || param.has("hat") ||
|
|
||||||
param.has("axis")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ModifierInputSetting -> {
|
|
||||||
val params = NativeInput.getStickParam(item.playerIndex, item.nativeAnalog)
|
|
||||||
binding.buttonOptions.setVisible(params.has("modifier"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonOptions.setOnClickListener(null)
|
|
||||||
binding.buttonOptions.setOnClickListener {
|
|
||||||
adapter.onInputOptionsClick(binding.buttonOptions, setting, bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) =
|
|
||||||
adapter.onInputClick(setting, bindingAdapterPosition)
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean =
|
|
||||||
adapter.onLongClick(setting, bindingAdapterPosition)
|
|
||||||
}
|
|
@ -5,11 +5,11 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.RunnableSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -17,28 +17,34 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as RunnableSetting
|
setting = item as RunnableSetting
|
||||||
binding.icon.setVisible(setting.iconId != 0)
|
if (item.iconId != 0) {
|
||||||
if (setting.iconId != 0) {
|
binding.icon.visibility = View.VISIBLE
|
||||||
binding.icon.setImageDrawable(
|
binding.icon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
binding.icon.resources,
|
binding.icon.resources,
|
||||||
setting.iconId,
|
item.iconId,
|
||||||
binding.icon.context.theme
|
binding.icon.context.theme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
binding.icon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.textSettingName.text = setting.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = item.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
binding.textSettingValue.setVisible(false)
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
binding.buttonClear.setVisible(false)
|
} else {
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.textSettingValue.visibility = View.GONE
|
||||||
|
binding.buttonClear.visibility = View.GONE
|
||||||
|
|
||||||
setStyle(setting.isEditable, binding)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
if (setting.isRunnable) {
|
if (!setting.isRuntimeRunnable && !NativeLibrary.isRunning()) {
|
||||||
setting.runnable.invoke()
|
setting.runnable.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,11 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -18,13 +17,16 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item
|
setting = item
|
||||||
binding.textSettingName.text = setting.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = item.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
binding.textSettingValue.setVisible(true)
|
binding.textSettingValue.visibility = View.VISIBLE
|
||||||
when (item) {
|
if (item is SingleChoiceSetting) {
|
||||||
is SingleChoiceSetting -> {
|
|
||||||
val resMgr = binding.textSettingValue.context.resources
|
val resMgr = binding.textSettingValue.context.resources
|
||||||
val values = resMgr.getIntArray(item.valuesId)
|
val values = resMgr.getIntArray(item.valuesId)
|
||||||
for (i in values.indices) {
|
for (i in values.indices) {
|
||||||
@ -33,21 +35,22 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (item is StringSingleChoiceSetting) {
|
||||||
|
for (i in item.values.indices) {
|
||||||
|
if (item.values[i] == item.getSelectedValue()) {
|
||||||
|
binding.textSettingValue.text = item.choices[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is StringSingleChoiceSetting -> {
|
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||||
binding.textSettingValue.text = item.getSelectedValue()
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
is IntSingleChoiceSetting -> {
|
|
||||||
binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (binding.textSettingValue.text.isEmpty()) {
|
|
||||||
binding.textSettingValue.setVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.buttonClear.setVisible(setting.clearable)
|
|
||||||
binding.buttonClear.setOnClickListener {
|
binding.buttonClear.setOnClickListener {
|
||||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
@ -60,26 +63,17 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
when (setting) {
|
if (setting is SingleChoiceSetting) {
|
||||||
is SingleChoiceSetting -> adapter.onSingleChoiceClick(
|
adapter.onSingleChoiceClick(
|
||||||
setting as SingleChoiceSetting,
|
(setting as SingleChoiceSetting),
|
||||||
bindingAdapterPosition
|
bindingAdapterPosition
|
||||||
)
|
)
|
||||||
|
} else if (setting is StringSingleChoiceSetting) {
|
||||||
is StringSingleChoiceSetting -> {
|
|
||||||
adapter.onStringSingleChoiceClick(
|
adapter.onStringSingleChoiceClick(
|
||||||
setting as StringSingleChoiceSetting,
|
(setting as StringSingleChoiceSetting),
|
||||||
bindingAdapterPosition
|
bindingAdapterPosition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is IntSingleChoiceSetting -> {
|
|
||||||
adapter.onIntSingleChoiceClick(
|
|
||||||
setting as IntSingleChoiceSetting,
|
|
||||||
bindingAdapterPosition
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
|
@ -9,7 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -17,17 +17,27 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as SliderSetting
|
setting = item as SliderSetting
|
||||||
binding.textSettingName.text = setting.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = setting.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
binding.textSettingValue.setVisible(true)
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.textSettingValue.visibility = View.VISIBLE
|
||||||
binding.textSettingValue.text = String.format(
|
binding.textSettingValue.text = String.format(
|
||||||
binding.textSettingValue.context.getString(R.string.value_with_units),
|
binding.textSettingValue.context.getString(R.string.value_with_units),
|
||||||
setting.getSelectedValue(),
|
setting.getSelectedValue(),
|
||||||
setting.units
|
setting.units
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.buttonClear.setVisible(setting.clearable)
|
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||||
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
binding.buttonClear.setOnClickListener {
|
binding.buttonClear.setOnClickListener {
|
||||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
|
||||||
SettingViewHolder(binding.root, adapter) {
|
|
||||||
private lateinit var setting: StringInputSetting
|
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
|
||||||
setting = item as StringInputSetting
|
|
||||||
binding.textSettingName.text = setting.title
|
|
||||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
|
||||||
binding.textSettingDescription.text = setting.description
|
|
||||||
binding.textSettingValue.setVisible(true)
|
|
||||||
binding.textSettingValue.text = setting.getSelectedValue()
|
|
||||||
|
|
||||||
binding.buttonClear.setVisible(setting.clearable)
|
|
||||||
binding.buttonClear.setOnClickListener {
|
|
||||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
setStyle(setting.isEditable, binding)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
|
||||||
if (setting.isEditable) {
|
|
||||||
adapter.onStringInputClick(setting, bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
|
||||||
if (setting.isEditable) {
|
|
||||||
return adapter.onLongClick(setting, bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,34 +9,39 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SubmenuSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
|
|
||||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
private lateinit var setting: SubmenuSetting
|
private lateinit var item: SubmenuSetting
|
||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as SubmenuSetting
|
this.item = item as SubmenuSetting
|
||||||
binding.icon.setVisible(setting.iconId != 0)
|
if (item.iconId != 0) {
|
||||||
if (setting.iconId != 0) {
|
binding.icon.visibility = View.VISIBLE
|
||||||
binding.icon.setImageDrawable(
|
binding.icon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
binding.icon.resources,
|
binding.icon.resources,
|
||||||
setting.iconId,
|
item.iconId,
|
||||||
binding.icon.context.theme
|
binding.icon.context.theme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
binding.icon.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.textSettingName.text = setting.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = setting.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
binding.textSettingValue.setVisible(false)
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
binding.buttonClear.setVisible(false)
|
} else {
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
binding.textSettingValue.visibility = View.GONE
|
||||||
|
binding.buttonClear.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
adapter.onSubmenuClick(setting)
|
adapter.onSubmenuClick(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(clicked: View): Boolean {
|
override fun onLongClick(clicked: View): Boolean {
|
||||||
|
@ -9,7 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -18,17 +18,28 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||||||
|
|
||||||
override fun bind(item: SettingsItem) {
|
override fun bind(item: SettingsItem) {
|
||||||
setting = item as SwitchSetting
|
setting = item as SwitchSetting
|
||||||
binding.textSettingName.text = setting.title
|
binding.textSettingName.setText(item.nameId)
|
||||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
if (item.descriptionId != 0) {
|
||||||
binding.textSettingDescription.text = setting.description
|
binding.textSettingDescription.setText(item.descriptionId)
|
||||||
|
binding.textSettingDescription.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.textSettingDescription.text = ""
|
||||||
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
binding.switchWidget.setOnCheckedChangeListener(null)
|
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||||
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
||||||
adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition)
|
adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.buttonClear.setVisible(setting.clearable)
|
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||||
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
binding.buttonClear.setOnClickListener {
|
binding.buttonClear.setOnClickListener {
|
||||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -15,6 +16,9 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -28,7 +32,6 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
|
|||||||
import org.yuzu.yuzu_emu.utils.AddonUtil
|
import org.yuzu.yuzu_emu.utils.AddonUtil
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
|
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class AddonsFragment : Fragment() {
|
class AddonsFragment : Fragment() {
|
||||||
@ -57,6 +60,8 @@ class AddonsFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
||||||
@ -73,41 +78,53 @@ class AddonsFragment : Fragment() {
|
|||||||
adapter = AddonAdapter(addonViewModel)
|
adapter = AddonAdapter(addonViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
addonViewModel.addonList.collect {
|
||||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||||
}
|
}
|
||||||
addonViewModel.showModInstallPicker.collect(
|
}
|
||||||
viewLifecycleOwner,
|
}
|
||||||
resetState = { addonViewModel.showModInstallPicker(false) }
|
launch {
|
||||||
) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
addonViewModel.showModNoticeDialog.collect(
|
addonViewModel.showModInstallPicker.collect {
|
||||||
viewLifecycleOwner,
|
if (it) {
|
||||||
resetState = { addonViewModel.showModNoticeDialog(false) }
|
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||||
) {
|
addonViewModel.showModInstallPicker(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
addonViewModel.showModNoticeDialog.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
titleId = R.string.addon_notice,
|
titleId = R.string.addon_notice,
|
||||||
descriptionId = R.string.addon_notice_description,
|
descriptionId = R.string.addon_notice_description,
|
||||||
dismissible = false,
|
positiveAction = { addonViewModel.showModInstallPicker(true) }
|
||||||
positiveAction = { addonViewModel.showModInstallPicker(true) },
|
|
||||||
negativeAction = {},
|
|
||||||
negativeButtonTitleId = R.string.close
|
|
||||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
addonViewModel.showModNoticeDialog(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addonViewModel.addonToDelete.collect(
|
}
|
||||||
viewLifecycleOwner,
|
}
|
||||||
resetState = { addonViewModel.setAddonToDelete(null) }
|
launch {
|
||||||
) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
addonViewModel.addonToDelete.collect {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
MessageDialogFragment.newInstance(
|
MessageDialogFragment.newInstance(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
titleId = R.string.confirm_uninstall,
|
titleId = R.string.confirm_uninstall,
|
||||||
descriptionId = R.string.confirm_uninstall_description,
|
descriptionId = R.string.confirm_uninstall_description,
|
||||||
positiveAction = { addonViewModel.onDeleteAddon(it) },
|
positiveAction = { addonViewModel.onDeleteAddon(it) }
|
||||||
negativeAction = {}
|
|
||||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||||
|
addonViewModel.setAddonToDelete(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
|
|
||||||
class CoreErrorDialogFragment : DialogFragment() {
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
|
||||||
MaterialAlertDialogBuilder(requireActivity())
|
|
||||||
.setTitle(requireArguments().getString(TITLE))
|
|
||||||
.setMessage(requireArguments().getString(MESSAGE))
|
|
||||||
.setPositiveButton(R.string.continue_button, null)
|
|
||||||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
|
||||||
NativeLibrary.coreErrorAlertResult = false
|
|
||||||
synchronized(NativeLibrary.coreErrorAlertLock) {
|
|
||||||
NativeLibrary.coreErrorAlertLock.notify()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
|
||||||
super.onDismiss(dialog)
|
|
||||||
NativeLibrary.coreErrorAlertResult = true
|
|
||||||
synchronized(NativeLibrary.coreErrorAlertLock) { NativeLibrary.coreErrorAlertLock.notify() }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TITLE = "Title"
|
|
||||||
const val MESSAGE = "Message"
|
|
||||||
|
|
||||||
fun newInstance(title: String, message: String): CoreErrorDialogFragment {
|
|
||||||
val frag = CoreErrorDialogFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(TITLE, title)
|
|
||||||
args.putString(MESSAGE, message)
|
|
||||||
frag.arguments = args
|
|
||||||
return frag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -13,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
@ -31,7 +35,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
|||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@ -60,6 +63,8 @@ class DriverManagerFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||||
@ -84,8 +89,15 @@ class DriverManagerFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driverViewModel.showClearButton.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
driverViewModel.showClearButton.collect {
|
||||||
|
binding.toolbarDrivers.menu
|
||||||
|
.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,11 +10,14 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class DriversLoadingDialogFragment : DialogFragment() {
|
class DriversLoadingDialogFragment : DialogFragment() {
|
||||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||||
@ -41,7 +44,13 @@ class DriversLoadingDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -15,9 +15,7 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Rational
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
@ -26,12 +24,14 @@ import androidx.core.content.res.ResourcesCompat
|
|||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.window.layout.FoldingFeature
|
import androidx.window.layout.FoldingFeature
|
||||||
@ -39,6 +39,9 @@ import androidx.window.layout.WindowInfoTracker
|
|||||||
import androidx.window.layout.WindowLayoutInfo
|
import androidx.window.layout.WindowLayoutInfo
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
@ -49,7 +52,6 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
|||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation
|
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationOrientation
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings.EmulationVerticalAlignment
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
@ -57,7 +59,6 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel
|
|||||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import java.lang.NullPointerException
|
import java.lang.NullPointerException
|
||||||
|
|
||||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
@ -85,6 +86,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
if (context is EmulationActivity) {
|
if (context is EmulationActivity) {
|
||||||
emulationActivity = context
|
emulationActivity = context
|
||||||
NativeLibrary.setEmulationActivity(context)
|
NativeLibrary.setEmulationActivity(context)
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
WindowInfoTracker.getOrCreate(context)
|
||||||
|
.windowLayoutInfo(context)
|
||||||
|
.collect { updateFoldableLayout(context, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||||
}
|
}
|
||||||
@ -155,6 +164,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
if (requireActivity().isFinishing) {
|
if (requireActivity().isFinishing) {
|
||||||
@ -262,15 +273,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_controls -> {
|
|
||||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
|
||||||
null,
|
|
||||||
Settings.MenuTag.SECTION_INPUT
|
|
||||||
)
|
|
||||||
binding.root.findNavController().navigate(action)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.menu_overlay_controls -> {
|
R.id.menu_overlay_controls -> {
|
||||||
showOverlayOptions()
|
showOverlayOptions()
|
||||||
true
|
true
|
||||||
@ -335,11 +337,19 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
binding.loadingTitle.isSelected = true
|
binding.loadingTitle.isSelected = true
|
||||||
binding.loadingText.isSelected = true
|
binding.loadingText.isSelected = true
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
WindowInfoTracker.getOrCreate(requireContext())
|
WindowInfoTracker.getOrCreate(requireContext())
|
||||||
.windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) {
|
.windowLayoutInfo(requireActivity())
|
||||||
|
.collect {
|
||||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||||
}
|
}
|
||||||
emulationViewModel.shaderProgress.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.shaderProgress.collectLatest {
|
||||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||||
binding.loadingProgressIndicator.isIndeterminate = false
|
binding.loadingProgressIndicator.isIndeterminate = false
|
||||||
|
|
||||||
@ -353,16 +363,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
binding.loadingProgressIndicator.isIndeterminate = true
|
binding.loadingProgressIndicator.isIndeterminate = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.totalShaders.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.totalShaders.collectLatest {
|
||||||
binding.loadingProgressIndicator.max = it
|
binding.loadingProgressIndicator.max = it
|
||||||
}
|
}
|
||||||
emulationViewModel.shaderMessage.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.shaderMessage.collectLatest {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
binding.loadingText.text = it
|
binding.loadingText.text = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
emulationViewModel.emulationStarted.collect(viewLifecycleOwner) {
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.isInteractionAllowed.collect {
|
||||||
|
if (it) {
|
||||||
|
startEmulation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.emulationStarted.collectLatest {
|
||||||
if (it) {
|
if (it) {
|
||||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||||
@ -375,7 +405,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
updateThermalOverlay()
|
updateThermalOverlay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.isEmulationStopping.collectLatest {
|
||||||
if (it) {
|
if (it) {
|
||||||
binding.loadingText.setText(R.string.shutting_down)
|
binding.loadingText.setText(R.string.shutting_down)
|
||||||
ViewUtils.showView(binding.loadingIndicator)
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
@ -383,7 +417,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
ViewUtils.hideView(binding.showFpsText)
|
ViewUtils.hideView(binding.showFpsText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.drawerOpen.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
binding.drawerLayout.open()
|
binding.drawerLayout.open()
|
||||||
binding.inGameMenu.requestFocus()
|
binding.inGameMenu.requestFocus()
|
||||||
@ -391,7 +429,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.programChanged.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.programChanged.collect {
|
||||||
if (it != 0) {
|
if (it != 0) {
|
||||||
emulationViewModel.setEmulationStarted(false)
|
emulationViewModel.setEmulationStarted(false)
|
||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
@ -401,7 +443,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
ViewUtils.showView(binding.loadingIndicator)
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
emulationViewModel.emulationStopped.collect {
|
||||||
if (it && emulationViewModel.programChanged.value != -1) {
|
if (it && emulationViewModel.programChanged.value != -1) {
|
||||||
if (perfStatsUpdater != null) {
|
if (perfStatsUpdater != null) {
|
||||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||||
@ -411,9 +457,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
emulationViewModel.setEmulationStopped(false)
|
emulationViewModel.setEmulationStopped(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
}
|
||||||
if (it) startEmulation()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,12 +487,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
}
|
}
|
||||||
if (showInputOverlay) {
|
if (showInputOverlay) {
|
||||||
binding.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.surfaceInputOverlay.setVisible(
|
if (showInputOverlay && emulationViewModel.emulationStarted.value) {
|
||||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||||
)
|
} else {
|
||||||
|
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
if (!isInFoldableLayout) {
|
if (!isInFoldableLayout) {
|
||||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||||
@ -484,9 +531,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShowFpsOverlay() {
|
private fun updateShowFpsOverlay() {
|
||||||
val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
|
||||||
binding.showFpsText.setVisible(showOverlay)
|
|
||||||
if (showOverlay) {
|
|
||||||
val SYSTEM_FPS = 0
|
val SYSTEM_FPS = 0
|
||||||
val FPS = 1
|
val FPS = 1
|
||||||
val FRAMETIME = 2
|
val FRAMETIME = 2
|
||||||
@ -506,17 +551,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
||||||
|
binding.showFpsText.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
if (perfStatsUpdater != null) {
|
if (perfStatsUpdater != null) {
|
||||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||||
}
|
}
|
||||||
|
binding.showFpsText.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateThermalOverlay() {
|
private fun updateThermalOverlay() {
|
||||||
val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) {
|
||||||
binding.showThermalsText.setVisible(showOverlay)
|
|
||||||
if (showOverlay) {
|
|
||||||
thermalStatsUpdater = {
|
thermalStatsUpdater = {
|
||||||
if (emulationViewModel.emulationStarted.value &&
|
if (emulationViewModel.emulationStarted.value &&
|
||||||
!emulationViewModel.isEmulationStopping.value
|
!emulationViewModel.isEmulationStopping.value
|
||||||
@ -538,10 +583,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
|
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
|
||||||
|
binding.showThermalsText.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
if (thermalStatsUpdater != null) {
|
if (thermalStatsUpdater != null) {
|
||||||
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
|
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
|
||||||
}
|
}
|
||||||
|
binding.showThermalsText.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,46 +617,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateScreenLayout() {
|
private fun updateScreenLayout() {
|
||||||
val verticalAlignment =
|
|
||||||
EmulationVerticalAlignment.from(IntSetting.VERTICAL_ALIGNMENT.getInt())
|
|
||||||
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) {
|
|
||||||
0 -> Rational(16, 9)
|
|
||||||
1 -> Rational(4, 3)
|
|
||||||
2 -> Rational(21, 9)
|
|
||||||
3 -> Rational(16, 10)
|
|
||||||
else -> null // Best fit
|
|
||||||
}
|
|
||||||
when (verticalAlignment) {
|
|
||||||
EmulationVerticalAlignment.Top -> {
|
|
||||||
binding.surfaceEmulation.setAspectRatio(aspectRatio)
|
|
||||||
val params = FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
||||||
binding.surfaceEmulation.layoutParams = params
|
|
||||||
}
|
|
||||||
|
|
||||||
EmulationVerticalAlignment.Center -> {
|
|
||||||
binding.surfaceEmulation.setAspectRatio(null)
|
binding.surfaceEmulation.setAspectRatio(null)
|
||||||
binding.surfaceEmulation.updateLayoutParams {
|
|
||||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
height = ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EmulationVerticalAlignment.Bottom -> {
|
|
||||||
binding.surfaceEmulation.setAspectRatio(aspectRatio)
|
|
||||||
val params =
|
|
||||||
FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
||||||
)
|
|
||||||
params.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
|
||||||
binding.surfaceEmulation.layoutParams = params
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emulationState.updateSurface()
|
|
||||||
emulationActivity?.buildPictureInPictureParams()
|
emulationActivity?.buildPictureInPictureParams()
|
||||||
updateOrientation()
|
updateOrientation()
|
||||||
}
|
}
|
||||||
@ -810,12 +818,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.doneControlConfig.setVisible(true)
|
binding.doneControlConfig.visibility = View.VISIBLE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopConfiguringControls() {
|
private fun stopConfiguringControls() {
|
||||||
binding.doneControlConfig.setVisible(false)
|
binding.doneControlConfig.visibility = View.GONE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||||
// Unlock the orientation if it was locked for editing
|
// Unlock the orientation if it was locked for editing
|
||||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
|
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
|
||||||
|
@ -13,6 +13,9 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
@ -24,7 +27,6 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
|
|||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class GameFoldersFragment : Fragment() {
|
class GameFoldersFragment : Fragment() {
|
||||||
private var _binding: FragmentFoldersBinding? = null
|
private var _binding: FragmentFoldersBinding? = null
|
||||||
@ -68,9 +70,13 @@ class GameFoldersFragment : Fragment() {
|
|||||||
adapter = FolderAdapter(requireActivity(), gamesViewModel)
|
adapter = FolderAdapter(requireActivity(), gamesViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesViewModel.folders.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
gamesViewModel.folders.collect {
|
||||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val mainActivity = requireActivity() as MainActivity
|
val mainActivity = requireActivity() as MainActivity
|
||||||
binding.buttonAdd.setOnClickListener {
|
binding.buttonAdd.setOnClickListener {
|
||||||
|
@ -27,7 +27,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
|
|||||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.GameMetadata
|
import org.yuzu.yuzu_emu.utils.GameMetadata
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
|
|
||||||
class GameInfoFragment : Fragment() {
|
class GameInfoFragment : Fragment() {
|
||||||
@ -86,7 +85,7 @@ class GameInfoFragment : Fragment() {
|
|||||||
copyToClipboard(getString(R.string.developer), args.game.developer)
|
copyToClipboard(getString(R.string.developer), args.game.developer)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
developer.setVisible(false)
|
developer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
version.setHint(R.string.version)
|
version.setHint(R.string.version)
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.ShortcutInfo
|
import android.content.pm.ShortcutInfo
|
||||||
import android.content.pm.ShortcutManager
|
import android.content.pm.ShortcutManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -16,7 +18,9 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
@ -42,9 +46,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
|||||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -74,6 +76,8 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||||
@ -103,7 +107,13 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
|
|
||||||
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
|
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
|
||||||
binding.title.text = args.game.title
|
binding.title.text = args.game.title
|
||||||
binding.title.marquee()
|
binding.title.postDelayed(
|
||||||
|
{
|
||||||
|
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
binding.title.isSelected = true
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
|
||||||
binding.buttonStart.setOnClickListener {
|
binding.buttonStart.setOnClickListener {
|
||||||
LaunchGameDialogFragment.newInstance(args.game)
|
LaunchGameDialogFragment.newInstance(args.game)
|
||||||
@ -112,14 +122,28 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
|
|
||||||
reloadList()
|
reloadList()
|
||||||
|
|
||||||
homeViewModel.openImportSaves.collect(
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
viewLifecycleOwner,
|
launch {
|
||||||
resetState = { homeViewModel.setOpenImportSaves(false) }
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
) { if (it) importSaves.launch(arrayOf("application/zip")) }
|
homeViewModel.openImportSaves.collect {
|
||||||
homeViewModel.reloadPropertiesList.collect(
|
if (it) {
|
||||||
viewLifecycleOwner,
|
importSaves.launch(arrayOf("application/zip"))
|
||||||
resetState = { homeViewModel.reloadPropertiesList(false) }
|
homeViewModel.setOpenImportSaves(false)
|
||||||
) { if (it) reloadList() }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
homeViewModel.reloadPropertiesList.collect {
|
||||||
|
if (it) {
|
||||||
|
reloadList()
|
||||||
|
homeViewModel.reloadPropertiesList(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
@ -219,9 +243,7 @@ class GamePropertiesFragment : Fragment() {
|
|||||||
requireActivity(),
|
requireActivity(),
|
||||||
titleId = R.string.delete_save_data,
|
titleId = R.string.delete_save_data,
|
||||||
descriptionId = R.string.delete_save_data_warning_description,
|
descriptionId = R.string.delete_save_data_warning_description,
|
||||||
positiveButtonTitleId = android.R.string.cancel,
|
positiveAction = {
|
||||||
negativeButtonTitleId = android.R.string.ok,
|
|
||||||
negativeAction = {
|
|
||||||
File(args.game.saveDir).deleteRecursively()
|
File(args.game.saveDir).deleteRecursively()
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
YuzuApplication.appContext,
|
YuzuApplication.appContext,
|
||||||
|
@ -89,20 +89,6 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
|
||||||
HomeSetting(
|
|
||||||
R.string.preferences_controls,
|
|
||||||
R.string.preferences_controls_description,
|
|
||||||
R.drawable.ic_controller,
|
|
||||||
{
|
|
||||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
|
||||||
null,
|
|
||||||
Settings.MenuTag.SECTION_INPUT
|
|
||||||
)
|
|
||||||
binding.root.findNavController().navigate(action)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.gpu_driver_manager,
|
R.string.gpu_driver_manager,
|
||||||
|
@ -14,6 +14,9 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
@ -32,7 +35,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
|||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
@ -73,12 +75,16 @@ class InstallableFragment : Fragment() {
|
|||||||
binding.root.findNavController().popBackStack()
|
binding.root.findNavController().popBackStack()
|
||||||
}
|
}
|
||||||
|
|
||||||
homeViewModel.openImportSaves.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
homeViewModel.openImportSaves.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
importSaves.launch(arrayOf("application/zip"))
|
importSaves.launch(arrayOf("application/zip"))
|
||||||
homeViewModel.setOpenImportSaves(false)
|
homeViewModel.setOpenImportSaves(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val installables = listOf(
|
val installables = listOf(
|
||||||
Installable(
|
Installable(
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -15,52 +16,18 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.model.MessageDialogViewModel
|
import org.yuzu.yuzu_emu.model.MessageDialogViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
|
||||||
|
|
||||||
class MessageDialogFragment : DialogFragment() {
|
class MessageDialogFragment : DialogFragment() {
|
||||||
private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
|
private val messageDialogViewModel: MessageDialogViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val titleId = requireArguments().getInt(TITLE_ID)
|
val titleId = requireArguments().getInt(TITLE_ID)
|
||||||
val title = if (titleId != 0) {
|
val titleString = requireArguments().getString(TITLE_STRING)!!
|
||||||
getString(titleId)
|
|
||||||
} else {
|
|
||||||
requireArguments().getString(TITLE_STRING)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
|
val descriptionId = requireArguments().getInt(DESCRIPTION_ID)
|
||||||
val description = if (descriptionId != 0) {
|
val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!!
|
||||||
getString(descriptionId)
|
|
||||||
} else {
|
|
||||||
requireArguments().getString(DESCRIPTION_STRING)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
val positiveButtonId = requireArguments().getInt(POSITIVE_BUTTON_TITLE_ID)
|
|
||||||
val positiveButtonString = requireArguments().getString(POSITIVE_BUTTON_TITLE_STRING)!!
|
|
||||||
val positiveButton = if (positiveButtonId != 0) {
|
|
||||||
getString(positiveButtonId)
|
|
||||||
} else if (positiveButtonString.isNotEmpty()) {
|
|
||||||
positiveButtonString
|
|
||||||
} else if (messageDialogViewModel.positiveAction != null) {
|
|
||||||
getString(android.R.string.ok)
|
|
||||||
} else {
|
|
||||||
getString(R.string.close)
|
|
||||||
}
|
|
||||||
|
|
||||||
val negativeButtonId = requireArguments().getInt(NEGATIVE_BUTTON_TITLE_ID)
|
|
||||||
val negativeButtonString = requireArguments().getString(NEGATIVE_BUTTON_TITLE_STRING)!!
|
|
||||||
val negativeButton = if (negativeButtonId != 0) {
|
|
||||||
getString(negativeButtonId)
|
|
||||||
} else if (negativeButtonString.isNotEmpty()) {
|
|
||||||
negativeButtonString
|
|
||||||
} else {
|
|
||||||
getString(android.R.string.cancel)
|
|
||||||
}
|
|
||||||
|
|
||||||
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
||||||
val dismissible = requireArguments().getBoolean(DISMISSIBLE)
|
val dismissible = requireArguments().getBoolean(DISMISSIBLE)
|
||||||
val clearPositiveAction = requireArguments().getBoolean(CLEAR_ACTIONS)
|
val clearPositiveAction = requireArguments().getBoolean(CLEAR_POSITIVE_ACTION)
|
||||||
val showNegativeButton = requireArguments().getBoolean(SHOW_NEGATIVE_BUTTON)
|
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
|
||||||
@ -68,19 +35,21 @@ class MessageDialogFragment : DialogFragment() {
|
|||||||
messageDialogViewModel.positiveAction = null
|
messageDialogViewModel.positiveAction = null
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(positiveButton) { _, _ ->
|
if (messageDialogViewModel.positiveAction == null) {
|
||||||
|
builder.setPositiveButton(R.string.close, null)
|
||||||
|
} else {
|
||||||
|
builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
messageDialogViewModel.positiveAction?.invoke()
|
messageDialogViewModel.positiveAction?.invoke()
|
||||||
}
|
}.setNegativeButton(android.R.string.cancel, null)
|
||||||
if (messageDialogViewModel.negativeAction != null || showNegativeButton) {
|
|
||||||
builder.setNegativeButton(negativeButton) { _, _ ->
|
|
||||||
messageDialogViewModel.negativeAction?.invoke()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title.isNotEmpty()) builder.setTitle(title)
|
if (titleId != 0) builder.setTitle(titleId)
|
||||||
if (description.isNotEmpty()) {
|
if (titleString.isNotEmpty()) builder.setTitle(titleString)
|
||||||
builder.setMessage(Html.fromHtml(description, Html.FROM_HTML_MODE_LEGACY))
|
|
||||||
|
if (descriptionId != 0) {
|
||||||
|
builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY))
|
||||||
}
|
}
|
||||||
|
if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString)
|
||||||
|
|
||||||
if (helpLinkId != 0) {
|
if (helpLinkId != 0) {
|
||||||
builder.setNeutralButton(R.string.learn_more) { _, _ ->
|
builder.setNeutralButton(R.string.learn_more) { _, _ ->
|
||||||
@ -107,41 +76,8 @@ class MessageDialogFragment : DialogFragment() {
|
|||||||
private const val DESCRIPTION_STRING = "DescriptionString"
|
private const val DESCRIPTION_STRING = "DescriptionString"
|
||||||
private const val HELP_LINK = "Link"
|
private const val HELP_LINK = "Link"
|
||||||
private const val DISMISSIBLE = "Dismissible"
|
private const val DISMISSIBLE = "Dismissible"
|
||||||
private const val CLEAR_ACTIONS = "ClearActions"
|
private const val CLEAR_POSITIVE_ACTION = "ClearPositiveAction"
|
||||||
private const val POSITIVE_BUTTON_TITLE_ID = "PositiveButtonTitleId"
|
|
||||||
private const val POSITIVE_BUTTON_TITLE_STRING = "PositiveButtonTitleString"
|
|
||||||
private const val SHOW_NEGATIVE_BUTTON = "ShowNegativeButton"
|
|
||||||
private const val NEGATIVE_BUTTON_TITLE_ID = "NegativeButtonTitleId"
|
|
||||||
private const val NEGATIVE_BUTTON_TITLE_STRING = "NegativeButtonTitleString"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new [MessageDialogFragment] instance.
|
|
||||||
* @param activity Activity that will hold a [MessageDialogViewModel] instance if using
|
|
||||||
* [positiveAction] or [negativeAction].
|
|
||||||
* @param titleId String resource ID that will be used for the title. [titleString] used if 0.
|
|
||||||
* @param titleString String that will be used for the title. No title is set if empty.
|
|
||||||
* @param descriptionId String resource ID that will be used for the description.
|
|
||||||
* [descriptionString] used if 0.
|
|
||||||
* @param descriptionString String that will be used for the description.
|
|
||||||
* No description is set if empty.
|
|
||||||
* @param helpLinkId String resource ID that contains a help link. Will be added as a neutral
|
|
||||||
* button with the title R.string.help.
|
|
||||||
* @param dismissible Whether the dialog is dismissible or not. Typically used to ensure that
|
|
||||||
* the user clicks on one of the dialog buttons before closing.
|
|
||||||
* @param positiveButtonTitleId String resource ID that will be used for the positive button.
|
|
||||||
* [positiveButtonTitleString] used if 0.
|
|
||||||
* @param positiveButtonTitleString String that will be used for the positive button.
|
|
||||||
* android.R.string.close used if empty. android.R.string.ok will be used if [positiveAction]
|
|
||||||
* is not null.
|
|
||||||
* @param positiveAction Lambda to run when the positive button is clicked.
|
|
||||||
* @param showNegativeButton Normally the negative button isn't shown if there is no
|
|
||||||
* [negativeAction] set. This can override that behavior to always show a button.
|
|
||||||
* @param negativeButtonTitleId String resource ID that will be used for the negative button.
|
|
||||||
* [negativeButtonTitleString] used if 0.
|
|
||||||
* @param negativeButtonTitleString String that will be used for the negative button.
|
|
||||||
* android.R.string.cancel used if empty.
|
|
||||||
* @param negativeAction Lambda to run when the negative button is clicked
|
|
||||||
*/
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
activity: FragmentActivity? = null,
|
activity: FragmentActivity? = null,
|
||||||
titleId: Int = 0,
|
titleId: Int = 0,
|
||||||
@ -150,27 +86,16 @@ class MessageDialogFragment : DialogFragment() {
|
|||||||
descriptionString: String = "",
|
descriptionString: String = "",
|
||||||
helpLinkId: Int = 0,
|
helpLinkId: Int = 0,
|
||||||
dismissible: Boolean = true,
|
dismissible: Boolean = true,
|
||||||
positiveButtonTitleId: Int = 0,
|
positiveAction: (() -> Unit)? = null
|
||||||
positiveButtonTitleString: String = "",
|
|
||||||
positiveAction: (() -> Unit)? = null,
|
|
||||||
showNegativeButton: Boolean = false,
|
|
||||||
negativeButtonTitleId: Int = 0,
|
|
||||||
negativeButtonTitleString: String = "",
|
|
||||||
negativeAction: (() -> Unit)? = null
|
|
||||||
): MessageDialogFragment {
|
): MessageDialogFragment {
|
||||||
var clearActions = false
|
var clearPositiveAction = false
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
|
ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply {
|
||||||
clear()
|
clear()
|
||||||
this.positiveAction = positiveAction
|
this.positiveAction = positiveAction
|
||||||
this.negativeAction = negativeAction
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clearActions = true
|
clearPositiveAction = true
|
||||||
}
|
|
||||||
|
|
||||||
if (activity == null && (positiveAction == null || negativeAction == null)) {
|
|
||||||
Log.warning("[$TAG] Tried to set action with no activity!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = MessageDialogFragment()
|
val dialog = MessageDialogFragment()
|
||||||
@ -181,12 +106,7 @@ class MessageDialogFragment : DialogFragment() {
|
|||||||
putString(DESCRIPTION_STRING, descriptionString)
|
putString(DESCRIPTION_STRING, descriptionString)
|
||||||
putInt(HELP_LINK, helpLinkId)
|
putInt(HELP_LINK, helpLinkId)
|
||||||
putBoolean(DISMISSIBLE, dismissible)
|
putBoolean(DISMISSIBLE, dismissible)
|
||||||
putBoolean(CLEAR_ACTIONS, clearActions)
|
putBoolean(CLEAR_POSITIVE_ACTION, clearPositiveAction)
|
||||||
putInt(POSITIVE_BUTTON_TITLE_ID, positiveButtonTitleId)
|
|
||||||
putString(POSITIVE_BUTTON_TITLE_STRING, positiveButtonTitleString)
|
|
||||||
putBoolean(SHOW_NEGATIVE_BUTTON, showNegativeButton)
|
|
||||||
putInt(NEGATIVE_BUTTON_TITLE_ID, negativeButtonTitleId)
|
|
||||||
putString(NEGATIVE_BUTTON_TITLE_STRING, negativeButtonTitleString)
|
|
||||||
}
|
}
|
||||||
dialog.arguments = bundle
|
dialog.arguments = bundle
|
||||||
return dialog
|
return dialog
|
||||||
|
@ -13,13 +13,15 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class ProgressDialogFragment : DialogFragment() {
|
class ProgressDialogFragment : DialogFragment() {
|
||||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||||
@ -62,7 +64,10 @@ class ProgressDialogFragment : DialogFragment() {
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.message.isSelected = true
|
binding.message.isSelected = true
|
||||||
taskViewModel.isComplete.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
taskViewModel.isComplete.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
dismiss()
|
dismiss()
|
||||||
when (val result = taskViewModel.result.value) {
|
when (val result = taskViewModel.result.value) {
|
||||||
@ -84,12 +89,20 @@ class ProgressDialogFragment : DialogFragment() {
|
|||||||
taskViewModel.clear()
|
taskViewModel.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
taskViewModel.cancelled.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
taskViewModel.cancelled.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
dialog?.setTitle(R.string.cancelling)
|
dialog?.setTitle(R.string.cancelling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
taskViewModel.progress.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
taskViewModel.progress.collect {
|
||||||
if (it != 0.0) {
|
if (it != 0.0) {
|
||||||
binding.progressBar.apply {
|
binding.progressBar.apply {
|
||||||
isIndeterminate = false
|
isIndeterminate = false
|
||||||
@ -102,11 +115,22 @@ class ProgressDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
taskViewModel.message.collect(viewLifecycleOwner) {
|
}
|
||||||
binding.message.setVisible(it.isNotEmpty())
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
taskViewModel.message.collect {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
binding.message.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.message.visibility = View.VISIBLE
|
||||||
binding.message.text = it
|
binding.message.text = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
|
// By default, the ProgressDialog will immediately dismiss itself upon a button being pressed.
|
||||||
// Setting the OnClickListener again after the dialog is shown overrides this behavior.
|
// Setting the OnClickListener again after the dialog is shown overrides this behavior.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -17,9 +18,14 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import info.debatty.java.stringsimilarity.Jaccard
|
import info.debatty.java.stringsimilarity.Jaccard
|
||||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
@ -29,8 +35,6 @@ import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
|||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class SearchFragment : Fragment() {
|
class SearchFragment : Fragment() {
|
||||||
private var _binding: FragmentSearchBinding? = null
|
private var _binding: FragmentSearchBinding? = null
|
||||||
@ -54,6 +58,8 @@ class SearchFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||||
@ -75,18 +81,42 @@ class SearchFragment : Fragment() {
|
|||||||
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
|
binding.chipGroup.setOnCheckedStateChangeListener { _, _ -> filterAndSearch() }
|
||||||
|
|
||||||
binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
|
binding.searchText.doOnTextChanged { text: CharSequence?, _: Int, _: Int, _: Int ->
|
||||||
binding.clearButton.setVisible(text.toString().isNotEmpty())
|
if (text.toString().isNotEmpty()) {
|
||||||
|
binding.clearButton.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.clearButton.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
filterAndSearch()
|
filterAndSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesViewModel.searchFocused.collect(
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
viewLifecycleOwner,
|
launch {
|
||||||
resetState = { gamesViewModel.setSearchFocused(false) }
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
) { if (it) focusSearch() }
|
gamesViewModel.searchFocused.collect {
|
||||||
gamesViewModel.games.collect(viewLifecycleOwner) { filterAndSearch() }
|
if (it) {
|
||||||
gamesViewModel.searchedGames.collect(viewLifecycleOwner) {
|
focusSearch()
|
||||||
|
gamesViewModel.setSearchFocused(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
gamesViewModel.games.collectLatest { filterAndSearch() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
gamesViewModel.searchedGames.collect {
|
||||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||||
binding.noResultsView.setVisible(it.isNotEmpty())
|
if (it.isEmpty()) {
|
||||||
|
binding.noResultsView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.noResultsView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
binding.clearButton.setOnClickListener { binding.searchText.setText("") }
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
@ -11,23 +11,19 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.ButtonInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
||||||
private var type = 0
|
private var type = 0
|
||||||
@ -39,7 +35,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||||
|
|
||||||
private lateinit var sliderBinding: DialogSliderBinding
|
private lateinit var sliderBinding: DialogSliderBinding
|
||||||
private lateinit var stringInputBinding: DialogEditTextBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -55,50 +50,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setMessage(R.string.reset_setting_confirmation)
|
.setMessage(R.string.reset_setting_confirmation)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
when (val item = settingsViewModel.clickedItem) {
|
|
||||||
is AnalogInputSetting -> {
|
|
||||||
val stickParam = NativeInput.getStickParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeAnalog
|
|
||||||
)
|
|
||||||
if (stickParam.get("engine", "") == "analog_from_button") {
|
|
||||||
when (item.analogDirection) {
|
|
||||||
AnalogDirection.Up -> stickParam.erase("up")
|
|
||||||
AnalogDirection.Down -> stickParam.erase("down")
|
|
||||||
AnalogDirection.Left -> stickParam.erase("left")
|
|
||||||
AnalogDirection.Right -> stickParam.erase("right")
|
|
||||||
}
|
|
||||||
NativeInput.setStickParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeAnalog,
|
|
||||||
stickParam
|
|
||||||
)
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
} else {
|
|
||||||
NativeInput.setStickParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeAnalog,
|
|
||||||
ParamPackage()
|
|
||||||
)
|
|
||||||
settingsViewModel.setDatasetChanged(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is ButtonInputSetting -> {
|
|
||||||
NativeInput.setButtonParam(
|
|
||||||
item.playerIndex,
|
|
||||||
item.nativeButton,
|
|
||||||
ParamPackage()
|
|
||||||
)
|
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
settingsViewModel.clickedItem!!.setting.reset()
|
settingsViewModel.clickedItem!!.setting.reset()
|
||||||
settingsViewModel.setAdapterItemChanged(position)
|
settingsViewModel.setAdapterItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
@ -107,7 +61,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
val item = settingsViewModel.clickedItem as SingleChoiceSetting
|
val item = settingsViewModel.clickedItem as SingleChoiceSetting
|
||||||
val value = getSelectionForSingleChoiceValue(item)
|
val value = getSelectionForSingleChoiceValue(item)
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(item.title)
|
.setTitle(item.nameId)
|
||||||
.setSingleChoiceItems(item.choicesId, value, this)
|
.setSingleChoiceItems(item.choicesId, value, this)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
@ -127,38 +81,18 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(item.title)
|
.setTitle(item.nameId)
|
||||||
.setView(sliderBinding.root)
|
.setView(sliderBinding.root)
|
||||||
.setPositiveButton(android.R.string.ok, this)
|
.setPositiveButton(android.R.string.ok, this)
|
||||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_INPUT -> {
|
|
||||||
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
|
||||||
val item = settingsViewModel.clickedItem as StringInputSetting
|
|
||||||
stringInputBinding.editText.setText(item.getSelectedValue())
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(item.title)
|
|
||||||
.setView(stringInputBinding.root)
|
|
||||||
.setPositiveButton(android.R.string.ok, this)
|
|
||||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||||
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(item.title)
|
.setTitle(item.nameId)
|
||||||
.setSingleChoiceItems(item.choices, item.selectedValueIndex, this)
|
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsItem.TYPE_INT_SINGLE_CHOICE -> {
|
|
||||||
val item = settingsViewModel.clickedItem as IntSingleChoiceSetting
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(item.title)
|
|
||||||
.setSingleChoiceItems(item.choices, item.selectedValueIndex, this)
|
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,7 +107,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
): View? {
|
): View? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
||||||
SettingsItem.TYPE_STRING_INPUT -> stringInputBinding.root
|
|
||||||
else -> super.onCreateView(inflater, container, savedInstanceState)
|
else -> super.onCreateView(inflater, container, savedInstanceState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,15 +115,21 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
when (type) {
|
when (type) {
|
||||||
SettingsItem.TYPE_SLIDER -> {
|
SettingsItem.TYPE_SLIDER -> {
|
||||||
settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.sliderTextValue.collect {
|
||||||
sliderBinding.textValue.text = it
|
sliderBinding.textValue.text = it
|
||||||
}
|
}
|
||||||
settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
|
}
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.sliderProgress.collect {
|
||||||
sliderBinding.slider.value = it.toFloat()
|
sliderBinding.slider.value = it.toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
when (settingsViewModel.clickedItem) {
|
when (settingsViewModel.clickedItem) {
|
||||||
@ -206,23 +145,10 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
scSetting.setSelectedValue(value)
|
scSetting.setSelectedValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
is IntSingleChoiceSetting -> {
|
|
||||||
val scSetting = settingsViewModel.clickedItem as IntSingleChoiceSetting
|
|
||||||
val value = scSetting.getValueAt(which)
|
|
||||||
scSetting.setSelectedValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
is SliderSetting -> {
|
is SliderSetting -> {
|
||||||
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
|
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
|
||||||
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
is StringInputSetting -> {
|
|
||||||
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
|
||||||
stringInputSetting.setSelectedValue(
|
|
||||||
(stringInputBinding.editText.text ?: "").toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
closeDialog()
|
closeDialog()
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -15,17 +15,21 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import info.debatty.java.stringsimilarity.Cosine
|
import info.debatty.java.stringsimilarity.Cosine
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class SettingsSearchFragment : Fragment() {
|
class SettingsSearchFragment : Fragment() {
|
||||||
private var _binding: FragmentSettingsSearchBinding? = null
|
private var _binding: FragmentSettingsSearchBinding? = null
|
||||||
@ -81,12 +85,16 @@ class SettingsSearchFragment : Fragment() {
|
|||||||
search()
|
search()
|
||||||
binding.settingsList.smoothScrollToPosition(0)
|
binding.settingsList.smoothScrollToPosition(0)
|
||||||
}
|
}
|
||||||
settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
settingsViewModel.shouldReloadSettingsList.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
settingsViewModel.setShouldReloadSettingsList(false)
|
settingsViewModel.setShouldReloadSettingsList(false)
|
||||||
search()
|
search()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
search()
|
search()
|
||||||
|
|
||||||
@ -100,9 +108,10 @@ class SettingsSearchFragment : Fragment() {
|
|||||||
|
|
||||||
private fun search() {
|
private fun search() {
|
||||||
val searchTerm = binding.searchText.text.toString().lowercase()
|
val searchTerm = binding.searchText.text.toString().lowercase()
|
||||||
binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false)
|
binding.clearButton.visibility =
|
||||||
|
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
||||||
if (searchTerm.isEmpty()) {
|
if (searchTerm.isEmpty()) {
|
||||||
binding.noResultsView.setVisible(visible = false, gone = false)
|
binding.noResultsView.visibility = View.VISIBLE
|
||||||
settingsAdapter?.submitList(emptyList())
|
settingsAdapter?.submitList(emptyList())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -110,7 +119,7 @@ class SettingsSearchFragment : Fragment() {
|
|||||||
val baseList = SettingsItem.settingsItems
|
val baseList = SettingsItem.settingsItems
|
||||||
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
|
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
|
||||||
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
|
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
|
||||||
val title = item.value.title.lowercase()
|
val title = getString(item.value.nameId).lowercase()
|
||||||
val similarity = similarityAlgorithm.similarity(searchTerm, title)
|
val similarity = similarityAlgorithm.similarity(searchTerm, title)
|
||||||
if (similarity > 0.08) {
|
if (similarity > 0.08) {
|
||||||
Pair(similarity, item)
|
Pair(similarity, item)
|
||||||
@ -129,7 +138,8 @@ class SettingsSearchFragment : Fragment() {
|
|||||||
optionalSetting
|
optionalSetting
|
||||||
}
|
}
|
||||||
settingsAdapter?.submitList(sortedList)
|
settingsAdapter?.submitList(sortedList)
|
||||||
binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false)
|
binding.noResultsView.visibility =
|
||||||
|
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun focusSearch() {
|
private fun focusSearch() {
|
@ -4,6 +4,7 @@
|
|||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -22,6 +23,9 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
@ -42,8 +46,6 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity
|
|||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class SetupFragment : Fragment() {
|
class SetupFragment : Fragment() {
|
||||||
private var _binding: FragmentSetupBinding? = null
|
private var _binding: FragmentSetupBinding? = null
|
||||||
@ -75,6 +77,8 @@ class SetupFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
mainActivity = requireActivity() as MainActivity
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
@ -206,14 +210,28 @@ class SetupFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
homeViewModel.shouldPageForward.collect(
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
viewLifecycleOwner,
|
launch {
|
||||||
resetState = { homeViewModel.setShouldPageForward(false) }
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
) { if (it) pageForward() }
|
homeViewModel.shouldPageForward.collect {
|
||||||
homeViewModel.gamesDirSelected.collect(
|
if (it) {
|
||||||
viewLifecycleOwner,
|
pageForward()
|
||||||
resetState = { homeViewModel.setGamesDirSelected(false) }
|
homeViewModel.setShouldPageForward(false)
|
||||||
) { if (it) gamesDirCallback.onStepCompleted() }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
homeViewModel.gamesDirSelected.collect {
|
||||||
|
if (it) {
|
||||||
|
gamesDirCallback.onStepCompleted()
|
||||||
|
homeViewModel.setGamesDirSelected(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.viewPager2.apply {
|
binding.viewPager2.apply {
|
||||||
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
|
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
|
||||||
@ -274,8 +292,12 @@ class SetupFragment : Fragment() {
|
|||||||
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY)
|
||||||
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!!
|
||||||
|
|
||||||
binding.buttonNext.setVisible(nextIsVisible)
|
if (nextIsVisible) {
|
||||||
binding.buttonBack.setVisible(backIsVisible)
|
binding.buttonNext.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
if (backIsVisible) {
|
||||||
|
binding.buttonBack.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
hasBeenWarned = BooleanArray(pages.size)
|
hasBeenWarned = BooleanArray(pages.size)
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,8 @@ import androidx.lifecycle.ViewModel
|
|||||||
|
|
||||||
class MessageDialogViewModel : ViewModel() {
|
class MessageDialogViewModel : ViewModel() {
|
||||||
var positiveAction: (() -> Unit)? = null
|
var positiveAction: (() -> Unit)? = null
|
||||||
var negativeAction: (() -> Unit)? = null
|
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
positiveAction = null
|
positiveAction = null
|
||||||
negativeAction = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
|
||||||
|
|
||||||
class SettingsViewModel : ViewModel() {
|
class SettingsViewModel : ViewModel() {
|
||||||
var game: Game? = null
|
var game: Game? = null
|
||||||
|
|
||||||
var clickedItem: SettingsItem? = null
|
var clickedItem: SettingsItem? = null
|
||||||
|
|
||||||
var currentDevice = 0
|
|
||||||
|
|
||||||
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
||||||
private val _shouldRecreate = MutableStateFlow(false)
|
private val _shouldRecreate = MutableStateFlow(false)
|
||||||
|
|
||||||
@ -42,18 +36,6 @@ class SettingsViewModel : ViewModel() {
|
|||||||
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
||||||
private val _adapterItemChanged = MutableStateFlow(-1)
|
private val _adapterItemChanged = MutableStateFlow(-1)
|
||||||
|
|
||||||
private val _datasetChanged = MutableStateFlow(false)
|
|
||||||
val datasetChanged = _datasetChanged.asStateFlow()
|
|
||||||
|
|
||||||
private val _reloadListAndNotifyDataset = MutableStateFlow(false)
|
|
||||||
val reloadListAndNotifyDataset = _reloadListAndNotifyDataset.asStateFlow()
|
|
||||||
|
|
||||||
private val _shouldShowDeleteProfileDialog = MutableStateFlow("")
|
|
||||||
val shouldShowDeleteProfileDialog = _shouldShowDeleteProfileDialog.asStateFlow()
|
|
||||||
|
|
||||||
private val _shouldShowResetInputDialog = MutableStateFlow(false)
|
|
||||||
val shouldShowResetInputDialog = _shouldShowResetInputDialog.asStateFlow()
|
|
||||||
|
|
||||||
fun setShouldRecreate(value: Boolean) {
|
fun setShouldRecreate(value: Boolean) {
|
||||||
_shouldRecreate.value = value
|
_shouldRecreate.value = value
|
||||||
}
|
}
|
||||||
@ -86,27 +68,4 @@ class SettingsViewModel : ViewModel() {
|
|||||||
fun setAdapterItemChanged(value: Int) {
|
fun setAdapterItemChanged(value: Int) {
|
||||||
_adapterItemChanged.value = value
|
_adapterItemChanged.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDatasetChanged(value: Boolean) {
|
|
||||||
_datasetChanged.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setReloadListAndNotifyDataset(value: Boolean) {
|
|
||||||
_reloadListAndNotifyDataset.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShouldShowDeleteProfileDialog(profile: String) {
|
|
||||||
_shouldShowDeleteProfileDialog.value = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setShouldShowResetInputDialog(value: Boolean) {
|
|
||||||
_shouldShowResetInputDialog.value = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCurrentDeviceParams(defaultParams: ParamPackage): ParamPackage =
|
|
||||||
try {
|
|
||||||
InputHandler.registeredControllers[currentDevice]
|
|
||||||
} catch (e: IndexOutOfBoundsException) {
|
|
||||||
defaultParams
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -24,11 +24,10 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary.StickType
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||||
@ -100,18 +99,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shouldUpdateView = false
|
var shouldUpdateView = false
|
||||||
val playerIndex = when (NativeInput.getStyleIndex(0)) {
|
val playerIndex =
|
||||||
NpadStyleIndex.Handheld -> 8
|
if (NativeLibrary.isHandheldOnly()) {
|
||||||
else -> 0
|
NativeLibrary.ConsoleDevice
|
||||||
|
} else {
|
||||||
|
NativeLibrary.Player1Device
|
||||||
}
|
}
|
||||||
|
|
||||||
for (button in overlayButtons) {
|
for (button in overlayButtons) {
|
||||||
if (!button.updateStatus(event)) {
|
if (!button.updateStatus(event)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
button.button,
|
button.buttonId,
|
||||||
button.status
|
button.status
|
||||||
)
|
)
|
||||||
playHaptics(event)
|
playHaptics(event)
|
||||||
@ -122,24 +123,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) {
|
if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
dpad.up,
|
dpad.upId,
|
||||||
dpad.upStatus
|
dpad.upStatus
|
||||||
)
|
)
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
dpad.down,
|
dpad.downId,
|
||||||
dpad.downStatus
|
dpad.downStatus
|
||||||
)
|
)
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
dpad.left,
|
dpad.leftId,
|
||||||
dpad.leftStatus
|
dpad.leftStatus
|
||||||
)
|
)
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
dpad.right,
|
dpad.rightId,
|
||||||
dpad.rightStatus
|
dpad.rightStatus
|
||||||
)
|
)
|
||||||
playHaptics(event)
|
playHaptics(event)
|
||||||
@ -150,15 +151,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
if (!joystick.updateStatus(event)) {
|
if (!joystick.updateStatus(event)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
NativeInput.onOverlayJoystickEvent(
|
val axisID = joystick.joystickId
|
||||||
|
NativeLibrary.onGamePadJoystickEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
joystick.joystick,
|
axisID,
|
||||||
joystick.xAxis,
|
joystick.xAxis,
|
||||||
joystick.realYAxis
|
joystick.realYAxis
|
||||||
)
|
)
|
||||||
NativeInput.onOverlayButtonEvent(
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
joystick.button,
|
joystick.buttonId,
|
||||||
joystick.buttonStatus
|
joystick.buttonStatus
|
||||||
)
|
)
|
||||||
playHaptics(event)
|
playHaptics(event)
|
||||||
@ -185,7 +187,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
|
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
|
||||||
|
|
||||||
if (isActionDown && !isTouchInputConsumed(pointerId)) {
|
if (isActionDown && !isTouchInputConsumed(pointerId)) {
|
||||||
NativeInput.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
|
NativeLibrary.onTouchPressed(pointerId, xPosition.toFloat(), yPosition.toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActionMove) {
|
if (isActionMove) {
|
||||||
@ -194,12 +196,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
if (isTouchInputConsumed(fingerId)) {
|
if (isTouchInputConsumed(fingerId)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
NativeInput.onTouchMoved(fingerId, event.getX(i), event.getY(i))
|
NativeLibrary.onTouchMoved(fingerId, event.getX(i), event.getY(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActionUp && !isTouchInputConsumed(pointerId)) {
|
if (isActionUp && !isTouchInputConsumed(pointerId)) {
|
||||||
NativeInput.onTouchReleased(pointerId)
|
NativeLibrary.onTouchReleased(pointerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -357,7 +359,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_a,
|
R.drawable.facebutton_a,
|
||||||
R.drawable.facebutton_a_depressed,
|
R.drawable.facebutton_a_depressed,
|
||||||
NativeButton.A,
|
ButtonType.BUTTON_A,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -371,7 +373,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_b,
|
R.drawable.facebutton_b,
|
||||||
R.drawable.facebutton_b_depressed,
|
R.drawable.facebutton_b_depressed,
|
||||||
NativeButton.B,
|
ButtonType.BUTTON_B,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -385,7 +387,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_x,
|
R.drawable.facebutton_x,
|
||||||
R.drawable.facebutton_x_depressed,
|
R.drawable.facebutton_x_depressed,
|
||||||
NativeButton.X,
|
ButtonType.BUTTON_X,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -399,7 +401,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_y,
|
R.drawable.facebutton_y,
|
||||||
R.drawable.facebutton_y_depressed,
|
R.drawable.facebutton_y_depressed,
|
||||||
NativeButton.Y,
|
ButtonType.BUTTON_Y,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -413,7 +415,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_plus,
|
R.drawable.facebutton_plus,
|
||||||
R.drawable.facebutton_plus_depressed,
|
R.drawable.facebutton_plus_depressed,
|
||||||
NativeButton.Plus,
|
ButtonType.BUTTON_PLUS,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -427,7 +429,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_minus,
|
R.drawable.facebutton_minus,
|
||||||
R.drawable.facebutton_minus_depressed,
|
R.drawable.facebutton_minus_depressed,
|
||||||
NativeButton.Minus,
|
ButtonType.BUTTON_MINUS,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -441,7 +443,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_home,
|
R.drawable.facebutton_home,
|
||||||
R.drawable.facebutton_home_depressed,
|
R.drawable.facebutton_home_depressed,
|
||||||
NativeButton.Home,
|
ButtonType.BUTTON_HOME,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -455,7 +457,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.facebutton_screenshot,
|
R.drawable.facebutton_screenshot,
|
||||||
R.drawable.facebutton_screenshot_depressed,
|
R.drawable.facebutton_screenshot_depressed,
|
||||||
NativeButton.Capture,
|
ButtonType.BUTTON_CAPTURE,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -469,7 +471,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.l_shoulder,
|
R.drawable.l_shoulder,
|
||||||
R.drawable.l_shoulder_depressed,
|
R.drawable.l_shoulder_depressed,
|
||||||
NativeButton.L,
|
ButtonType.TRIGGER_L,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -483,7 +485,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.r_shoulder,
|
R.drawable.r_shoulder,
|
||||||
R.drawable.r_shoulder_depressed,
|
R.drawable.r_shoulder_depressed,
|
||||||
NativeButton.R,
|
ButtonType.TRIGGER_R,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -497,7 +499,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.zl_trigger,
|
R.drawable.zl_trigger,
|
||||||
R.drawable.zl_trigger_depressed,
|
R.drawable.zl_trigger_depressed,
|
||||||
NativeButton.ZL,
|
ButtonType.TRIGGER_ZL,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -511,7 +513,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.zr_trigger,
|
R.drawable.zr_trigger,
|
||||||
R.drawable.zr_trigger_depressed,
|
R.drawable.zr_trigger_depressed,
|
||||||
NativeButton.ZR,
|
ButtonType.TRIGGER_ZR,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -525,7 +527,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.button_l3,
|
R.drawable.button_l3,
|
||||||
R.drawable.button_l3_depressed,
|
R.drawable.button_l3_depressed,
|
||||||
NativeButton.LStick,
|
ButtonType.STICK_L,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -539,7 +541,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize,
|
windowSize,
|
||||||
R.drawable.button_r3,
|
R.drawable.button_r3,
|
||||||
R.drawable.button_r3_depressed,
|
R.drawable.button_r3_depressed,
|
||||||
NativeButton.RStick,
|
ButtonType.STICK_R,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -554,8 +556,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
R.drawable.joystick_range,
|
R.drawable.joystick_range,
|
||||||
R.drawable.joystick,
|
R.drawable.joystick,
|
||||||
R.drawable.joystick_depressed,
|
R.drawable.joystick_depressed,
|
||||||
NativeAnalog.LStick,
|
StickType.STICK_L,
|
||||||
NativeButton.LStick,
|
ButtonType.STICK_L,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -570,8 +572,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
R.drawable.joystick_range,
|
R.drawable.joystick_range,
|
||||||
R.drawable.joystick,
|
R.drawable.joystick,
|
||||||
R.drawable.joystick_depressed,
|
R.drawable.joystick_depressed,
|
||||||
NativeAnalog.RStick,
|
StickType.STICK_R,
|
||||||
NativeButton.RStick,
|
ButtonType.STICK_R,
|
||||||
data,
|
data,
|
||||||
position
|
position
|
||||||
)
|
)
|
||||||
@ -663,7 +665,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
|
|
||||||
val overlayControlData = NativeConfig.getOverlayControlData()
|
val overlayControlData = NativeConfig.getOverlayControlData()
|
||||||
overlayControlData.forEach {
|
overlayControlData.forEach {
|
||||||
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true
|
it.enabled = OverlayControl.from(it.id)?.defaultVisibility == false
|
||||||
}
|
}
|
||||||
NativeConfig.setOverlayControlData(overlayControlData)
|
NativeConfig.setOverlayControlData(overlayControlData)
|
||||||
|
|
||||||
@ -833,7 +835,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
windowSize: Pair<Point, Point>,
|
windowSize: Pair<Point, Point>,
|
||||||
defaultResId: Int,
|
defaultResId: Int,
|
||||||
pressedResId: Int,
|
pressedResId: Int,
|
||||||
button: NativeButton,
|
buttonId: Int,
|
||||||
overlayControlData: OverlayControlData,
|
overlayControlData: OverlayControlData,
|
||||||
position: Pair<Double, Double>
|
position: Pair<Double, Double>
|
||||||
): InputOverlayDrawableButton {
|
): InputOverlayDrawableButton {
|
||||||
@ -867,7 +869,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
res,
|
res,
|
||||||
defaultStateBitmap,
|
defaultStateBitmap,
|
||||||
pressedStateBitmap,
|
pressedStateBitmap,
|
||||||
button,
|
buttonId,
|
||||||
overlayControlData
|
overlayControlData
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -938,7 +940,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
res,
|
res,
|
||||||
defaultStateBitmap,
|
defaultStateBitmap,
|
||||||
pressedOneDirectionStateBitmap,
|
pressedOneDirectionStateBitmap,
|
||||||
pressedTwoDirectionsStateBitmap
|
pressedTwoDirectionsStateBitmap,
|
||||||
|
ButtonType.DPAD_UP,
|
||||||
|
ButtonType.DPAD_DOWN,
|
||||||
|
ButtonType.DPAD_LEFT,
|
||||||
|
ButtonType.DPAD_RIGHT
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
||||||
@ -987,8 +993,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
resOuter: Int,
|
resOuter: Int,
|
||||||
defaultResInner: Int,
|
defaultResInner: Int,
|
||||||
pressedResInner: Int,
|
pressedResInner: Int,
|
||||||
joystick: NativeAnalog,
|
joystick: Int,
|
||||||
button: NativeButton,
|
buttonId: Int,
|
||||||
overlayControlData: OverlayControlData,
|
overlayControlData: OverlayControlData,
|
||||||
position: Pair<Double, Double>
|
position: Pair<Double, Double>
|
||||||
): InputOverlayDrawableJoystick {
|
): InputOverlayDrawableJoystick {
|
||||||
@ -1036,7 +1042,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
|
|||||||
outerRect,
|
outerRect,
|
||||||
innerRect,
|
innerRect,
|
||||||
joystick,
|
joystick,
|
||||||
button,
|
buttonId,
|
||||||
overlayControlData.id
|
overlayControlData.id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,8 +9,7 @@ import android.graphics.Canvas
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,13 +19,13 @@ import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
|||||||
* @param res [Resources] instance.
|
* @param res [Resources] instance.
|
||||||
* @param defaultStateBitmap [Bitmap] to use with the default state Drawable.
|
* @param defaultStateBitmap [Bitmap] to use with the default state Drawable.
|
||||||
* @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable.
|
* @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable.
|
||||||
* @param button [NativeButton] for this type of button.
|
* @param buttonId Identifier for this type of button.
|
||||||
*/
|
*/
|
||||||
class InputOverlayDrawableButton(
|
class InputOverlayDrawableButton(
|
||||||
res: Resources,
|
res: Resources,
|
||||||
defaultStateBitmap: Bitmap,
|
defaultStateBitmap: Bitmap,
|
||||||
pressedStateBitmap: Bitmap,
|
pressedStateBitmap: Bitmap,
|
||||||
val button: NativeButton,
|
val buttonId: Int,
|
||||||
val overlayControlData: OverlayControlData
|
val overlayControlData: OverlayControlData
|
||||||
) {
|
) {
|
||||||
// The ID value what motion event is tracking
|
// The ID value what motion event is tracking
|
||||||
|
@ -9,8 +9,7 @@ import android.graphics.Canvas
|
|||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom [BitmapDrawable] that is capable
|
* Custom [BitmapDrawable] that is capable
|
||||||
@ -20,12 +19,20 @@ import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|||||||
* @param defaultStateBitmap [Bitmap] of the default state.
|
* @param defaultStateBitmap [Bitmap] of the default state.
|
||||||
* @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction.
|
* @param pressedOneDirectionStateBitmap [Bitmap] of the pressed state in one direction.
|
||||||
* @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction.
|
* @param pressedTwoDirectionsStateBitmap [Bitmap] of the pressed state in two direction.
|
||||||
|
* @param buttonUp Identifier for the up button.
|
||||||
|
* @param buttonDown Identifier for the down button.
|
||||||
|
* @param buttonLeft Identifier for the left button.
|
||||||
|
* @param buttonRight Identifier for the right button.
|
||||||
*/
|
*/
|
||||||
class InputOverlayDrawableDpad(
|
class InputOverlayDrawableDpad(
|
||||||
res: Resources,
|
res: Resources,
|
||||||
defaultStateBitmap: Bitmap,
|
defaultStateBitmap: Bitmap,
|
||||||
pressedOneDirectionStateBitmap: Bitmap,
|
pressedOneDirectionStateBitmap: Bitmap,
|
||||||
pressedTwoDirectionsStateBitmap: Bitmap
|
pressedTwoDirectionsStateBitmap: Bitmap,
|
||||||
|
buttonUp: Int,
|
||||||
|
buttonDown: Int,
|
||||||
|
buttonLeft: Int,
|
||||||
|
buttonRight: Int
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Gets one of the InputOverlayDrawableDpad's button IDs.
|
* Gets one of the InputOverlayDrawableDpad's button IDs.
|
||||||
@ -33,10 +40,10 @@ class InputOverlayDrawableDpad(
|
|||||||
* @return the requested InputOverlayDrawableDpad's button ID.
|
* @return the requested InputOverlayDrawableDpad's button ID.
|
||||||
*/
|
*/
|
||||||
// The ID identifying what type of button this Drawable represents.
|
// The ID identifying what type of button this Drawable represents.
|
||||||
val up = NativeButton.DUp
|
val upId: Int
|
||||||
val down = NativeButton.DDown
|
val downId: Int
|
||||||
val left = NativeButton.DLeft
|
val leftId: Int
|
||||||
val right = NativeButton.DRight
|
val rightId: Int
|
||||||
var trackId: Int
|
var trackId: Int
|
||||||
|
|
||||||
val width: Int
|
val width: Int
|
||||||
@ -62,6 +69,10 @@ class InputOverlayDrawableDpad(
|
|||||||
this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap)
|
this.pressedTwoDirectionsStateBitmap = BitmapDrawable(res, pressedTwoDirectionsStateBitmap)
|
||||||
width = this.defaultStateBitmap.intrinsicWidth
|
width = this.defaultStateBitmap.intrinsicWidth
|
||||||
height = this.defaultStateBitmap.intrinsicHeight
|
height = this.defaultStateBitmap.intrinsicHeight
|
||||||
|
upId = buttonUp
|
||||||
|
downId = buttonDown
|
||||||
|
leftId = buttonLeft
|
||||||
|
rightId = buttonRight
|
||||||
trackId = -1
|
trackId = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,9 +13,7 @@ import kotlin.math.atan2
|
|||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput.ButtonState
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeAnalog
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.NativeButton
|
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,8 +26,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
|||||||
* @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick.
|
* @param bitmapInnerPressed [Bitmap] which represents the pressed inner movable part of the joystick.
|
||||||
* @param rectOuter [Rect] which represents the outer joystick bounds.
|
* @param rectOuter [Rect] which represents the outer joystick bounds.
|
||||||
* @param rectInner [Rect] which represents the inner joystick bounds.
|
* @param rectInner [Rect] which represents the inner joystick bounds.
|
||||||
* @param joystick The [NativeAnalog] this Drawable represents.
|
* @param joystickId The ID value what type of joystick this Drawable represents.
|
||||||
* @param button The [NativeButton] this Drawable represents.
|
* @param buttonId The ID value what type of button this Drawable represents.
|
||||||
*/
|
*/
|
||||||
class InputOverlayDrawableJoystick(
|
class InputOverlayDrawableJoystick(
|
||||||
res: Resources,
|
res: Resources,
|
||||||
@ -38,8 +36,8 @@ class InputOverlayDrawableJoystick(
|
|||||||
bitmapInnerPressed: Bitmap,
|
bitmapInnerPressed: Bitmap,
|
||||||
rectOuter: Rect,
|
rectOuter: Rect,
|
||||||
rectInner: Rect,
|
rectInner: Rect,
|
||||||
val joystick: NativeAnalog,
|
val joystickId: Int,
|
||||||
val button: NativeButton,
|
val buttonId: Int,
|
||||||
val prefId: String
|
val prefId: String
|
||||||
) {
|
) {
|
||||||
// The ID value what motion event is tracking
|
// The ID value what motion event is tracking
|
||||||
@ -71,7 +69,8 @@ class InputOverlayDrawableJoystick(
|
|||||||
|
|
||||||
// TODO: Add button support
|
// TODO: Add button support
|
||||||
val buttonStatus: Int
|
val buttonStatus: Int
|
||||||
get() = ButtonState.RELEASED
|
get() =
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
var bounds: Rect
|
var bounds: Rect
|
||||||
get() = outerBitmap.bounds
|
get() = outerBitmap.bounds
|
||||||
set(bounds) {
|
set(bounds) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui
|
package org.yuzu.yuzu_emu.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -13,16 +14,19 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
||||||
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
import org.yuzu.yuzu_emu.layout.AutofitGridLayoutManager
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||||
import org.yuzu.yuzu_emu.utils.collect
|
|
||||||
|
|
||||||
class GamesFragment : Fragment() {
|
class GamesFragment : Fragment() {
|
||||||
private var _binding: FragmentGamesBinding? = null
|
private var _binding: FragmentGamesBinding? = null
|
||||||
@ -40,6 +44,8 @@ class GamesFragment : Fragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is using the correct scope, lint is just acting up
|
||||||
|
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
homeViewModel.setNavigationVisibility(visible = true, animated = true)
|
||||||
@ -82,28 +88,49 @@ class GamesFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gamesViewModel.isReloading.collect(viewLifecycleOwner) {
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
gamesViewModel.isReloading.collect {
|
||||||
binding.swipeRefresh.isRefreshing = it
|
binding.swipeRefresh.isRefreshing = it
|
||||||
binding.noticeText.setVisible(
|
if (gamesViewModel.games.value.isEmpty() && !it) {
|
||||||
visible = gamesViewModel.games.value.isEmpty() && !it,
|
binding.noticeText.visibility = View.VISIBLE
|
||||||
gone = false
|
} else {
|
||||||
)
|
binding.noticeText.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
gamesViewModel.games.collect(viewLifecycleOwner) {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
gamesViewModel.games.collectLatest {
|
||||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||||
}
|
}
|
||||||
gamesViewModel.shouldSwapData.collect(
|
}
|
||||||
viewLifecycleOwner,
|
}
|
||||||
resetState = { gamesViewModel.setShouldSwapData(false) }
|
launch {
|
||||||
) {
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
gamesViewModel.shouldSwapData.collect {
|
||||||
if (it) {
|
if (it) {
|
||||||
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value)
|
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||||
|
gamesViewModel.games.value
|
||||||
|
)
|
||||||
|
gamesViewModel.setShouldSwapData(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
gamesViewModel.shouldScrollToTop.collect {
|
||||||
|
if (it) {
|
||||||
|
scrollToTop()
|
||||||
|
gamesViewModel.setShouldScrollToTop(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gamesViewModel.shouldScrollToTop.collect(
|
|
||||||
viewLifecycleOwner,
|
|
||||||
resetState = { gamesViewModel.setShouldScrollToTop(false) }
|
|
||||||
) { if (it) scrollToTop() }
|
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
@ -27,6 +30,7 @@ import com.google.android.material.color.MaterialColors
|
|||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FilenameFilter
|
import java.io.FilenameFilter
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
@ -43,7 +47,6 @@ import org.yuzu.yuzu_emu.model.InstallResult
|
|||||||
import org.yuzu.yuzu_emu.model.TaskState
|
import org.yuzu.yuzu_emu.model.TaskState
|
||||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
@ -136,22 +139,41 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
// Prevents navigation from being drawn for a short time on recreation if set to hidden
|
// Prevents navigation from being drawn for a short time on recreation if set to hidden
|
||||||
if (!homeViewModel.navigationVisible.value.first) {
|
if (!homeViewModel.navigationVisible.value.first) {
|
||||||
binding.navigationView.setVisible(visible = false, gone = false)
|
binding.navigationView.visibility = View.INVISIBLE
|
||||||
binding.statusBarShade.setVisible(visible = false, gone = false)
|
binding.statusBarShade.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
homeViewModel.navigationVisible.collect(this) { showNavigation(it.first, it.second) }
|
lifecycleScope.apply {
|
||||||
homeViewModel.statusBarShadeVisible.collect(this) { showStatusBarShade(it) }
|
launch {
|
||||||
homeViewModel.contentToInstall.collect(
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
this,
|
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
|
||||||
resetState = { homeViewModel.setContentToInstall(null) }
|
}
|
||||||
) {
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
homeViewModel.contentToInstall.collect {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
installContent(it)
|
installContent(it)
|
||||||
|
homeViewModel.setContentToInstall(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
homeViewModel.checkKeys.collect {
|
||||||
|
if (it) {
|
||||||
|
checkKeys()
|
||||||
|
homeViewModel.setCheckKeys(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
homeViewModel.checkKeys.collect(this, resetState = { homeViewModel.setCheckKeys(false) }) {
|
|
||||||
if (it) checkKeys()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
@ -192,14 +214,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
private fun showNavigation(visible: Boolean, animated: Boolean) {
|
private fun showNavigation(visible: Boolean, animated: Boolean) {
|
||||||
if (!animated) {
|
if (!animated) {
|
||||||
binding.navigationView.setVisible(visible)
|
if (visible) {
|
||||||
|
binding.navigationView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.navigationView.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val smallLayout = resources.getBoolean(R.bool.small_layout)
|
val smallLayout = resources.getBoolean(R.bool.small_layout)
|
||||||
binding.navigationView.animate().apply {
|
binding.navigationView.animate().apply {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
binding.navigationView.setVisible(true)
|
binding.navigationView.visibility = View.VISIBLE
|
||||||
duration = 300
|
duration = 300
|
||||||
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
|
interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
|
||||||
|
|
||||||
@ -238,7 +264,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
}
|
}
|
||||||
}.withEndAction {
|
}.withEndAction {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
binding.navigationView.setVisible(visible = false, gone = false)
|
binding.navigationView.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
@ -246,7 +272,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
private fun showStatusBarShade(visible: Boolean) {
|
private fun showStatusBarShade(visible: Boolean) {
|
||||||
binding.statusBarShade.animate().apply {
|
binding.statusBarShade.animate().apply {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
binding.statusBarShade.setVisible(true)
|
binding.statusBarShade.visibility = View.VISIBLE
|
||||||
binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
|
binding.statusBarShade.translationY = binding.statusBarShade.height.toFloat() * -2
|
||||||
duration = 300
|
duration = 300
|
||||||
translationY(0f)
|
translationY(0f)
|
||||||
@ -258,7 +284,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
}
|
}
|
||||||
}.withEndAction {
|
}.withEndAction {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
binding.statusBarShade.setVisible(visible = false, gone = false)
|
binding.statusBarShade.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
@ -498,8 +524,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
titleId = R.string.content_install_notice,
|
titleId = R.string.content_install_notice,
|
||||||
descriptionId = R.string.content_install_notice_description,
|
descriptionId = R.string.content_install_notice_description,
|
||||||
positiveAction = { homeViewModel.setContentToInstall(documents) },
|
positiveAction = { homeViewModel.setContentToInstall(documents) }
|
||||||
negativeAction = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||||
|
@ -6,89 +6,439 @@ package org.yuzu.yuzu_emu.utils
|
|||||||
import android.view.InputDevice
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
import kotlin.math.sqrt
|
||||||
import org.yuzu.yuzu_emu.features.input.YuzuInputOverlayDevice
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.features.input.YuzuPhysicalDevice
|
|
||||||
|
|
||||||
object InputHandler {
|
object InputHandler {
|
||||||
var androidControllers = mapOf<Int, YuzuPhysicalDevice>()
|
private var controllerIds = getGameControllerIds()
|
||||||
var registeredControllers = mutableListOf<ParamPackage>()
|
|
||||||
|
fun initialize() {
|
||||||
|
// Connect first controller
|
||||||
|
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateControllerIds() {
|
||||||
|
controllerIds = getGameControllerIds()
|
||||||
|
}
|
||||||
|
|
||||||
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
val button: Int = when (event.device.vendorId) {
|
||||||
|
0x045E -> getInputXboxButtonKey(event.keyCode)
|
||||||
|
0x054C -> getInputDS5ButtonKey(event.keyCode)
|
||||||
|
0x057E -> getInputJoyconButtonKey(event.keyCode)
|
||||||
|
0x1532 -> getInputRazerButtonKey(event.keyCode)
|
||||||
|
0x3537 -> getInputRedmagicButtonKey(event.keyCode)
|
||||||
|
0x358A -> getInputBackboneLabsButtonKey(event.keyCode)
|
||||||
|
else -> getInputGenericButtonKey(event.keyCode)
|
||||||
|
}
|
||||||
|
|
||||||
val action = when (event.action) {
|
val action = when (event.action) {
|
||||||
KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED
|
KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED
|
||||||
KeyEvent.ACTION_UP -> NativeInput.ButtonState.RELEASED
|
KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var controllerData = androidControllers[event.device.controllerNumber]
|
// Ignore invalid buttons
|
||||||
if (controllerData == null) {
|
if (button < 0) {
|
||||||
updateControllerData()
|
return false
|
||||||
controllerData = androidControllers[event.device.controllerNumber] ?: return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeInput.onGamePadButtonEvent(
|
return NativeLibrary.onGamePadButtonEvent(
|
||||||
controllerData.getGUID(),
|
getPlayerNumber(event.device.controllerNumber, event.deviceId),
|
||||||
controllerData.getPort(),
|
button,
|
||||||
event.keyCode,
|
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||||
val controllerData =
|
val device = event.device
|
||||||
androidControllers[event.device.controllerNumber] ?: return false
|
// Check every axis input available on the controller
|
||||||
event.device.motionRanges.forEach {
|
for (range in device.motionRanges) {
|
||||||
NativeInput.onGamePadAxisEvent(
|
val axis = range.axis
|
||||||
controllerData.getGUID(),
|
when (device.vendorId) {
|
||||||
controllerData.getPort(),
|
0x045E -> setGenericAxisInput(event, axis)
|
||||||
it.axis,
|
0x054C -> setGenericAxisInput(event, axis)
|
||||||
event.getAxisValue(it.axis)
|
0x057E -> setJoyconAxisInput(event, axis)
|
||||||
)
|
0x1532 -> setRazerAxisInput(event, axis)
|
||||||
|
else -> setGenericAxisInput(event, axis)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDevices(): Map<Int, YuzuPhysicalDevice> {
|
private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
|
||||||
val gameControllerDeviceIds = mutableMapOf<Int, YuzuPhysicalDevice>()
|
var deviceIndex = index
|
||||||
|
if (deviceId != -1) {
|
||||||
|
deviceIndex = controllerIds[deviceId] ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Joycons are handled as different controllers. Find a way to merge them.
|
||||||
|
return when (deviceIndex) {
|
||||||
|
2 -> NativeLibrary.Player2Device
|
||||||
|
3 -> NativeLibrary.Player3Device
|
||||||
|
4 -> NativeLibrary.Player4Device
|
||||||
|
5 -> NativeLibrary.Player5Device
|
||||||
|
6 -> NativeLibrary.Player6Device
|
||||||
|
7 -> NativeLibrary.Player7Device
|
||||||
|
8 -> NativeLibrary.Player8Device
|
||||||
|
else -> if (NativeLibrary.isHandheldOnly()) {
|
||||||
|
NativeLibrary.ConsoleDevice
|
||||||
|
} else {
|
||||||
|
NativeLibrary.Player1Device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setStickState(playerNumber: Int, index: Int, xAxis: Float, yAxis: Float) {
|
||||||
|
// Calculate vector size
|
||||||
|
val r2 = xAxis * xAxis + yAxis * yAxis
|
||||||
|
var r = sqrt(r2.toDouble()).toFloat()
|
||||||
|
|
||||||
|
// Adjust range of joystick
|
||||||
|
val deadzone = 0.15f
|
||||||
|
var x = xAxis
|
||||||
|
var y = yAxis
|
||||||
|
|
||||||
|
if (r > deadzone) {
|
||||||
|
val deadzoneFactor = 1.0f / r * (r - deadzone) / (1.0f - deadzone)
|
||||||
|
x *= deadzoneFactor
|
||||||
|
y *= deadzoneFactor
|
||||||
|
r *= deadzoneFactor
|
||||||
|
} else {
|
||||||
|
x = 0.0f
|
||||||
|
y = 0.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize joystick
|
||||||
|
if (r > 1.0f) {
|
||||||
|
x /= r
|
||||||
|
y /= r
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.onGamePadJoystickEvent(
|
||||||
|
playerNumber,
|
||||||
|
index,
|
||||||
|
x,
|
||||||
|
-y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAxisToButton(axis: Float): Int {
|
||||||
|
return if (axis > 0.5f) {
|
||||||
|
NativeLibrary.ButtonState.PRESSED
|
||||||
|
} else {
|
||||||
|
NativeLibrary.ButtonState.RELEASED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.DPAD_UP,
|
||||||
|
getAxisToButton(-yAxis)
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.DPAD_DOWN,
|
||||||
|
getAxisToButton(yAxis)
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.DPAD_LEFT,
|
||||||
|
getAxisToButton(-xAxis)
|
||||||
|
)
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.DPAD_RIGHT,
|
||||||
|
getAxisToButton(xAxis)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputDS5ButtonKey(key: Int): Int {
|
||||||
|
// The missing ds5 buttons are axis
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputJoyconButtonKey(key: Int): Int {
|
||||||
|
// Joycon support is half dead. A lot of buttons can't be mapped
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputXboxButtonKey(key: Int): Int {
|
||||||
|
// The missing xbox buttons are axis
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputRazerButtonKey(key: Int): Int {
|
||||||
|
// The missing xbox buttons are axis
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputRedmagicButtonKey(key: Int): Int {
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputBackboneLabsButtonKey(key: Int): Int {
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInputGenericButtonKey(key: Int): Int {
|
||||||
|
return when (key) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
|
||||||
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
|
when (axis) {
|
||||||
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_L,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_X),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_R,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RX),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RY)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_R,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Z),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_LTRIGGER ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZL,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_BRAKE ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZL,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_RTRIGGER ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZR,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_GAS ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZR,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
|
||||||
|
setAxisDpadState(
|
||||||
|
playerNumber,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_HAT_X),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
||||||
|
// Joycon support is half dead. Right joystick doesn't work
|
||||||
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
|
when (axis) {
|
||||||
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_L,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_X),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_R,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Z),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_RX, MotionEvent.AXIS_RY ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_R,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RX),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RY)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
|
||||||
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
|
when (axis) {
|
||||||
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_L,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_X),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ ->
|
||||||
|
setStickState(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.StickType.STICK_R,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_Z),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_BRAKE ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZL,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_GAS ->
|
||||||
|
NativeLibrary.onGamePadButtonEvent(
|
||||||
|
playerNumber,
|
||||||
|
NativeLibrary.ButtonType.TRIGGER_ZR,
|
||||||
|
getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS))
|
||||||
|
)
|
||||||
|
MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y ->
|
||||||
|
setAxisDpadState(
|
||||||
|
playerNumber,
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_HAT_X),
|
||||||
|
event.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGameControllerIds(): Map<Int, Int> {
|
||||||
|
val gameControllerDeviceIds = mutableMapOf<Int, Int>()
|
||||||
val deviceIds = InputDevice.getDeviceIds()
|
val deviceIds = InputDevice.getDeviceIds()
|
||||||
var port = 0
|
var controllerSlot = 1
|
||||||
val inputSettings = NativeConfig.getInputSettings(true)
|
|
||||||
deviceIds.forEach { deviceId ->
|
deviceIds.forEach { deviceId ->
|
||||||
InputDevice.getDevice(deviceId)?.apply {
|
InputDevice.getDevice(deviceId)?.apply {
|
||||||
|
// Don't over-assign controllers
|
||||||
|
if (controllerSlot >= 8) {
|
||||||
|
return gameControllerDeviceIds
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the device has gamepad buttons, control sticks, or both.
|
// Verify that the device has gamepad buttons, control sticks, or both.
|
||||||
if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
|
||||||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
|
||||||
) {
|
) {
|
||||||
if (!gameControllerDeviceIds.contains(controllerNumber)) {
|
// This device is a game controller. Store its device ID.
|
||||||
gameControllerDeviceIds[controllerNumber] = YuzuPhysicalDevice(
|
if (deviceId and id and vendorId and productId != 0) {
|
||||||
this,
|
// Additionally filter out devices that have no ID
|
||||||
port,
|
gameControllerDeviceIds
|
||||||
inputSettings[port].useSystemVibrator
|
.takeIf { !it.contains(deviceId) }
|
||||||
)
|
?.put(deviceId, controllerSlot)
|
||||||
|
controllerSlot++
|
||||||
}
|
}
|
||||||
port++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gameControllerDeviceIds
|
return gameControllerDeviceIds
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateControllerData() {
|
|
||||||
androidControllers = getDevices()
|
|
||||||
androidControllers.forEach {
|
|
||||||
NativeInput.registerController(it.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the input overlay on a dedicated port for all player 1 vibrations
|
|
||||||
NativeInput.registerController(YuzuInputOverlayDevice(androidControllers.isEmpty(), 100))
|
|
||||||
registeredControllers.clear()
|
|
||||||
NativeInput.getInputDevices().forEach {
|
|
||||||
registeredControllers.add(ParamPackage(it))
|
|
||||||
}
|
|
||||||
registeredControllers.sortBy { it.get("port", 0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun InputDevice.getGUID(): String = String.format("%016x%016x", productId, vendorId)
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleOwner
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collects this [Flow] with a given [LifecycleOwner].
|
|
||||||
* @param scope [LifecycleOwner] that this [Flow] will be collected with.
|
|
||||||
* @param repeatState When to repeat collection on this [Flow].
|
|
||||||
* @param resetState Optional lambda to reset state of an underlying [MutableStateFlow] after
|
|
||||||
* [stateCollector] has been run.
|
|
||||||
* @param stateCollector Lambda that receives new state.
|
|
||||||
*/
|
|
||||||
inline fun <reified T> Flow<T>.collect(
|
|
||||||
scope: LifecycleOwner,
|
|
||||||
repeatState: Lifecycle.State = Lifecycle.State.CREATED,
|
|
||||||
crossinline resetState: () -> Unit = {},
|
|
||||||
crossinline stateCollector: (state: T) -> Unit
|
|
||||||
) {
|
|
||||||
scope.apply {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(repeatState) {
|
|
||||||
this@collect.collect {
|
|
||||||
stateCollector(it)
|
|
||||||
resetState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,8 +6,6 @@ package org.yuzu.yuzu_emu.utils
|
|||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
|
||||||
|
|
||||||
import org.yuzu.yuzu_emu.features.input.model.PlayerInput
|
|
||||||
|
|
||||||
object NativeConfig {
|
object NativeConfig {
|
||||||
/**
|
/**
|
||||||
* Loads global config.
|
* Loads global config.
|
||||||
@ -170,17 +168,4 @@ object NativeConfig {
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
|
external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
external fun getInputSettings(global: Boolean): Array<PlayerInput>
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
external fun setInputSettings(value: Array<PlayerInput>, global: Boolean)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves control values for a specific player
|
|
||||||
* Must be used when per game config is loaded
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
external fun saveControlPlayerValues()
|
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import android.os.Build
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
|
||||||
class NfcReader(private val activity: Activity) {
|
class NfcReader(private val activity: Activity) {
|
||||||
private var nfcAdapter: NfcAdapter? = null
|
private var nfcAdapter: NfcAdapter? = null
|
||||||
@ -76,12 +76,12 @@ class NfcReader(private val activity: Activity) {
|
|||||||
amiibo.connect()
|
amiibo.connect()
|
||||||
|
|
||||||
val tagData = ntag215ReadAll(amiibo) ?: return
|
val tagData = ntag215ReadAll(amiibo) ?: return
|
||||||
NativeInput.onReadNfcTag(tagData)
|
NativeLibrary.onReadNfcTag(tagData)
|
||||||
|
|
||||||
nfcAdapter?.ignore(
|
nfcAdapter?.ignore(
|
||||||
tag,
|
tag,
|
||||||
1000,
|
1000,
|
||||||
{ NativeInput.onRemoveNfcTag() },
|
{ NativeLibrary.onRemoveNfcTag() },
|
||||||
Handler(Looper.getMainLooper())
|
Handler(Looper.getMainLooper())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
// Kotlin version of src/common/param_package.h
|
|
||||||
class ParamPackage(serialized: String = "") {
|
|
||||||
private val KEY_VALUE_SEPARATOR = ":"
|
|
||||||
private val PARAM_SEPARATOR = ","
|
|
||||||
|
|
||||||
private val ESCAPE_CHARACTER = "$"
|
|
||||||
private val KEY_VALUE_SEPARATOR_ESCAPE = "$0"
|
|
||||||
private val PARAM_SEPARATOR_ESCAPE = "$1"
|
|
||||||
private val ESCAPE_CHARACTER_ESCAPE = "$2"
|
|
||||||
|
|
||||||
private val EMPTY_PLACEHOLDER = "[empty]"
|
|
||||||
|
|
||||||
val data = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
val pairs = serialized.split(PARAM_SEPARATOR)
|
|
||||||
for (pair in pairs) {
|
|
||||||
val keyValue = pair.split(KEY_VALUE_SEPARATOR).toMutableList()
|
|
||||||
if (keyValue.size != 2) {
|
|
||||||
Log.error("[ParamPackage] Invalid key pair $keyValue")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
keyValue.forEachIndexed { i: Int, _: String ->
|
|
||||||
keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR_ESCAPE, KEY_VALUE_SEPARATOR)
|
|
||||||
keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR_ESCAPE, PARAM_SEPARATOR)
|
|
||||||
keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER_ESCAPE, ESCAPE_CHARACTER)
|
|
||||||
}
|
|
||||||
|
|
||||||
set(keyValue[0], keyValue[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(params: List<Pair<String, String>>) : this() {
|
|
||||||
params.forEach {
|
|
||||||
data[it.first] = it.second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(): String {
|
|
||||||
if (data.isEmpty()) {
|
|
||||||
return EMPTY_PLACEHOLDER
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = StringBuilder()
|
|
||||||
data.forEach {
|
|
||||||
val keyValue = mutableListOf(it.key, it.value)
|
|
||||||
keyValue.forEachIndexed { i, _ ->
|
|
||||||
keyValue[i] = keyValue[i].replace(ESCAPE_CHARACTER, ESCAPE_CHARACTER_ESCAPE)
|
|
||||||
keyValue[i] = keyValue[i].replace(PARAM_SEPARATOR, PARAM_SEPARATOR_ESCAPE)
|
|
||||||
keyValue[i] = keyValue[i].replace(KEY_VALUE_SEPARATOR, KEY_VALUE_SEPARATOR_ESCAPE)
|
|
||||||
}
|
|
||||||
result.append("${keyValue[0]}$KEY_VALUE_SEPARATOR${keyValue[1]}$PARAM_SEPARATOR")
|
|
||||||
}
|
|
||||||
return result.removeSuffix(PARAM_SEPARATOR).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(key: String, defaultValue: String): String =
|
|
||||||
if (has(key)) {
|
|
||||||
data[key]!!
|
|
||||||
} else {
|
|
||||||
Log.debug("[ParamPackage] key $key not found")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(key: String, defaultValue: Int): Int =
|
|
||||||
if (has(key)) {
|
|
||||||
try {
|
|
||||||
data[key]!!.toInt()
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
Log.debug("[ParamPackage] failed to convert ${data[key]!!} to int")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.debug("[ParamPackage] key $key not found")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Int.toBoolean(): Boolean =
|
|
||||||
if (this == 1) {
|
|
||||||
true
|
|
||||||
} else if (this == 0) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
throw Exception("Tried to convert a value to a boolean that was not 0 or 1!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(key: String, defaultValue: Boolean): Boolean =
|
|
||||||
if (has(key)) {
|
|
||||||
try {
|
|
||||||
get(key, if (defaultValue) 1 else 0).toBoolean()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.debug("[ParamPackage] failed to convert ${data[key]!!} to boolean")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.debug("[ParamPackage] key $key not found")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(key: String, defaultValue: Float): Float =
|
|
||||||
if (has(key)) {
|
|
||||||
try {
|
|
||||||
data[key]!!.toFloat()
|
|
||||||
} catch (e: NumberFormatException) {
|
|
||||||
Log.debug("[ParamPackage] failed to convert ${data[key]!!} to float")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.debug("[ParamPackage] key $key not found")
|
|
||||||
defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun set(key: String, value: String) {
|
|
||||||
data[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun set(key: String, value: Int) {
|
|
||||||
data[key] = value.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Boolean.toInt(): Int = if (this) 1 else 0
|
|
||||||
fun set(key: String, value: Boolean) {
|
|
||||||
data[key] = value.toInt().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun set(key: String, value: Float) {
|
|
||||||
data[key] = value.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun has(key: String): Boolean = data.containsKey(key)
|
|
||||||
|
|
||||||
fun erase(key: String) = data.remove(key)
|
|
||||||
|
|
||||||
fun clear() = data.clear()
|
|
||||||
}
|
|
@ -3,10 +3,8 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
|
|
||||||
object ViewUtils {
|
object ViewUtils {
|
||||||
fun showView(view: View, length: Long = 300) {
|
fun showView(view: View, length: Long = 300) {
|
||||||
@ -59,35 +57,4 @@ object ViewUtils {
|
|||||||
}
|
}
|
||||||
this.layoutParams = layoutParams
|
this.layoutParams = layoutParams
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows or hides a view.
|
|
||||||
* @param visible Whether a view will be made View.VISIBLE or View.INVISIBLE/GONE.
|
|
||||||
* @param gone Optional parameter for hiding a view. Uses View.GONE if true and View.INVISIBLE otherwise.
|
|
||||||
*/
|
|
||||||
fun View.setVisible(visible: Boolean, gone: Boolean = true) {
|
|
||||||
visibility = if (visible) {
|
|
||||||
View.VISIBLE
|
|
||||||
} else {
|
|
||||||
if (gone) {
|
|
||||||
View.GONE
|
|
||||||
} else {
|
|
||||||
View.INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a marquee on some text.
|
|
||||||
* @param delay Optional parameter for changing the start delay. 3 seconds of delay by default.
|
|
||||||
*/
|
|
||||||
fun TextView.marquee(delay: Long = 3000) {
|
|
||||||
ellipsize = null
|
|
||||||
marqueeRepeatLimit = -1
|
|
||||||
isSingleLine = true
|
|
||||||
postDelayed({
|
|
||||||
ellipsize = TextUtils.TruncateAt.MARQUEE
|
|
||||||
isSelected = true
|
|
||||||
}, delay)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ add_library(yuzu-android SHARED
|
|||||||
native_log.cpp
|
native_log.cpp
|
||||||
android_config.cpp
|
android_config.cpp
|
||||||
android_config.h
|
android_config.h
|
||||||
native_input.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <common/logging/log.h>
|
|
||||||
#include <input_common/main.h>
|
|
||||||
#include "android_config.h"
|
#include "android_config.h"
|
||||||
#include "android_settings.h"
|
#include "android_settings.h"
|
||||||
#include "common/settings_setting.h"
|
#include "common/settings_setting.h"
|
||||||
@ -34,7 +32,6 @@ void AndroidConfig::ReadAndroidValues() {
|
|||||||
ReadOverlayValues();
|
ReadOverlayValues();
|
||||||
}
|
}
|
||||||
ReadDriverValues();
|
ReadDriverValues();
|
||||||
ReadAndroidControlValues();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidConfig::ReadAndroidUIValues() {
|
void AndroidConfig::ReadAndroidUIValues() {
|
||||||
@ -110,76 +107,6 @@ void AndroidConfig::ReadOverlayValues() {
|
|||||||
EndGroup();
|
EndGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidConfig::ReadAndroidPlayerValues(std::size_t player_index) {
|
|
||||||
std::string player_prefix;
|
|
||||||
if (type != ConfigType::InputProfile) {
|
|
||||||
player_prefix.append("player_").append(ToString(player_index)).append("_");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& player = Settings::values.players.GetValue()[player_index];
|
|
||||||
if (IsCustomConfig()) {
|
|
||||||
const auto profile_name =
|
|
||||||
ReadStringSetting(std::string(player_prefix).append("profile_name"));
|
|
||||||
if (profile_name.empty()) {
|
|
||||||
// Use the global input config
|
|
||||||
player = Settings::values.players.GetValue(true)[player_index];
|
|
||||||
player.profile_name = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android doesn't have default options for controllers. We have the input overlay for that.
|
|
||||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
|
||||||
const std::string default_param;
|
|
||||||
auto& player_buttons = player.buttons[i];
|
|
||||||
|
|
||||||
player_buttons = ReadStringSetting(
|
|
||||||
std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
|
|
||||||
if (player_buttons.empty()) {
|
|
||||||
player_buttons = default_param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
|
||||||
const std::string default_param;
|
|
||||||
auto& player_analogs = player.analogs[i];
|
|
||||||
|
|
||||||
player_analogs = ReadStringSetting(
|
|
||||||
std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
|
|
||||||
if (player_analogs.empty()) {
|
|
||||||
player_analogs = default_param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
|
||||||
const std::string default_param;
|
|
||||||
auto& player_motions = player.motions[i];
|
|
||||||
|
|
||||||
player_motions = ReadStringSetting(
|
|
||||||
std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
|
|
||||||
if (player_motions.empty()) {
|
|
||||||
player_motions = default_param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
player.use_system_vibrator = ReadBooleanSetting(
|
|
||||||
std::string(player_prefix).append("use_system_vibrator"), player_index == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidConfig::ReadAndroidControlValues() {
|
|
||||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
|
||||||
|
|
||||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
|
||||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
|
||||||
ReadAndroidPlayerValues(p);
|
|
||||||
}
|
|
||||||
if (IsCustomConfig()) {
|
|
||||||
EndGroup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ReadDebugControlValues();
|
|
||||||
// ReadHidbusValues();
|
|
||||||
|
|
||||||
EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidConfig::SaveAndroidValues() {
|
void AndroidConfig::SaveAndroidValues() {
|
||||||
if (global) {
|
if (global) {
|
||||||
SaveAndroidUIValues();
|
SaveAndroidUIValues();
|
||||||
@ -187,7 +114,6 @@ void AndroidConfig::SaveAndroidValues() {
|
|||||||
SaveOverlayValues();
|
SaveOverlayValues();
|
||||||
}
|
}
|
||||||
SaveDriverValues();
|
SaveDriverValues();
|
||||||
SaveAndroidControlValues();
|
|
||||||
|
|
||||||
WriteToIni();
|
WriteToIni();
|
||||||
}
|
}
|
||||||
@ -261,52 +187,6 @@ void AndroidConfig::SaveOverlayValues() {
|
|||||||
EndGroup();
|
EndGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidConfig::SaveAndroidPlayerValues(std::size_t player_index) {
|
|
||||||
std::string player_prefix;
|
|
||||||
if (type != ConfigType::InputProfile) {
|
|
||||||
player_prefix = std::string("player_").append(ToString(player_index)).append("_");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
|
||||||
if (IsCustomConfig() && player.profile_name.empty()) {
|
|
||||||
// No custom profile selected
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string default_param;
|
|
||||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
|
||||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
|
|
||||||
player.buttons[i], std::make_optional(default_param));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
|
||||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
|
|
||||||
player.analogs[i], std::make_optional(default_param));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
|
||||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
|
|
||||||
player.motions[i], std::make_optional(default_param));
|
|
||||||
}
|
|
||||||
WriteBooleanSetting(std::string(player_prefix).append("use_system_vibrator"),
|
|
||||||
player.use_system_vibrator, std::make_optional(player_index == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidConfig::SaveAndroidControlValues() {
|
|
||||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
|
||||||
|
|
||||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
|
||||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
|
||||||
SaveAndroidPlayerValues(p);
|
|
||||||
}
|
|
||||||
if (IsCustomConfig()) {
|
|
||||||
EndGroup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// SaveDebugControlValues();
|
|
||||||
// SaveHidbusValues();
|
|
||||||
|
|
||||||
EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
|
std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
|
||||||
auto& map = Settings::values.linkage.by_category;
|
auto& map = Settings::values.linkage.by_category;
|
||||||
if (map.contains(category)) {
|
if (map.contains(category)) {
|
||||||
@ -314,24 +194,3 @@ std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::
|
|||||||
}
|
}
|
||||||
return AndroidSettings::values.linkage.by_category[category];
|
return AndroidSettings::values.linkage.by_category[category];
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidConfig::ReadAndroidControlPlayerValues(std::size_t player_index) {
|
|
||||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
|
||||||
|
|
||||||
ReadPlayerValues(player_index);
|
|
||||||
ReadAndroidPlayerValues(player_index);
|
|
||||||
|
|
||||||
EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidConfig::SaveAndroidControlPlayerValues(std::size_t player_index) {
|
|
||||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
|
||||||
|
|
||||||
LOG_DEBUG(Config, "Saving players control configuration values");
|
|
||||||
SavePlayerValues(player_index);
|
|
||||||
SaveAndroidPlayerValues(player_index);
|
|
||||||
|
|
||||||
EndGroup();
|
|
||||||
|
|
||||||
WriteToIni();
|
|
||||||
}
|
|
||||||
|
@ -13,12 +13,7 @@ public:
|
|||||||
void ReloadAllValues() override;
|
void ReloadAllValues() override;
|
||||||
void SaveAllValues() override;
|
void SaveAllValues() override;
|
||||||
|
|
||||||
void ReadAndroidControlPlayerValues(std::size_t player_index);
|
|
||||||
void SaveAndroidControlPlayerValues(std::size_t player_index);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ReadAndroidPlayerValues(std::size_t player_index);
|
|
||||||
void ReadAndroidControlValues();
|
|
||||||
void ReadAndroidValues();
|
void ReadAndroidValues();
|
||||||
void ReadAndroidUIValues();
|
void ReadAndroidUIValues();
|
||||||
void ReadDriverValues();
|
void ReadDriverValues();
|
||||||
@ -32,8 +27,6 @@ protected:
|
|||||||
void ReadUILayoutValues() override {}
|
void ReadUILayoutValues() override {}
|
||||||
void ReadMultiplayerValues() override {}
|
void ReadMultiplayerValues() override {}
|
||||||
|
|
||||||
void SaveAndroidPlayerValues(std::size_t player_index);
|
|
||||||
void SaveAndroidControlValues();
|
|
||||||
void SaveAndroidValues();
|
void SaveAndroidValues();
|
||||||
void SaveAndroidUIValues();
|
void SaveAndroidUIValues();
|
||||||
void SaveDriverValues();
|
void SaveDriverValues();
|
||||||
|
@ -38,13 +38,6 @@ struct Values {
|
|||||||
Settings::Specialization::Default,
|
Settings::Specialization::Default,
|
||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
Settings::Setting<s32> vertical_alignment{linkage,
|
|
||||||
0,
|
|
||||||
"vertical_alignment",
|
|
||||||
Settings::Category::Android,
|
|
||||||
Settings::Specialization::Default,
|
|
||||||
true,
|
|
||||||
true};
|
|
||||||
|
|
||||||
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
|
Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
|
||||||
Settings::Category::GpuDriver};
|
Settings::Category::GpuDriver};
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include "common/android/id_cache.h"
|
#include "common/android/id_cache.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "input_common/drivers/android.h"
|
|
||||||
#include "input_common/drivers/touch_screen.h"
|
#include "input_common/drivers/touch_screen.h"
|
||||||
#include "input_common/drivers/virtual_amiibo.h"
|
#include "input_common/drivers/virtual_amiibo.h"
|
||||||
#include "input_common/drivers/virtual_gamepad.h"
|
#include "input_common/drivers/virtual_gamepad.h"
|
||||||
@ -25,18 +24,39 @@ void EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
|||||||
|
|
||||||
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
|
void EmuWindow_Android::OnTouchPressed(int id, float x, float y) {
|
||||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x,
|
m_input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, id);
|
||||||
touch_y, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_Android::OnTouchMoved(int id, float x, float y) {
|
void EmuWindow_Android::OnTouchMoved(int id, float x, float y) {
|
||||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x,
|
m_input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, id);
|
||||||
touch_y, id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_Android::OnTouchReleased(int id) {
|
void EmuWindow_Android::OnTouchReleased(int id) {
|
||||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id);
|
m_input_subsystem->GetTouchScreen()->TouchReleased(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuWindow_Android::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) {
|
||||||
|
m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuWindow_Android::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) {
|
||||||
|
m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuWindow_Android::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x,
|
||||||
|
float gyro_y, float gyro_z, float accel_x,
|
||||||
|
float accel_y, float accel_z) {
|
||||||
|
m_input_subsystem->GetVirtualGamepad()->SetMotionState(
|
||||||
|
player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuWindow_Android::OnReadNfcTag(std::span<u8> data) {
|
||||||
|
m_input_subsystem->GetVirtualAmiibo()->LoadAmiibo(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuWindow_Android::OnRemoveNfcTag() {
|
||||||
|
m_input_subsystem->GetVirtualAmiibo()->CloseAmiibo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuWindow_Android::OnFrameDisplayed() {
|
void EmuWindow_Android::OnFrameDisplayed() {
|
||||||
@ -47,9 +67,10 @@ void EmuWindow_Android::OnFrameDisplayed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
|
EmuWindow_Android::EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem,
|
||||||
|
ANativeWindow* surface,
|
||||||
std::shared_ptr<Common::DynamicLibrary> driver_library)
|
std::shared_ptr<Common::DynamicLibrary> driver_library)
|
||||||
: m_driver_library{driver_library} {
|
: m_input_subsystem{input_subsystem}, m_driver_library{driver_library} {
|
||||||
LOG_INFO(Frontend, "initializing");
|
LOG_INFO(Frontend, "initializing");
|
||||||
|
|
||||||
if (!surface) {
|
if (!surface) {
|
||||||
@ -59,4 +80,10 @@ EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface,
|
|||||||
|
|
||||||
OnSurfaceChanged(surface);
|
OnSurfaceChanged(surface);
|
||||||
window_info.type = Core::Frontend::WindowSystemType::Android;
|
window_info.type = Core::Frontend::WindowSystemType::Android;
|
||||||
|
|
||||||
|
m_input_subsystem->Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuWindow_Android::~EmuWindow_Android() {
|
||||||
|
m_input_subsystem->Shutdown();
|
||||||
}
|
}
|
||||||
|
@ -30,17 +30,22 @@ private:
|
|||||||
class EmuWindow_Android final : public Core::Frontend::EmuWindow {
|
class EmuWindow_Android final : public Core::Frontend::EmuWindow {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EmuWindow_Android(ANativeWindow* surface,
|
EmuWindow_Android(InputCommon::InputSubsystem* input_subsystem, ANativeWindow* surface,
|
||||||
std::shared_ptr<Common::DynamicLibrary> driver_library);
|
std::shared_ptr<Common::DynamicLibrary> driver_library);
|
||||||
|
|
||||||
~EmuWindow_Android() = default;
|
~EmuWindow_Android();
|
||||||
|
|
||||||
void OnSurfaceChanged(ANativeWindow* surface);
|
void OnSurfaceChanged(ANativeWindow* surface);
|
||||||
void OnFrameDisplayed() override;
|
|
||||||
|
|
||||||
void OnTouchPressed(int id, float x, float y);
|
void OnTouchPressed(int id, float x, float y);
|
||||||
void OnTouchMoved(int id, float x, float y);
|
void OnTouchMoved(int id, float x, float y);
|
||||||
void OnTouchReleased(int id);
|
void OnTouchReleased(int id);
|
||||||
|
void OnGamepadButtonEvent(int player_index, int button_id, bool pressed);
|
||||||
|
void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y);
|
||||||
|
void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
|
||||||
|
float gyro_z, float accel_x, float accel_y, float accel_z);
|
||||||
|
void OnReadNfcTag(std::span<u8> data);
|
||||||
|
void OnRemoveNfcTag();
|
||||||
|
void OnFrameDisplayed() override;
|
||||||
|
|
||||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||||
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
|
return {std::make_unique<GraphicsContext_Android>(m_driver_library)};
|
||||||
@ -50,6 +55,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
InputCommon::InputSubsystem* m_input_subsystem{};
|
||||||
|
|
||||||
float m_window_width{};
|
float m_window_width{};
|
||||||
float m_window_height{};
|
float m_window_height{};
|
||||||
|
|
||||||
|
@ -49,7 +49,9 @@
|
|||||||
#include "core/frontend/applets/profile_select.h"
|
#include "core/frontend/applets/profile_select.h"
|
||||||
#include "core/frontend/applets/software_keyboard.h"
|
#include "core/frontend/applets/software_keyboard.h"
|
||||||
#include "core/frontend/applets/web_browser.h"
|
#include "core/frontend/applets/web_browser.h"
|
||||||
|
#include "core/hle/service/am/applet_ae.h"
|
||||||
#include "core/hle/service/am/applet_manager.h"
|
#include "core/hle/service/am/applet_manager.h"
|
||||||
|
#include "core/hle/service/am/applet_oe.h"
|
||||||
#include "core/hle/service/am/frontend/applets.h"
|
#include "core/hle/service/am/frontend/applets.h"
|
||||||
#include "core/hle/service/filesystem/filesystem.h"
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
@ -88,10 +90,6 @@ FileSys::ManualContentProvider* EmulationSession::GetContentProvider() {
|
|||||||
return m_manual_provider.get();
|
return m_manual_provider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() {
|
|
||||||
return m_input_subsystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmuWindow_Android& EmulationSession::Window() const {
|
const EmuWindow_Android& EmulationSession::Window() const {
|
||||||
return *m_window;
|
return *m_window;
|
||||||
}
|
}
|
||||||
@ -202,8 +200,6 @@ void EmulationSession::InitializeSystem(bool reload) {
|
|||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||||
Common::Log::Start();
|
Common::Log::Start();
|
||||||
|
|
||||||
m_input_subsystem.Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize filesystem.
|
// Initialize filesystem.
|
||||||
@ -228,7 +224,8 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
|||||||
std::scoped_lock lock(m_mutex);
|
std::scoped_lock lock(m_mutex);
|
||||||
|
|
||||||
// Create the render window.
|
// Create the render window.
|
||||||
m_window = std::make_unique<EmuWindow_Android>(m_native_window, m_vulkan_library);
|
m_window =
|
||||||
|
std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
|
||||||
|
|
||||||
// Initialize system.
|
// Initialize system.
|
||||||
jauto android_keyboard = std::make_unique<Common::Android::SoftwareKeyboard::AndroidKeyboard>();
|
jauto android_keyboard = std::make_unique<Common::Android::SoftwareKeyboard::AndroidKeyboard>();
|
||||||
@ -292,9 +289,6 @@ void EmulationSession::ShutdownEmulation() {
|
|||||||
// Unload user input.
|
// Unload user input.
|
||||||
m_system.HIDCore().UnloadInputDevices();
|
m_system.HIDCore().UnloadInputDevices();
|
||||||
|
|
||||||
// Enable all controllers
|
|
||||||
m_system.HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
|
||||||
|
|
||||||
// Shutdown the main emulated process
|
// Shutdown the main emulated process
|
||||||
if (m_load_result == Core::SystemResultStatus::Success) {
|
if (m_load_result == Core::SystemResultStatus::Success) {
|
||||||
m_system.DetachDebugger();
|
m_system.DetachDebugger();
|
||||||
@ -363,6 +357,60 @@ void EmulationSession::RunEmulation() {
|
|||||||
m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
|
m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EmulationSession::IsHandheldOnly() {
|
||||||
|
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||||
|
|
||||||
|
if (npad_style_set.fullkey == 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (npad_style_set.handheld == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !Settings::IsDockedMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
|
||||||
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||||
|
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||||
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||||
|
|
||||||
|
// Ensure that player1 is configured correctly and handheld disconnected
|
||||||
|
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||||
|
jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||||
|
|
||||||
|
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||||
|
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
|
||||||
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
|
||||||
|
handheld->Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||||
|
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||||
|
jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||||
|
|
||||||
|
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||||
|
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||||
|
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||||
|
player1->Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!controller->IsConnected()) {
|
||||||
|
controller->Connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||||
|
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||||
|
controller->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
|
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
|
||||||
return m_software_keyboard;
|
return m_software_keyboard;
|
||||||
}
|
}
|
||||||
@ -407,9 +455,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath,
|
|||||||
const size_t program_index,
|
const size_t program_index,
|
||||||
const bool frontend_initiated) {
|
const bool frontend_initiated) {
|
||||||
MicroProfileOnThreadCreate("EmuThread");
|
MicroProfileOnThreadCreate("EmuThread");
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||||
MicroProfileShutdown();
|
|
||||||
};
|
|
||||||
|
|
||||||
LOG_INFO(Frontend, "starting");
|
LOG_INFO(Frontend, "starting");
|
||||||
|
|
||||||
@ -418,9 +464,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath,
|
|||||||
return Core::SystemResultStatus::ErrorLoader;
|
return Core::SystemResultStatus::ErrorLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
|
||||||
EmulationSession::GetInstance().ShutdownEmulation();
|
|
||||||
};
|
|
||||||
|
|
||||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index,
|
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index,
|
||||||
frontend_initiated);
|
frontend_initiated);
|
||||||
@ -532,14 +576,14 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_getSystemDriverInfo(
|
|||||||
nullptr, nullptr, file_redirect_dir_, nullptr);
|
nullptr, nullptr, file_redirect_dir_, nullptr);
|
||||||
auto driver_library = std::make_shared<Common::DynamicLibrary>(handle);
|
auto driver_library = std::make_shared<Common::DynamicLibrary>(handle);
|
||||||
InputCommon::InputSubsystem input_subsystem;
|
InputCommon::InputSubsystem input_subsystem;
|
||||||
auto window =
|
auto m_window = std::make_unique<EmuWindow_Android>(
|
||||||
std::make_unique<EmuWindow_Android>(ANativeWindow_fromSurface(env, j_surf), driver_library);
|
&input_subsystem, ANativeWindow_fromSurface(env, j_surf), driver_library);
|
||||||
|
|
||||||
Vulkan::vk::InstanceDispatch dld;
|
Vulkan::vk::InstanceDispatch dld;
|
||||||
Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance(
|
Vulkan::vk::Instance vk_instance = Vulkan::CreateInstance(
|
||||||
*driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android);
|
*driver_library, dld, VK_API_VERSION_1_1, Core::Frontend::WindowSystemType::Android);
|
||||||
|
|
||||||
auto surface = Vulkan::CreateSurface(vk_instance, window->GetWindowInfo());
|
auto surface = Vulkan::CreateSurface(vk_instance, m_window->GetWindowInfo());
|
||||||
|
|
||||||
auto device = Vulkan::CreateDevice(vk_instance, dld, *surface);
|
auto device = Vulkan::CreateDevice(vk_instance, dld, *surface);
|
||||||
|
|
||||||
@ -580,6 +624,103 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
|||||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
|
||||||
|
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType(JNIEnv* env, jclass clazz,
|
||||||
|
jint j_device, jint j_type) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().SetDeviceType(j_device, j_type);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent(JNIEnv* env, jclass clazz,
|
||||||
|
jint j_device) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent(JNIEnv* env, jclass clazz,
|
||||||
|
jint j_device) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent(JNIEnv* env, jclass clazz,
|
||||||
|
jint j_device, jint j_button,
|
||||||
|
jint action) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
// Ensure gamepad is connected
|
||||||
|
EmulationSession::GetInstance().OnGamepadConnectEvent(j_device);
|
||||||
|
EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button,
|
||||||
|
action != 0);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadJoystickEvent(JNIEnv* env, jclass clazz,
|
||||||
|
jint j_device, jint stick_id,
|
||||||
|
jfloat x, jfloat y) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(j_device, stick_id, x, y);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadMotionEvent(
|
||||||
|
JNIEnv* env, jclass clazz, jint j_device, jlong delta_timestamp, jfloat gyro_x, jfloat gyro_y,
|
||||||
|
jfloat gyro_z, jfloat accel_x, jfloat accel_y, jfloat accel_z) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnGamepadMotionEvent(
|
||||||
|
j_device, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onReadNfcTag(JNIEnv* env, jclass clazz,
|
||||||
|
jbyteArray j_data) {
|
||||||
|
jboolean isCopy{false};
|
||||||
|
std::span<u8> data(reinterpret_cast<u8*>(env->GetByteArrayElements(j_data, &isCopy)),
|
||||||
|
static_cast<size_t>(env->GetArrayLength(j_data)));
|
||||||
|
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnReadNfcTag(data);
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onRemoveNfcTag(JNIEnv* env, jclass clazz) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnRemoveNfcTag();
|
||||||
|
}
|
||||||
|
return static_cast<jboolean>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchPressed(JNIEnv* env, jclass clazz, jint id,
|
||||||
|
jfloat x, jfloat y) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnTouchPressed(id, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, jint id,
|
||||||
|
jfloat x, jfloat y) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnTouchMoved(id, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass clazz, jint id) {
|
||||||
|
if (EmulationSession::GetInstance().IsRunning()) {
|
||||||
|
EmulationSession::GetInstance().Window().OnTouchReleased(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
|
||||||
jboolean reload) {
|
jboolean reload) {
|
||||||
// Initialize the emulated system.
|
// Initialize the emulated system.
|
||||||
@ -620,7 +761,6 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuDriver(JNIEnv* env, jobject
|
|||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) {
|
||||||
EmulationSession::GetInstance().System().ApplySettings();
|
EmulationSession::GetInstance().System().ApplySettings();
|
||||||
EmulationSession::GetInstance().System().HIDCore().ReloadInputDevices();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) {
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) {
|
||||||
@ -668,7 +808,7 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmptyUserDirectory(JNIEnv*
|
|||||||
ASSERT(user_id);
|
ASSERT(user_id);
|
||||||
|
|
||||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||||
{}, vfs_nand_dir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Account, 1,
|
{}, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, 1,
|
||||||
user_id->AsU128(), 0);
|
user_id->AsU128(), 0);
|
||||||
|
|
||||||
const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
|
const auto full_path = Common::FS::ConcatPathSafe(nand_dir, user_save_data_path);
|
||||||
@ -836,8 +976,8 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject j
|
|||||||
FileSys::OpenMode::Read);
|
FileSys::OpenMode::Read);
|
||||||
|
|
||||||
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath(
|
||||||
{}, vfsNandDir, FileSys::SaveDataSpaceId::User, FileSys::SaveDataType::Account, program_id,
|
{}, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData,
|
||||||
user_id->AsU128(), 0);
|
program_id, user_id->AsU128(), 0);
|
||||||
return Common::Android::ToJString(env, user_save_data_path);
|
return Common::Android::ToJString(env, user_save_data_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ public:
|
|||||||
const Core::System& System() const;
|
const Core::System& System() const;
|
||||||
Core::System& System();
|
Core::System& System();
|
||||||
FileSys::ManualContentProvider* GetContentProvider();
|
FileSys::ManualContentProvider* GetContentProvider();
|
||||||
InputCommon::InputSubsystem& GetInputSubsystem();
|
|
||||||
|
|
||||||
const EmuWindow_Android& Window() const;
|
const EmuWindow_Android& Window() const;
|
||||||
EmuWindow_Android& Window();
|
EmuWindow_Android& Window();
|
||||||
@ -51,6 +50,10 @@ public:
|
|||||||
const std::size_t program_index,
|
const std::size_t program_index,
|
||||||
const bool frontend_initiated);
|
const bool frontend_initiated);
|
||||||
|
|
||||||
|
bool IsHandheldOnly();
|
||||||
|
void SetDeviceType([[maybe_unused]] int index, int type);
|
||||||
|
void OnGamepadConnectEvent([[maybe_unused]] int index);
|
||||||
|
void OnGamepadDisconnectEvent([[maybe_unused]] int index);
|
||||||
Common::Android::SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
|
Common::Android::SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
|
||||||
|
|
||||||
static void OnEmulationStarted();
|
static void OnEmulationStarted();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user