Compare commits
295 Commits
43404b3326
...
f73f265bfa
Author | SHA1 | Date |
---|---|---|
F David | f73f265bfa | |
Narr the Reg | 15e6e48bef | |
Pengfei Zhu | 2f57c5a0e9 | |
Pengfei Zhu | 338e088b9d | |
The yuzu Community | 56c9107d08 | |
liamwhite | dc94882c90 | |
liamwhite | 30567a5909 | |
liamwhite | f1b1530249 | |
liamwhite | 6948ac8c16 | |
liamwhite | b2e129eaa5 | |
liamwhite | 1de37306a5 | |
liamwhite | 9bc85dda5f | |
Liam | c7174d5f61 | |
Narr the Reg | 1bec420695 | |
liamwhite | 79edad2533 | |
liamwhite | ce62fa6f7b | |
Liam | a0e254e7c4 | |
Liam | 25c3bbba0e | |
Liam | d66ca8b731 | |
Liam | fd9ed54f27 | |
liamwhite | f9bfdb1555 | |
liamwhite | 15831b19a3 | |
liamwhite | 8416d1c028 | |
Merry | 4d5d37ae61 | |
german77 | e62cea20d1 | |
Narr the Reg | 9e27dbb53b | |
Liam | dc50b95a47 | |
Liam | 4050242cf3 | |
Liam | fd718f350c | |
Liam | 0d6fd12231 | |
Liam | f297e98a9e | |
Liam | 637c54e205 | |
Liam | f045fa576b | |
Liam | 692ba0fa7d | |
liamwhite | a93d249ac1 | |
german77 | 9fccccedee | |
german77 | ca7f949ee8 | |
liamwhite | 05f94dc5fc | |
liamwhite | dcf7698924 | |
Narr the Reg | 7b68d7d467 | |
german77 | 4741e50047 | |
Liam | 7836c0867d | |
liamwhite | d1e0039bc8 | |
liamwhite | 7a51eaa727 | |
liamwhite | 6c40d75e47 | |
Narr the Reg | 0a0c257206 | |
Narr the Reg | 7019023cbc | |
Narr the Reg | c48c182fe0 | |
Narr the Reg | 98be02898b | |
Narr the Reg | e1bdeb2942 | |
Narr the Reg | 015d666a4d | |
Narr the Reg | 624c90a439 | |
Narr the Reg | 0fb26acccc | |
wheremyfoodat | ed315fb8a5 | |
Narr the Reg | fc6a87bba1 | |
Narr the Reg | d08f201e0c | |
t895 | 0369c65870 | |
liamwhite | 975d6f1ec4 | |
liamwhite | 7c9e2255be | |
liamwhite | 9f6818a6e5 | |
Liam | f1c16b487a | |
Liam | 6512f39061 | |
Liam | 22b91afa69 | |
liamwhite | 77107ba124 | |
liamwhite | fa4dec9fe9 | |
liamwhite | 215e887be0 | |
liamwhite | 0da6704fc2 | |
Liam | 812754edec | |
Liam | 964e19ab56 | |
liamwhite | 9dc624f5dc | |
liamwhite | dad9ea3e07 | |
Liam | 2c00599a53 | |
FearlessTobi | 2786d34dd7 | |
Narr the Reg | 864b046500 | |
liamwhite | d12d9dad40 | |
Narr the Reg | 2b3f1d3fc5 | |
Narr the Reg | 984396a21a | |
Narr the Reg | 4f95ee5209 | |
Liam | c04567fad4 | |
Liam | 89c2fd3d28 | |
Liam | 5ab49c833d | |
Liam | 0e74204aad | |
Liam | a37bd0b9a7 | |
Liam | 01d89acd13 | |
Liam | e85466c1ae | |
Liam | 352297d361 | |
Liam | 6c2d6cff19 | |
Liam | e540757279 | |
Liam | a8bca24292 | |
Liam | 5f3c03d6a8 | |
Liam | 6b956a6951 | |
Liam | 8689370830 | |
Liam | 8ffa27b311 | |
Liam | 6334616b44 | |
liamwhite | 8bbc209950 | |
liamwhite | 9e1a67b950 | |
t895 | de5422b1fd | |
t895 | 45f450fca5 | |
t895 | 9a3fd76b25 | |
Charles Lombardo | 60fc6df407 | |
Liam | 2e4a6b7f92 | |
Liam | 5f90bd88da | |
Liam | c575a85233 | |
Liam | ea4703cb31 | |
Liam | 0471e54e5a | |
Liam | 6012c9fe3a | |
Liam | f65539504f | |
Liam | 62083fcafd | |
Liam | 2e5a9cf119 | |
Liam | a45b8bc9bc | |
Liam | a05bd3c47e | |
Liam | 2a2c92f181 | |
Liam | f54277364c | |
t895 | de2d496e71 | |
liamwhite | 7b5bdd076d | |
Matías Locatti | e0c17a2113 | |
Liam | b107435a3f | |
liamwhite | 4e1fcd4a63 | |
liamwhite | ea4a96b45e | |
t895 | 6a90db8c19 | |
t895 | 0e5972b0b5 | |
Liam | 5f7608a7c6 | |
Liam | 668ff0db3a | |
Liam | 9f159dd62c | |
Andrew Pilley | d1eaeeed8c | |
liamwhite | 10e27a2902 | |
Charles Lombardo | f567a41f53 | |
t895 | 704c62ca01 | |
liamwhite | 8d5473e67c | |
Charles Lombardo | 3b1b98c645 | |
Charles Lombardo | daf350f5d3 | |
FearlessTobi | ef50277124 | |
FearlessTobi | ba70dc4c13 | |
FearlessTobi | 934e420e36 | |
FearlessTobi | d5e4617ab5 | |
FearlessTobi | b5a17b501b | |
FearlessTobi | 2b18957365 | |
FearlessTobi | 4c71bf3d90 | |
FearlessTobi | fdf4a5bc90 | |
FearlessTobi | b7d9eba72b | |
FearlessTobi | 380475af32 | |
FearlessTobi | a2a0be4246 | |
liamwhite | c9ef2e26ca | |
FearlessTobi | aa6532cf34 | |
FearlessTobi | 310c1f50be | |
FearlessTobi | 665fce871f | |
Narr the Reg | 58c7e846cb | |
Narr the Reg | 8b0fb98a11 | |
Narr the Reg | 8615509c40 | |
Narr the Reg | d0af52f28e | |
Liam | f46dc31683 | |
liamwhite | ef89b79d7e | |
liamwhite | 3e41f9a673 | |
Liam | d45a12826c | |
Liam | 56810541f0 | |
Liam | 911ee8fd1f | |
Charles Lombardo | 5361027ef0 | |
german77 | 56721517ea | |
Liam | 940a71422e | |
liamwhite | da225d4aa1 | |
t895 | 8d74c107f5 | |
liamwhite | 1fc86b1e3a | |
Liam | 4cdf18095d | |
Liam | 2d43831d1f | |
Liam | 2e96921f9c | |
Liam | cf0de18982 | |
Liam | ae83ee28a3 | |
Liam | 306ed4984b | |
Liam | 626f2e65b1 | |
Liam | 2eded86b4b | |
Liam | 786fc512e2 | |
Liam | c31ac45332 | |
Liam | db172ba249 | |
Liam | bb59940b03 | |
Liam | 04887953ff | |
Liam | 8ea72cc99d | |
Liam | 44d2e90217 | |
Liam | 12926eb5db | |
Liam | ae114d2fa1 | |
Liam | 270d07be2f | |
Liam | 947cdbe4b1 | |
liamwhite | 5583957616 | |
liamwhite | 6d731e1aa1 | |
liamwhite | 839ded7d59 | |
liamwhite | f57281ebc1 | |
t895 | 0a3bc6c0cf | |
t895 | 55a7815064 | |
t895 | a1c4f53c8c | |
Leystryku | 8bbb44a74e | |
Leystryku | bc5ae04ea0 | |
Leystryku | 4f387b0b74 | |
liamwhite | bdf8aca750 | |
liamwhite | acfc4d6dfb | |
t895 | 35a3c7226a | |
Leystryku | d93fdc8a6c | |
liamwhite | 5d3c7433b8 | |
t895 | 0010d42f82 | |
liamwhite | 316089c39f | |
liamwhite | 5024df1925 | |
liamwhite | e7146309de | |
t895 | c327d2a62c | |
Andrew Pilley | cb2e312f13 | |
german77 | 366bb52ec8 | |
Leystryku | 82949085c0 | |
Leystryku | 90c43aa2e7 | |
german77 | a07f0883b9 | |
Liam | 812f23d05c | |
Liam | dcce9837d2 | |
Liam | ee8eccc5fa | |
Liam | 7b79cddacd | |
Narr the Reg | 53f8383354 | |
liamwhite | 36108ce2be | |
Narr the Reg | 4cbafc1ef6 | |
Andrew Pilley | e31c926bf0 | |
Andrew Pilley | 59ede32f8e | |
Andrew Pilley | 9eba64adce | |
Narr the Reg | 110969e207 | |
t895 | 50ecad547e | |
Kelebek1 | 34fb14ec9a | |
Andrew Pilley | e2e0916100 | |
Andrew Pilley | 501e3ae05a | |
t895 | ac33847b30 | |
t895 | 18494b0ad6 | |
t895 | dc2c302a84 | |
t895 | a251f77556 | |
Narr the Reg | ec02a1cfe5 | |
Narr the Reg | 39b958ab86 | |
Narr the Reg | 9c0724b270 | |
Narr the Reg | dbcc447f43 | |
Narr the Reg | 2954c01b47 | |
liamwhite | c7588c042b | |
Narr the Reg | 6e2678a42c | |
Liam | 462ea921e3 | |
Liam | cb29aa0473 | |
Liam | af42482565 | |
Narr the Reg | caf16982d9 | |
Liam | 1842df1da5 | |
Liam | 8863940bf5 | |
Liam | b1c71f976c | |
Liam | 59011a04a1 | |
Liam | c448001d47 | |
Liam | 2e8c0e9247 | |
Liam | db871677b0 | |
liamwhite | a40adbc142 | |
Narr the Reg | 1e8554b01f | |
german77 | 75bfbadb23 | |
liamwhite | ad4ae39903 | |
liamwhite | fefdba05ca | |
t895 | f813dc78b2 | |
t895 | 10ba318807 | |
t895 | 86fc1e5b32 | |
t895 | 3c823254ff | |
t895 | a0513bc45b | |
Liam | 3067bfd126 | |
liamwhite | 95d96cfe66 | |
liamwhite | f75fceb3c0 | |
liamwhite | 3511d5552a | |
liamwhite | f27bdce70f | |
liamwhite | 85fd2bcb82 | |
Liam | 461eaca7e8 | |
t895 | 836592c447 | |
t895 | fbc1b61bff | |
Liam | bca698a17a | |
Liam | a65fb85b6d | |
Liam | bbb1ff6574 | |
Liam | 927fa532e5 | |
Liam | 87b740df46 | |
Liam | 203d213529 | |
Liam | 2e614ce08f | |
Liam | 9e271f2017 | |
Liam | 79f225bd59 | |
Liam | c7e94e2175 | |
Liam | 1c797a8048 | |
Liam | f9bba8007d | |
Liam | 2c49ebbeea | |
Liam | 17460def8e | |
Liam | 77b7e1e682 | |
Liam | eafaa5511d | |
Liam | 96fea99af9 | |
Liam | 44e7e85f23 | |
Liam | af35057212 | |
Liam | 590e86792c | |
Liam | 6fd6c65fd4 | |
Liam | c809f7193a | |
Liam | c7e97b22fb | |
Liam | b2e140b032 | |
german77 | 26e028808a | |
Narr the Reg | 2053ff96fc | |
Liam | 368bf2211f | |
Liam | de8a623932 | |
Liam | 865a0186b6 | |
Fernando Sahmkow | 9ce43ee677 | |
FearlessTobi | 7cfb51e5e7 | |
FearlessTobi | 2c357c929c | |
FearlessTobi | 482e203d5c |
|
@ -81,8 +81,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# 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
|
||||
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
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -4684,10 +4684,10 @@ Please, only use this feature to install updates and DLC.</source>
|
|||
</message>
|
||||
<message>
|
||||
<location filename="../../src/yuzu/main.cpp" line="3386"/>
|
||||
<source>You are about to force rederive all of your keys.
|
||||
If you do not know what this means or what you are doing,
|
||||
this is a potentially destructive action.
|
||||
Please make sure this is what you want
|
||||
<source>You are about to force rederive all of your keys.
|
||||
If you do not know what this means or what you are doing,
|
||||
this is a potentially destructive action.
|
||||
Please make sure this is what you want
|
||||
and optionally make backups.
|
||||
|
||||
This will delete your autogenerated key files and re-run the key derivation module.</source>
|
||||
|
@ -4731,7 +4731,7 @@ Tämä poistaa automaattisesti generoidut avaimet ja ajaa avainten laskentamoduu
|
|||
<message>
|
||||
<location filename="../../src/yuzu/main.cpp" line="3441"/>
|
||||
<source>Deriving keys...
|
||||
This may take up to a minute depending
|
||||
This may take up to a minute depending
|
||||
on your system's performance.</source>
|
||||
<translation>Johdetaan avaimia...
|
||||
Tähän voi kulua jonkin aikaa
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -121,6 +121,7 @@ else()
|
|||
-Wno-attributes
|
||||
-Wno-invalid-offsetof
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES Clang) # Clang or AppleClang
|
||||
|
|
|
@ -14,6 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<application
|
||||
android:name="org.yuzu.yuzu_emu.YuzuApplication"
|
||||
|
|
|
@ -3,24 +3,21 @@
|
|||
|
||||
package org.yuzu.yuzu_emu
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
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.FileUtil
|
||||
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.Patch
|
||||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
|
@ -30,34 +27,6 @@ import org.yuzu.yuzu_emu.model.GameVerificationResult
|
|||
* with the native side of the Yuzu code.
|
||||
*/
|
||||
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
|
||||
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
||||
|
||||
|
@ -127,112 +96,6 @@ object NativeLibrary {
|
|||
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)
|
||||
|
||||
/**
|
||||
|
@ -318,46 +181,13 @@ object NativeLibrary {
|
|||
ErrorUnknown
|
||||
}
|
||||
|
||||
private var coreErrorAlertResult = false
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
var coreErrorAlertResult = false
|
||||
val coreErrorAlertLock = Object()
|
||||
|
||||
private fun onCoreErrorImpl(title: String, message: String) {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -373,7 +203,7 @@ object NativeLibrary {
|
|||
fun onCoreError(error: CoreError?, details: String): Boolean {
|
||||
val emulationActivity = sEmulationActivity.get()
|
||||
if (emulationActivity == null) {
|
||||
error("[NativeLibrary] EmulationActivity not present")
|
||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -404,7 +234,7 @@ object NativeLibrary {
|
|||
}
|
||||
|
||||
// Show the AlertDialog on the main thread.
|
||||
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
||||
emulationActivity.runOnUiThread { onCoreErrorImpl(title, message) }
|
||||
|
||||
// Wait for the lock to notify that it is complete.
|
||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
||||
|
@ -629,46 +459,4 @@ object NativeLibrary {
|
|||
* Checks if all necessary keys are present for decryption
|
||||
*/
|
||||
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,6 +7,7 @@ import android.app.Application
|
|||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
|
@ -37,6 +38,7 @@ class YuzuApplication : Application() {
|
|||
documentsTree = DocumentsTree()
|
||||
DirectoryInitialization.start()
|
||||
GpuDriverHelper.initializeDriverParameters()
|
||||
NativeInput.reloadInputDevices()
|
||||
NativeLibrary.logDeviceInfo()
|
||||
Log.logDeviceInfo()
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
|
|||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
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.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
@ -47,7 +48,9 @@ import org.yuzu.yuzu_emu.model.Game
|
|||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
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.ParamPackage
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import java.text.NumberFormat
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -63,8 +66,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private var motionTimestamp: Long = 0
|
||||
private var flipMotionOrientation: Boolean = false
|
||||
|
||||
private var controllerIds = InputHandler.getGameControllerIds()
|
||||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||
|
@ -78,6 +79,33 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
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)
|
||||
setContentView(binding.root)
|
||||
|
||||
|
@ -95,8 +123,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
nfcReader = NfcReader(this)
|
||||
nfcReader.initialize()
|
||||
|
||||
InputHandler.initialize()
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
|
||||
|
@ -147,7 +173,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
InputHandler.updateControllerIds()
|
||||
InputHandler.updateControllerData()
|
||||
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
|
@ -172,6 +198,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
nfcReader.onNewIntent(intent)
|
||||
InputHandler.updateControllerData()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
|
@ -244,8 +271,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
}
|
||||
val deltaTimestamp = (event.timestamp - motionTimestamp) / 1000
|
||||
motionTimestamp = event.timestamp
|
||||
NativeLibrary.onGamePadMotionEvent(
|
||||
NativeLibrary.Player1Device,
|
||||
NativeInput.onDeviceMotionEvent(
|
||||
NativeInput.Player1Device,
|
||||
deltaTimestamp,
|
||||
gyro[0],
|
||||
gyro[1],
|
||||
|
@ -254,8 +281,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
accel[1],
|
||||
accel[2]
|
||||
)
|
||||
NativeLibrary.onGamePadMotionEvent(
|
||||
NativeLibrary.ConsoleDevice,
|
||||
NativeInput.onDeviceMotionEvent(
|
||||
NativeInput.ConsoleDevice,
|
||||
deltaTimestamp,
|
||||
gyro[0],
|
||||
gyro[1],
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.model.Driver
|
||||
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
|
||||
|
||||
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||
|
@ -44,25 +44,15 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
|||
}
|
||||
|
||||
// Delay marquee by 3s
|
||||
title.postDelayed(
|
||||
{
|
||||
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.marquee()
|
||||
version.marquee()
|
||||
description.marquee()
|
||||
title.text = model.title
|
||||
version.text = model.version
|
||||
description.text = model.description
|
||||
if (model.title != binding.root.context.getString(R.string.system_gpu_driver)) {
|
||||
buttonDelete.visibility = View.VISIBLE
|
||||
} else {
|
||||
buttonDelete.visibility = View.GONE
|
||||
}
|
||||
buttonDelete.setVisible(
|
||||
model.title != binding.root.context.getString(R.string.system_gpu_driver)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
|
@ -12,6 +11,7 @@ import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
|||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GameDir
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
|
||||
|
@ -29,13 +29,7 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
|
|||
override fun bind(model: GameDir) {
|
||||
binding.apply {
|
||||
path.text = Uri.parse(model.uriString).path
|
||||
path.postDelayed(
|
||||
{
|
||||
path.isSelected = true
|
||||
path.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
},
|
||||
3000
|
||||
)
|
||||
path.marquee()
|
||||
|
||||
buttonEdit.setOnClickListener {
|
||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
|
@ -27,6 +26,7 @@ import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
|||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity) :
|
||||
|
@ -44,14 +44,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
|||
|
||||
binding.textGameTitle.text = model.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||
|
||||
binding.textGameTitle.postDelayed(
|
||||
{
|
||||
binding.textGameTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.textGameTitle.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
|
||||
binding.textGameTitle.marquee()
|
||||
binding.cardGame.setOnClickListener { onClick(model) }
|
||||
binding.cardGame.setOnLongClickListener { onLongClick(model) }
|
||||
}
|
||||
|
|
|
@ -3,21 +3,18 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
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.CardSimpleOutlinedBinding
|
||||
import org.yuzu.yuzu_emu.model.GameProperty
|
||||
import org.yuzu.yuzu_emu.model.InstallableProperty
|
||||
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
|
||||
|
||||
class GamePropertiesAdapter(
|
||||
|
@ -76,23 +73,15 @@ class GamePropertiesAdapter(
|
|||
)
|
||||
)
|
||||
|
||||
binding.details.postDelayed({
|
||||
binding.details.isSelected = true
|
||||
binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
}, 3000)
|
||||
|
||||
binding.details.marquee()
|
||||
if (submenuProperty.details != null) {
|
||||
binding.details.visibility = View.VISIBLE
|
||||
binding.details.setVisible(true)
|
||||
binding.details.text = submenuProperty.details.invoke()
|
||||
} else if (submenuProperty.detailsFlow != null) {
|
||||
binding.details.visibility = View.VISIBLE
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
submenuProperty.detailsFlow.collect { binding.details.text = it }
|
||||
}
|
||||
}
|
||||
binding.details.setVisible(true)
|
||||
submenuProperty.detailsFlow.collect(viewLifecycle) { binding.details.text = it }
|
||||
} else {
|
||||
binding.details.visibility = View.GONE
|
||||
binding.details.setVisible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,14 +101,10 @@ class GamePropertiesAdapter(
|
|||
)
|
||||
)
|
||||
|
||||
if (installableProperty.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() }
|
||||
}
|
||||
if (installableProperty.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export.invoke() }
|
||||
}
|
||||
binding.buttonInstall.setVisible(installableProperty.install != null)
|
||||
binding.buttonInstall.setOnClickListener { installableProperty.install?.invoke() }
|
||||
binding.buttonExport.setVisible(installableProperty.export != null)
|
||||
binding.buttonExport.setOnClickListener { installableProperty.export?.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,22 +3,19 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
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.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
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
|
||||
|
||||
class HomeSettingAdapter(
|
||||
|
@ -59,18 +56,8 @@ class HomeSettingAdapter(
|
|||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
model.details.collect { updateOptionDetails(it) }
|
||||
}
|
||||
}
|
||||
binding.optionDetail.postDelayed(
|
||||
{
|
||||
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.optionDetail.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
model.details.collect(viewLifecycle) { updateOptionDetails(it) }
|
||||
binding.optionDetail.marquee()
|
||||
|
||||
binding.root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
|
@ -90,7 +77,7 @@ class HomeSettingAdapter(
|
|||
private fun updateOptionDetails(detailString: String) {
|
||||
if (detailString.isNotEmpty()) {
|
||||
binding.optionDetail.text = detailString
|
||||
binding.optionDetail.visibility = View.VISIBLE
|
||||
binding.optionDetail.setVisible(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import org.yuzu.yuzu_emu.databinding.CardInstallableBinding
|
||||
import org.yuzu.yuzu_emu.model.Installable
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class InstallableAdapter(installables: List<Installable>) :
|
||||
|
@ -26,14 +26,10 @@ class InstallableAdapter(installables: List<Installable>) :
|
|||
binding.title.setText(model.titleId)
|
||||
binding.description.setText(model.descriptionId)
|
||||
|
||||
if (model.install != null) {
|
||||
binding.buttonInstall.visibility = View.VISIBLE
|
||||
binding.buttonInstall.setOnClickListener { model.install.invoke() }
|
||||
}
|
||||
if (model.export != null) {
|
||||
binding.buttonExport.visibility = View.VISIBLE
|
||||
binding.buttonExport.setOnClickListener { model.export.invoke() }
|
||||
}
|
||||
binding.buttonInstall.setVisible(model.install != null)
|
||||
binding.buttonInstall.setOnClickListener { model.install?.invoke() }
|
||||
binding.buttonExport.setVisible(model.export != null)
|
||||
binding.buttonExport.setOnClickListener { model.export?.invoke() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.fragments.LicenseBottomSheetDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.License
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<License>) :
|
||||
|
@ -25,7 +25,7 @@ class LicenseAdapter(private val activity: AppCompatActivity, licenses: List<Lic
|
|||
binding.apply {
|
||||
textSettingName.text = root.context.getString(model.titleId)
|
||||
textSettingDescription.text = root.context.getString(model.descriptionId)
|
||||
textSettingValue.visibility = View.GONE
|
||||
textSettingValue.setVisible(false)
|
||||
|
||||
root.setOnClickListener { onClick(model) }
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.adapters
|
|||
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
|
@ -17,6 +16,7 @@ import org.yuzu.yuzu_emu.model.SetupCallback
|
|||
import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.model.StepState
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||
|
||||
class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
||||
|
@ -30,8 +30,8 @@ class SetupAdapter(val activity: AppCompatActivity, pages: List<SetupPage>) :
|
|||
AbstractViewHolder<SetupPage>(binding), SetupCallback {
|
||||
override fun bind(model: SetupPage) {
|
||||
if (model.stepCompleted.invoke() == StepState.COMPLETE) {
|
||||
binding.buttonAction.visibility = View.INVISIBLE
|
||||
binding.textConfirmation.visibility = View.VISIBLE
|
||||
binding.buttonAction.setVisible(visible = false, gone = false)
|
||||
binding.textConfirmation.setVisible(true)
|
||||
}
|
||||
|
||||
binding.icon.setImageDrawable(
|
||||
|
|
|
@ -0,0 +1,416 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// 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")
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
// 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,7 +24,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||
THEME_MODE("theme_mode"),
|
||||
OVERLAY_SCALE("control_scale"),
|
||||
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)
|
||||
|
||||
|
|
|
@ -4,17 +4,30 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
|
||||
object Settings {
|
||||
enum class MenuTag(val titleId: Int) {
|
||||
enum class MenuTag(val titleId: Int = 0) {
|
||||
SECTION_ROOT(R.string.advanced_settings),
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
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_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_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
|
@ -93,4 +106,15 @@ object Settings {
|
|||
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,7 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.model
|
|||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// 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,13 +3,16 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
|
||||
|
||||
class DateTimeSetting(
|
||||
private val longSetting: AbstractLongSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int
|
||||
) : SettingsItem(longSetting, titleId, descriptionId) {
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = ""
|
||||
) : SettingsItem(longSetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_DATETIME_SETTING
|
||||
|
||||
fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal)
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
class HeaderSetting(
|
||||
titleId: Int
|
||||
) : SettingsItem(emptySetting, titleId, 0) {
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = ""
|
||||
) : SettingsItem(emptySetting, titleId, titleString, 0, "") {
|
||||
override val type = TYPE_HEADER
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// 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)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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,13 +4,16 @@
|
|||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
class RunnableSetting(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val isRuntimeRunnable: Boolean,
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val isRunnable: Boolean,
|
||||
@DrawableRes val iconId: Int = 0,
|
||||
val runnable: () -> Unit
|
||||
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_RUNNABLE
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
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.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
|
@ -12,6 +16,7 @@ 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.LongSetting
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -23,13 +28,34 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||
*/
|
||||
abstract class SettingsItem(
|
||||
val setting: AbstractSetting,
|
||||
val nameId: Int,
|
||||
val descriptionId: Int
|
||||
@StringRes val titleId: Int,
|
||||
val titleString: String,
|
||||
@StringRes val descriptionId: Int,
|
||||
val descriptionString: String
|
||||
) {
|
||||
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
|
||||
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
|
||||
if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) {
|
||||
return false
|
||||
|
@ -50,6 +76,9 @@ abstract class SettingsItem(
|
|||
get() = NativeLibrary.isRunning() && !setting.global &&
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
val clearable: Boolean
|
||||
get() = !setting.global && NativeConfig.isPerGameConfigLoaded()
|
||||
|
||||
companion object {
|
||||
const val TYPE_HEADER = 0
|
||||
const val TYPE_SWITCH = 1
|
||||
|
@ -59,6 +88,10 @@ abstract class SettingsItem(
|
|||
const val TYPE_STRING_SINGLE_CHOICE = 5
|
||||
const val TYPE_DATETIME_SETTING = 6
|
||||
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"
|
||||
|
||||
|
@ -77,221 +110,246 @@ abstract class SettingsItem(
|
|||
|
||||
// List of all general
|
||||
val settingsItems = HashMap<String, SettingsItem>().apply {
|
||||
put(StringInputSetting(StringSetting.DEVICE_NAME, titleId = R.string.device_name))
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_USE_SPEED_LIMIT,
|
||||
R.string.frame_limit_enable,
|
||||
R.string.frame_limit_enable_description
|
||||
titleId = R.string.frame_limit_enable,
|
||||
descriptionId = R.string.frame_limit_enable_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
ShortSetting.RENDERER_SPEED_LIMIT,
|
||||
R.string.frame_limit_slider,
|
||||
R.string.frame_limit_slider_description,
|
||||
1,
|
||||
400,
|
||||
"%"
|
||||
titleId = R.string.frame_limit_slider,
|
||||
descriptionId = R.string.frame_limit_slider_description,
|
||||
min = 1,
|
||||
max = 400,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.CPU_BACKEND,
|
||||
R.string.cpu_backend,
|
||||
0,
|
||||
R.array.cpuBackendArm64Names,
|
||||
R.array.cpuBackendArm64Values
|
||||
titleId = R.string.cpu_backend,
|
||||
choicesId = R.array.cpuBackendArm64Names,
|
||||
valuesId = R.array.cpuBackendArm64Values
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.CPU_ACCURACY,
|
||||
R.string.cpu_accuracy,
|
||||
0,
|
||||
R.array.cpuAccuracyNames,
|
||||
R.array.cpuAccuracyValues
|
||||
titleId = R.string.cpu_accuracy,
|
||||
choicesId = R.array.cpuAccuracyNames,
|
||||
valuesId = R.array.cpuAccuracyValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.PICTURE_IN_PICTURE,
|
||||
R.string.picture_in_picture,
|
||||
R.string.picture_in_picture_description
|
||||
titleId = R.string.picture_in_picture,
|
||||
descriptionId = 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(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_DOCKED_MODE,
|
||||
R.string.use_docked_mode,
|
||||
R.string.use_docked_mode_description
|
||||
dockedModeSetting,
|
||||
titleId = R.string.use_docked_mode,
|
||||
descriptionId = R.string.use_docked_mode_description
|
||||
)
|
||||
)
|
||||
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.REGION_INDEX,
|
||||
R.string.emulated_region,
|
||||
0,
|
||||
R.array.regionNames,
|
||||
R.array.regionValues
|
||||
titleId = R.string.emulated_region,
|
||||
choicesId = R.array.regionNames,
|
||||
valuesId = R.array.regionValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.LANGUAGE_INDEX,
|
||||
R.string.emulated_language,
|
||||
0,
|
||||
R.array.languageNames,
|
||||
R.array.languageValues
|
||||
titleId = R.string.emulated_language,
|
||||
choicesId = R.array.languageNames,
|
||||
valuesId = R.array.languageValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.USE_CUSTOM_RTC,
|
||||
R.string.use_custom_rtc,
|
||||
R.string.use_custom_rtc_description
|
||||
titleId = R.string.use_custom_rtc,
|
||||
descriptionId = R.string.use_custom_rtc_description
|
||||
)
|
||||
)
|
||||
put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
|
||||
put(DateTimeSetting(LongSetting.CUSTOM_RTC, titleId = R.string.set_custom_rtc))
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ACCURACY,
|
||||
R.string.renderer_accuracy,
|
||||
0,
|
||||
R.array.rendererAccuracyNames,
|
||||
R.array.rendererAccuracyValues
|
||||
titleId = R.string.renderer_accuracy,
|
||||
choicesId = R.array.rendererAccuracyNames,
|
||||
valuesId = R.array.rendererAccuracyValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_RESOLUTION,
|
||||
R.string.renderer_resolution,
|
||||
0,
|
||||
R.array.rendererResolutionNames,
|
||||
R.array.rendererResolutionValues
|
||||
titleId = R.string.renderer_resolution,
|
||||
choicesId = R.array.rendererResolutionNames,
|
||||
valuesId = R.array.rendererResolutionValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_VSYNC,
|
||||
R.string.renderer_vsync,
|
||||
0,
|
||||
R.array.rendererVSyncNames,
|
||||
R.array.rendererVSyncValues
|
||||
titleId = R.string.renderer_vsync,
|
||||
choicesId = R.array.rendererVSyncNames,
|
||||
valuesId = R.array.rendererVSyncValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCALING_FILTER,
|
||||
R.string.renderer_scaling_filter,
|
||||
0,
|
||||
R.array.rendererScalingFilterNames,
|
||||
R.array.rendererScalingFilterValues
|
||||
titleId = R.string.renderer_scaling_filter,
|
||||
choicesId = R.array.rendererScalingFilterNames,
|
||||
valuesId = R.array.rendererScalingFilterValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.FSR_SHARPENING_SLIDER,
|
||||
titleId = R.string.fsr_sharpness,
|
||||
descriptionId = R.string.fsr_sharpness_description,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ANTI_ALIASING,
|
||||
R.string.renderer_anti_aliasing,
|
||||
0,
|
||||
R.array.rendererAntiAliasingNames,
|
||||
R.array.rendererAntiAliasingValues
|
||||
titleId = R.string.renderer_anti_aliasing,
|
||||
choicesId = R.array.rendererAntiAliasingNames,
|
||||
valuesId = R.array.rendererAntiAliasingValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||
R.string.renderer_screen_layout,
|
||||
0,
|
||||
R.array.rendererScreenLayoutNames,
|
||||
R.array.rendererScreenLayoutValues
|
||||
titleId = R.string.renderer_screen_layout,
|
||||
choicesId = R.array.rendererScreenLayoutNames,
|
||||
valuesId = R.array.rendererScreenLayoutValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ASPECT_RATIO,
|
||||
R.string.renderer_aspect_ratio,
|
||||
0,
|
||||
R.array.rendererAspectRatioNames,
|
||||
R.array.rendererAspectRatioValues
|
||||
titleId = R.string.renderer_aspect_ratio,
|
||||
choicesId = R.array.rendererAspectRatioNames,
|
||||
valuesId = R.array.rendererAspectRatioValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.VERTICAL_ALIGNMENT,
|
||||
titleId = R.string.vertical_alignment,
|
||||
descriptionId = 0,
|
||||
choicesId = R.array.verticalAlignmentEntries,
|
||||
valuesId = R.array.verticalAlignmentValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
|
||||
R.string.use_disk_shader_cache,
|
||||
R.string.use_disk_shader_cache_description
|
||||
titleId = R.string.use_disk_shader_cache,
|
||||
descriptionId = R.string.use_disk_shader_cache_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
|
||||
R.string.renderer_force_max_clock,
|
||||
R.string.renderer_force_max_clock_description
|
||||
titleId = R.string.renderer_force_max_clock,
|
||||
descriptionId = R.string.renderer_force_max_clock_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
|
||||
R.string.renderer_asynchronous_shaders,
|
||||
R.string.renderer_asynchronous_shaders_description
|
||||
titleId = R.string.renderer_asynchronous_shaders,
|
||||
descriptionId = R.string.renderer_asynchronous_shaders_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_REACTIVE_FLUSHING,
|
||||
R.string.renderer_reactive_flushing,
|
||||
R.string.renderer_reactive_flushing_description
|
||||
titleId = R.string.renderer_reactive_flushing,
|
||||
descriptionId = R.string.renderer_reactive_flushing_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.MAX_ANISOTROPY,
|
||||
R.string.anisotropic_filtering,
|
||||
R.string.anisotropic_filtering_description,
|
||||
R.array.anisoEntries,
|
||||
R.array.anisoValues
|
||||
titleId = R.string.anisotropic_filtering,
|
||||
descriptionId = R.string.anisotropic_filtering_description,
|
||||
choicesId = R.array.anisoEntries,
|
||||
valuesId = R.array.anisoValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.AUDIO_OUTPUT_ENGINE,
|
||||
R.string.audio_output_engine,
|
||||
0,
|
||||
R.array.outputEngineEntries,
|
||||
R.array.outputEngineValues
|
||||
titleId = R.string.audio_output_engine,
|
||||
choicesId = R.array.outputEngineEntries,
|
||||
valuesId = R.array.outputEngineValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
ByteSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%"
|
||||
titleId = R.string.audio_volume,
|
||||
descriptionId = R.string.audio_volume_description,
|
||||
units = "%"
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_BACKEND,
|
||||
R.string.renderer_api,
|
||||
0,
|
||||
R.array.rendererApiNames,
|
||||
R.array.rendererApiValues
|
||||
titleId = R.string.renderer_api,
|
||||
choicesId = R.array.rendererApiNames,
|
||||
valuesId = R.array.rendererApiValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_DEBUG,
|
||||
R.string.renderer_debug,
|
||||
R.string.renderer_debug_description
|
||||
titleId = R.string.renderer_debug,
|
||||
descriptionId = R.string.renderer_debug_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.CPU_DEBUG_MODE,
|
||||
R.string.cpu_debug_mode,
|
||||
R.string.cpu_debug_mode_description
|
||||
titleId = R.string.cpu_debug_mode,
|
||||
descriptionId = R.string.cpu_debug_mode_description
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -327,7 +385,7 @@ abstract class SettingsItem(
|
|||
|
||||
override fun reset() = setBoolean(defaultValue)
|
||||
}
|
||||
put(SwitchSetting(fastmem, R.string.fastmem, 0))
|
||||
put(SwitchSetting(fastmem, R.string.fastmem))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,20 @@
|
|||
|
||||
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.AbstractSetting
|
||||
|
||||
class SingleChoiceSetting(
|
||||
setting: AbstractSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Int,
|
||||
val valuesId: Int
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
@ArrayRes val choicesId: Int,
|
||||
@ArrayRes val valuesId: Int
|
||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_SINGLE_CHOICE
|
||||
|
||||
fun getSelectedValue(needsGlobal: Boolean = false) =
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
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.AbstractFloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
|
@ -12,12 +13,14 @@ import kotlin.math.roundToInt
|
|||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val min: Int,
|
||||
val max: Int,
|
||||
val units: String
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val min: Int = 0,
|
||||
val max: Int = 100,
|
||||
val units: String = ""
|
||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_SLIDER
|
||||
|
||||
fun getSelectedValue(needsGlobal: Boolean = false) =
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// 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,15 +3,18 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
class StringSingleChoiceSetting(
|
||||
private val stringSetting: AbstractStringSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
val choices: Array<String>,
|
||||
val values: Array<String>
|
||||
) : SettingsItem(stringSetting, titleId, descriptionId) {
|
||||
) : SettingsItem(stringSetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||
|
||||
fun getValueAt(index: Int): String =
|
||||
|
@ -20,7 +23,7 @@ class StringSingleChoiceSetting(
|
|||
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
|
||||
fun setSelectedValue(value: String) = stringSetting.setString(value)
|
||||
|
||||
val selectValueIndex: Int
|
||||
val selectedValueIndex: Int
|
||||
get() {
|
||||
for (i in values.indices) {
|
||||
if (values[i] == getSelectedValue()) {
|
||||
|
|
|
@ -8,10 +8,12 @@ import androidx.annotation.StringRes
|
|||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
class SubmenuSetting(
|
||||
@StringRes titleId: Int,
|
||||
@StringRes descriptionId: Int,
|
||||
@DrawableRes val iconId: Int,
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
@DrawableRes val iconId: Int = 0,
|
||||
val menuKey: Settings.MenuTag
|
||||
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||
) : SettingsItem(emptySetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_SUBMENU
|
||||
}
|
||||
|
|
|
@ -3,15 +3,18 @@
|
|||
|
||||
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.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class SwitchSetting(
|
||||
setting: AbstractSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = ""
|
||||
) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_SWITCH
|
||||
|
||||
fun getIsChecked(needsGlobal: Boolean = false): Boolean {
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
// 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// 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
|
|
@ -0,0 +1,148 @@
|
|||
// 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// 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,21 +13,16 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
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.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
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.fragments.ResetSettingsDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
@ -70,39 +65,23 @@ class SettingsActivity : AppCompatActivity() {
|
|||
)
|
||||
}
|
||||
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldRecreate.collectLatest {
|
||||
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(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldRecreate.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldRecreate(false) }
|
||||
) { if (it) recreate() }
|
||||
settingsViewModel.shouldNavigateBack.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldNavigateBack(false) }
|
||||
) { if (it) navigateBack() }
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collect(
|
||||
this,
|
||||
resetState = { settingsViewModel.setShouldShowResetSettingsDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +116,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||
super.onStop()
|
||||
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||
if (isFinishing) {
|
||||
NativeInput.reloadInputDevices()
|
||||
NativeLibrary.applySettings()
|
||||
if (args.game == null) {
|
||||
NativeConfig.saveGlobalConfig()
|
||||
|
|
|
@ -8,12 +8,11 @@ import android.icu.util.Calendar
|
|||
import android.icu.util.TimeZone
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupMenu
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
@ -21,16 +20,18 @@ import androidx.recyclerview.widget.ListAdapter
|
|||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.timepicker.MaterialTimePicker
|
||||
import com.google.android.material.timepicker.TimeFormat
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.SettingsNavigationDirections
|
||||
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.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.ui.viewholder.*
|
||||
import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
|
||||
class SettingsAdapter(
|
||||
private val fragment: Fragment,
|
||||
|
@ -41,19 +42,6 @@ class SettingsAdapter(
|
|||
private val settingsViewModel: SettingsViewModel
|
||||
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 {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return when (viewType) {
|
||||
|
@ -85,8 +73,23 @@ class SettingsAdapter(
|
|||
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 -> {
|
||||
// TODO: Create an error view since we can't return null now
|
||||
HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this)
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +129,15 @@ class SettingsAdapter(
|
|||
).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) {
|
||||
val storedTime = item.getValue() * 1000
|
||||
|
||||
|
@ -185,6 +197,214 @@ class SettingsAdapter(
|
|||
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 {
|
||||
SettingsDialogFragment.newInstance(
|
||||
settingsViewModel,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
|
@ -11,19 +11,23 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
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.slider.Slider
|
||||
import kotlinx.coroutines.launch
|
||||
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.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.SingleChoiceSetting
|
||||
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.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
|
||||
private var type = 0
|
||||
|
@ -35,6 +39,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
private val settingsViewModel: SettingsViewModel by activityViewModels()
|
||||
|
||||
private lateinit var sliderBinding: DialogSliderBinding
|
||||
private lateinit var stringInputBinding: DialogEditTextBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -50,8 +55,49 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.reset_setting_confirmation)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
settingsViewModel.clickedItem!!.setting.reset()
|
||||
settingsViewModel.setAdapterItemChanged(position)
|
||||
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.setAdapterItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
|
@ -61,7 +107,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
val item = settingsViewModel.clickedItem as SingleChoiceSetting
|
||||
val value = getSelectionForSingleChoiceValue(item)
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(item.nameId)
|
||||
.setTitle(item.title)
|
||||
.setSingleChoiceItems(item.choicesId, value, this)
|
||||
.create()
|
||||
}
|
||||
|
@ -81,18 +127,38 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
}
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(item.nameId)
|
||||
.setTitle(item.title)
|
||||
.setView(sliderBinding.root)
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||
.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 -> {
|
||||
val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(item.nameId)
|
||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
||||
.setTitle(item.title)
|
||||
.setSingleChoiceItems(item.choices, item.selectedValueIndex, 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()
|
||||
}
|
||||
|
||||
|
@ -107,6 +173,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
): View? {
|
||||
return when (type) {
|
||||
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
||||
SettingsItem.TYPE_STRING_INPUT -> stringInputBinding.root
|
||||
else -> super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
@ -115,17 +182,11 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
when (type) {
|
||||
SettingsItem.TYPE_SLIDER -> {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderTextValue.collect {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
}
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.sliderProgress.collect {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
}
|
||||
}
|
||||
settingsViewModel.sliderTextValue.collect(viewLifecycleOwner) {
|
||||
sliderBinding.textValue.text = it
|
||||
}
|
||||
settingsViewModel.sliderProgress.collect(viewLifecycleOwner) {
|
||||
sliderBinding.slider.value = it.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,10 +206,23 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||
scSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
val scSetting = settingsViewModel.clickedItem as IntSingleChoiceSetting
|
||||
val value = scSetting.getValueAt(which)
|
||||
scSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is SliderSetting -> {
|
||||
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
|
||||
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
||||
}
|
||||
|
||||
is StringInputSetting -> {
|
||||
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
||||
stringInputSetting.setSelectedValue(
|
||||
(stringInputBinding.editText.text ?: "").toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
closeDialog()
|
||||
}
|
|
@ -13,20 +13,17 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.input.NativeInput
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
private lateinit var presenter: SettingsFragmentPresenter
|
||||
|
@ -45,6 +42,12 @@ class SettingsFragment : Fragment() {
|
|||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
|
||||
val playerIndex = getPlayerIndex()
|
||||
if (playerIndex != -1) {
|
||||
NativeInput.loadInputProfiles()
|
||||
NativeInput.reloadInputDevices()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -56,9 +59,9 @@ class SettingsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||
presenter = SettingsFragmentPresenter(
|
||||
settingsViewModel,
|
||||
|
@ -71,7 +74,17 @@ class SettingsFragment : Fragment() {
|
|||
) {
|
||||
args.game!!.title
|
||||
} else {
|
||||
getString(args.menuTag.titleId)
|
||||
when (args.menuTag) {
|
||||
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 {
|
||||
adapter = settingsAdapter
|
||||
|
@ -82,16 +95,37 @@ class SettingsFragment : Fragment() {
|
|||
settingsViewModel.setShouldNavigateBack(true)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { settingsViewModel.setShouldReloadSettingsList(false) }
|
||||
) { if (it) presenter.loadSettingsList() }
|
||||
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) {
|
||||
MessageDialogFragment.newInstance(
|
||||
activity = requireActivity(),
|
||||
titleId = R.string.reset_mapping,
|
||||
descriptionId = R.string.reset_mapping_description,
|
||||
positiveAction = {
|
||||
NativeInput.resetControllerMappings(getPlayerIndex())
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
},
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +149,19 @@ class SettingsFragment : Fragment() {
|
|||
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() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
|
|
|
@ -3,11 +3,17 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
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.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.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
|
@ -15,18 +21,22 @@ 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.LongSetting
|
||||
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.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SettingsFragmentPresenter(
|
||||
private val settingsViewModel: SettingsViewModel,
|
||||
private val adapter: SettingsAdapter,
|
||||
private var menuTag: Settings.MenuTag
|
||||
private var menuTag: MenuTag
|
||||
) {
|
||||
private var settingsList = ArrayList<SettingsItem>()
|
||||
|
||||
private val context get() = YuzuApplication.appContext
|
||||
|
||||
// Extension for altering settings list based on each setting's properties
|
||||
fun ArrayList<SettingsItem>.add(key: String) {
|
||||
val item = SettingsItem.settingsItems[key]!!
|
||||
|
@ -53,73 +63,90 @@ class SettingsFragmentPresenter(
|
|||
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() {
|
||||
loadSettingsList()
|
||||
}
|
||||
|
||||
fun loadSettingsList() {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun loadSettingsList(notifyDataSetChanged: Boolean = false) {
|
||||
val sl = ArrayList<SettingsItem>()
|
||||
when (menuTag) {
|
||||
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
else -> {
|
||||
val context = YuzuApplication.appContext
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.unimplemented_menu),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return
|
||||
}
|
||||
MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
||||
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
||||
MenuTag.SECTION_INPUT_PLAYER_TWO -> addInputPlayer(sl, 1)
|
||||
MenuTag.SECTION_INPUT_PLAYER_THREE -> addInputPlayer(sl, 2)
|
||||
MenuTag.SECTION_INPUT_PLAYER_FOUR -> addInputPlayer(sl, 3)
|
||||
MenuTag.SECTION_INPUT_PLAYER_FIVE -> addInputPlayer(sl, 4)
|
||||
MenuTag.SECTION_INPUT_PLAYER_SIX -> addInputPlayer(sl, 5)
|
||||
MenuTag.SECTION_INPUT_PLAYER_SEVEN -> addInputPlayer(sl, 6)
|
||||
MenuTag.SECTION_INPUT_PLAYER_EIGHT -> addInputPlayer(sl, 7)
|
||||
MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||
MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
}
|
||||
settingsList = sl
|
||||
adapter.submitList(settingsList)
|
||||
adapter.submitList(settingsList) {
|
||||
if (notifyDataSetChanged) {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_system,
|
||||
R.string.preferences_system_description,
|
||||
R.drawable.ic_system_settings,
|
||||
Settings.MenuTag.SECTION_SYSTEM
|
||||
titleId = R.string.preferences_system,
|
||||
descriptionId = R.string.preferences_system_description,
|
||||
iconId = R.drawable.ic_system_settings,
|
||||
menuKey = MenuTag.SECTION_SYSTEM
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_graphics,
|
||||
R.string.preferences_graphics_description,
|
||||
R.drawable.ic_graphics,
|
||||
Settings.MenuTag.SECTION_RENDERER
|
||||
titleId = R.string.preferences_graphics,
|
||||
descriptionId = R.string.preferences_graphics_description,
|
||||
iconId = R.drawable.ic_graphics,
|
||||
menuKey = MenuTag.SECTION_RENDERER
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_audio,
|
||||
R.string.preferences_audio_description,
|
||||
R.drawable.ic_audio,
|
||||
Settings.MenuTag.SECTION_AUDIO
|
||||
titleId = R.string.preferences_audio,
|
||||
descriptionId = R.string.preferences_audio_description,
|
||||
iconId = R.drawable.ic_audio,
|
||||
menuKey = MenuTag.SECTION_AUDIO
|
||||
)
|
||||
)
|
||||
add(
|
||||
SubmenuSetting(
|
||||
R.string.preferences_debug,
|
||||
R.string.preferences_debug_description,
|
||||
R.drawable.ic_code,
|
||||
Settings.MenuTag.SECTION_DEBUG
|
||||
titleId = R.string.preferences_debug,
|
||||
descriptionId = R.string.preferences_debug_description,
|
||||
iconId = R.drawable.ic_code,
|
||||
menuKey = MenuTag.SECTION_DEBUG
|
||||
)
|
||||
)
|
||||
add(
|
||||
RunnableSetting(
|
||||
R.string.reset_to_default,
|
||||
R.string.reset_to_default_description,
|
||||
false,
|
||||
R.drawable.ic_restore
|
||||
titleId = R.string.reset_to_default,
|
||||
descriptionId = R.string.reset_to_default_description,
|
||||
isRunnable = !NativeLibrary.isRunning(),
|
||||
iconId = R.drawable.ic_restore
|
||||
) { settingsViewModel.setShouldShowResetSettingsDialog(true) }
|
||||
)
|
||||
}
|
||||
|
@ -127,6 +154,7 @@ class SettingsFragmentPresenter(
|
|||
|
||||
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(StringSetting.DEVICE_NAME.key)
|
||||
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
||||
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
||||
add(BooleanSetting.USE_DOCKED_MODE.key)
|
||||
|
@ -143,10 +171,12 @@ class SettingsFragmentPresenter(
|
|||
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||
add(IntSetting.RENDERER_VSYNC.key)
|
||||
add(IntSetting.RENDERER_SCALING_FILTER.key)
|
||||
add(IntSetting.FSR_SHARPENING_SLIDER.key)
|
||||
add(IntSetting.RENDERER_ANTI_ALIASING.key)
|
||||
add(IntSetting.MAX_ANISOTROPY.key)
|
||||
add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
|
||||
add(IntSetting.RENDERER_ASPECT_RATIO.key)
|
||||
add(IntSetting.VERTICAL_ALIGNMENT.key)
|
||||
add(BooleanSetting.PICTURE_IN_PICTURE.key)
|
||||
add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
|
||||
add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
|
||||
|
@ -162,6 +192,671 @@ 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>) {
|
||||
sl.apply {
|
||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||
|
@ -184,20 +879,18 @@ class SettingsFragmentPresenter(
|
|||
add(
|
||||
SingleChoiceSetting(
|
||||
theme,
|
||||
R.string.change_app_theme,
|
||||
0,
|
||||
R.array.themeEntriesA12,
|
||||
R.array.themeValuesA12
|
||||
titleId = R.string.change_app_theme,
|
||||
choicesId = R.array.themeEntriesA12,
|
||||
valuesId = R.array.themeValuesA12
|
||||
)
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
theme,
|
||||
R.string.change_app_theme,
|
||||
0,
|
||||
R.array.themeEntries,
|
||||
R.array.themeValues
|
||||
titleId = R.string.change_app_theme,
|
||||
choicesId = R.array.themeEntries,
|
||||
valuesId = R.array.themeValues
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -226,10 +919,9 @@ class SettingsFragmentPresenter(
|
|||
add(
|
||||
SingleChoiceSetting(
|
||||
themeMode,
|
||||
R.string.change_theme_mode,
|
||||
0,
|
||||
R.array.themeModeEntries,
|
||||
R.array.themeModeValues
|
||||
titleId = R.string.change_theme_mode,
|
||||
choicesId = R.array.themeModeEntries,
|
||||
valuesId = R.array.themeModeValues
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -260,8 +952,8 @@ class SettingsFragmentPresenter(
|
|||
add(
|
||||
SwitchSetting(
|
||||
blackBackgrounds,
|
||||
R.string.use_black_backgrounds,
|
||||
R.string.use_black_backgrounds_description
|
||||
titleId = R.string.use_black_backgrounds,
|
||||
descriptionId = R.string.use_black_backgrounds_description
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
|
@ -15,21 +15,17 @@ import androidx.core.view.updatePadding
|
|||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import info.debatty.java.stringsimilarity.Cosine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||
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.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class SettingsSearchFragment : Fragment() {
|
||||
private var _binding: FragmentSettingsSearchBinding? = null
|
||||
|
@ -85,14 +81,10 @@ class SettingsSearchFragment : Fragment() {
|
|||
search()
|
||||
binding.settingsList.smoothScrollToPosition(0)
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collect {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,10 +100,9 @@ class SettingsSearchFragment : Fragment() {
|
|||
|
||||
private fun search() {
|
||||
val searchTerm = binding.searchText.text.toString().lowercase()
|
||||
binding.clearButton.visibility =
|
||||
if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
|
||||
binding.clearButton.setVisible(visible = searchTerm.isNotEmpty(), gone = false)
|
||||
if (searchTerm.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
binding.noResultsView.setVisible(visible = false, gone = false)
|
||||
settingsAdapter?.submitList(emptyList())
|
||||
return
|
||||
}
|
||||
|
@ -119,7 +110,7 @@ class SettingsSearchFragment : Fragment() {
|
|||
val baseList = SettingsItem.settingsItems
|
||||
val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
|
||||
val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
|
||||
val title = getString(item.value.nameId).lowercase()
|
||||
val title = item.value.title.lowercase()
|
||||
val similarity = similarityAlgorithm.similarity(searchTerm, title)
|
||||
if (similarity > 0.08) {
|
||||
Pair(similarity, item)
|
||||
|
@ -138,8 +129,7 @@ class SettingsSearchFragment : Fragment() {
|
|||
optionalSetting
|
||||
}
|
||||
settingsAdapter?.submitList(sortedList)
|
||||
binding.noResultsView.visibility =
|
||||
if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
|
||||
binding.noResultsView.setVisible(visible = sortedList.isEmpty(), gone = false)
|
||||
}
|
||||
|
||||
private fun focusSearch() {
|
|
@ -1,20 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
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() {
|
||||
var game: Game? = null
|
||||
|
||||
var clickedItem: SettingsItem? = null
|
||||
|
||||
var currentDevice = 0
|
||||
|
||||
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
||||
private val _shouldRecreate = MutableStateFlow(false)
|
||||
|
||||
|
@ -36,6 +42,18 @@ class SettingsViewModel : ViewModel() {
|
|||
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
||||
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) {
|
||||
_shouldRecreate.value = value
|
||||
}
|
||||
|
@ -68,4 +86,27 @@ class SettingsViewModel : ViewModel() {
|
|||
fun setAdapterItemChanged(value: Int) {
|
||||
_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
|
||||
}
|
||||
}
|
|
@ -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.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -21,28 +21,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as DateTimeSetting
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingName.text = item.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingValue.setVisible(true)
|
||||
val epochTime = setting.getValue()
|
||||
val instant = Instant.ofEpochMilli(epochTime * 1000)
|
||||
val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
|
||||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setVisible(setting.clearable)
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett
|
|||
}
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
binding.textHeaderName.setText(item.nameId)
|
||||
binding.textHeaderName.text = item.title
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// 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
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
// 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 androidx.core.content.res.ResourcesCompat
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
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.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -17,34 +17,28 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as RunnableSetting
|
||||
if (item.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
item.iconId,
|
||||
setting.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
|
||||
setStyle(setting.isEditable, binding)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
if (!setting.isRuntimeRunnable && !NativeLibrary.isRunning()) {
|
||||
if (setting.isRunnable) {
|
||||
setting.runnable.invoke()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ 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.IntSingleChoiceSetting
|
||||
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.StringSingleChoiceSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -17,40 +18,36 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = item.description
|
||||
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
if (item is SingleChoiceSetting) {
|
||||
val resMgr = binding.textSettingValue.context.resources
|
||||
val values = resMgr.getIntArray(item.valuesId)
|
||||
for (i in values.indices) {
|
||||
if (values[i] == item.getSelectedValue()) {
|
||||
binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
|
||||
break
|
||||
binding.textSettingValue.setVisible(true)
|
||||
when (item) {
|
||||
is SingleChoiceSetting -> {
|
||||
val resMgr = binding.textSettingValue.context.resources
|
||||
val values = resMgr.getIntArray(item.valuesId)
|
||||
for (i in values.indices) {
|
||||
if (values[i] == item.getSelectedValue()) {
|
||||
binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i]
|
||||
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.textSettingValue.text = item.getSelectedValue()
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue())
|
||||
}
|
||||
}
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
if (binding.textSettingValue.text.isEmpty()) {
|
||||
binding.textSettingValue.setVisible(false)
|
||||
}
|
||||
|
||||
binding.buttonClear.setVisible(setting.clearable)
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
@ -63,16 +60,25 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
|||
return
|
||||
}
|
||||
|
||||
if (setting is SingleChoiceSetting) {
|
||||
adapter.onSingleChoiceClick(
|
||||
(setting as SingleChoiceSetting),
|
||||
bindingAdapterPosition
|
||||
)
|
||||
} else if (setting is StringSingleChoiceSetting) {
|
||||
adapter.onStringSingleChoiceClick(
|
||||
(setting as StringSingleChoiceSetting),
|
||||
when (setting) {
|
||||
is SingleChoiceSetting -> adapter.onSingleChoiceClick(
|
||||
setting as SingleChoiceSetting,
|
||||
bindingAdapterPosition
|
||||
)
|
||||
|
||||
is StringSingleChoiceSetting -> {
|
||||
adapter.onStringSingleChoiceClick(
|
||||
setting as StringSingleChoiceSetting,
|
||||
bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
adapter.onIntSingleChoiceClick(
|
||||
setting as IntSingleChoiceSetting,
|
||||
bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.SliderSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -17,27 +17,17 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SliderSetting
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.VISIBLE
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(item.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(true)
|
||||
binding.textSettingValue.text = String.format(
|
||||
binding.textSettingValue.context.getString(R.string.value_with_units),
|
||||
setting.getSelectedValue(),
|
||||
setting.units
|
||||
)
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setVisible(setting.clearable)
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// 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,39 +9,34 @@ 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.SubmenuSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
private lateinit var item: SubmenuSetting
|
||||
private lateinit var setting: SubmenuSetting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
this.item = item as SubmenuSetting
|
||||
if (item.iconId != 0) {
|
||||
binding.icon.visibility = View.VISIBLE
|
||||
setting = item as SubmenuSetting
|
||||
binding.icon.setVisible(setting.iconId != 0)
|
||||
if (setting.iconId != 0) {
|
||||
binding.icon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
binding.icon.resources,
|
||||
item.iconId,
|
||||
setting.iconId,
|
||||
binding.icon.context.theme
|
||||
)
|
||||
)
|
||||
} else {
|
||||
binding.icon.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingValue.visibility = View.GONE
|
||||
binding.buttonClear.visibility = View.GONE
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
binding.textSettingValue.setVisible(false)
|
||||
binding.buttonClear.setVisible(false)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
adapter.onSubmenuClick(item)
|
||||
adapter.onSubmenuClick(setting)
|
||||
}
|
||||
|
||||
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.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
@ -18,28 +18,17 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as SwitchSetting
|
||||
binding.textSettingName.setText(item.nameId)
|
||||
if (item.descriptionId != 0) {
|
||||
binding.textSettingDescription.setText(item.descriptionId)
|
||||
binding.textSettingDescription.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.textSettingDescription.text = ""
|
||||
binding.textSettingDescription.visibility = View.GONE
|
||||
}
|
||||
binding.textSettingName.text = setting.title
|
||||
binding.textSettingDescription.setVisible(setting.description.isNotEmpty())
|
||||
binding.textSettingDescription.text = setting.description
|
||||
|
||||
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
||||
adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||
adapter.onBooleanClick(setting, binding.switchWidget.isChecked, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
binding.buttonClear.visibility = if (setting.setting.global ||
|
||||
!NativeConfig.isPerGameConfigLoaded()
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
binding.buttonClear.setVisible(setting.clearable)
|
||||
binding.buttonClear.setOnClickListener {
|
||||
adapter.onClearClick(setting, bindingAdapterPosition)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -16,9 +15,6 @@ import androidx.core.view.updatePadding
|
|||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -32,6 +28,7 @@ import org.yuzu.yuzu_emu.model.HomeViewModel
|
|||
import org.yuzu.yuzu_emu.utils.AddonUtil
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
|
||||
class AddonsFragment : Fragment() {
|
||||
|
@ -60,8 +57,6 @@ class AddonsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = false)
|
||||
|
@ -78,53 +73,41 @@ class AddonsFragment : Fragment() {
|
|||
adapter = AddonAdapter(addonViewModel)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonList.collect {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
addonViewModel.addonList.collect(viewLifecycleOwner) {
|
||||
(binding.listAddons.adapter as AddonAdapter).submitList(it)
|
||||
}
|
||||
addonViewModel.showModInstallPicker.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.showModInstallPicker(false) }
|
||||
) { if (it) installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }
|
||||
addonViewModel.showModNoticeDialog.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.showModNoticeDialog(false) }
|
||||
) {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.addon_notice,
|
||||
descriptionId = R.string.addon_notice_description,
|
||||
dismissible = false,
|
||||
positiveAction = { addonViewModel.showModInstallPicker(true) },
|
||||
negativeAction = {},
|
||||
negativeButtonTitleId = R.string.close
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModInstallPicker.collect {
|
||||
if (it) {
|
||||
installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||
addonViewModel.showModInstallPicker(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.showModNoticeDialog.collect {
|
||||
if (it) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.addon_notice,
|
||||
descriptionId = R.string.addon_notice_description,
|
||||
positiveAction = { addonViewModel.showModInstallPicker(true) }
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
addonViewModel.showModNoticeDialog(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
addonViewModel.addonToDelete.collect {
|
||||
if (it != null) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.confirm_uninstall,
|
||||
descriptionId = R.string.confirm_uninstall_description,
|
||||
positiveAction = { addonViewModel.onDeleteAddon(it) }
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
addonViewModel.setAddonToDelete(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addonViewModel.addonToDelete.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { addonViewModel.setAddonToDelete(null) }
|
||||
) {
|
||||
if (it != null) {
|
||||
MessageDialogFragment.newInstance(
|
||||
requireActivity(),
|
||||
titleId = R.string.confirm_uninstall,
|
||||
descriptionId = R.string.confirm_uninstall_description,
|
||||
positiveAction = { addonViewModel.onDeleteAddon(it) },
|
||||
negativeAction = {}
|
||||
).show(parentFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// 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,7 +3,6 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -14,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
|
@ -35,6 +31,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
|||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -63,8 +60,6 @@ class DriverManagerFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
|
@ -89,15 +84,8 @@ class DriverManagerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
driverViewModel.showClearButton.collect {
|
||||
binding.toolbarDrivers.menu
|
||||
.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
}
|
||||
}
|
||||
}
|
||||
driverViewModel.showClearButton.collect(viewLifecycleOwner) {
|
||||
binding.toolbarDrivers.menu.findItem(R.id.menu_driver_use_global).isVisible = it
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,14 +10,11 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.DialogFragment
|
||||
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 kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class DriversLoadingDialogFragment : DialogFragment() {
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
|
@ -44,13 +41,7 @@ class DriversLoadingDialogFragment : DialogFragment() {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect { if (it) dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) { if (it) dismiss() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -15,7 +15,9 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Rational
|
||||
import android.view.*
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
|
@ -24,14 +26,12 @@ import androidx.core.content.res.ResourcesCompat
|
|||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.window.layout.FoldingFeature
|
||||
|
@ -39,9 +39,6 @@ import androidx.window.layout.WindowInfoTracker
|
|||
import androidx.window.layout.WindowLayoutInfo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
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.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
@ -52,6 +49,7 @@ 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.Settings
|
||||
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.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
|
@ -59,6 +57,7 @@ import org.yuzu.yuzu_emu.model.EmulationViewModel
|
|||
import org.yuzu.yuzu_emu.overlay.model.OverlayControl
|
||||
import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import java.lang.NullPointerException
|
||||
|
||||
class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
|
@ -86,14 +85,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(context)
|
||||
.windowLayoutInfo(context)
|
||||
.collect { updateFoldableLayout(context, it) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
|
@ -164,8 +155,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (requireActivity().isFinishing) {
|
||||
|
@ -273,6 +262,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
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 -> {
|
||||
showOverlayOptions()
|
||||
true
|
||||
|
@ -337,129 +335,86 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
binding.loadingTitle.isSelected = true
|
||||
binding.loadingText.isSelected = true
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
}
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity()).collect(viewLifecycleOwner) {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderProgress.collectLatest {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
emulationViewModel.shaderProgress.collect(viewLifecycleOwner) {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
}
|
||||
}
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
}
|
||||
}
|
||||
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.totalShaders.collectLatest {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.totalShaders.collect(viewLifecycleOwner) {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
emulationViewModel.shaderMessage.collect(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderMessage.collectLatest {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
driverViewModel.isInteractionAllowed.collect {
|
||||
if (it) {
|
||||
startEmulation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStarted.collectLatest {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
}
|
||||
|
||||
emulationState.updateSurface()
|
||||
emulationViewModel.emulationStarted.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(IntSetting.LOCK_DRAWER.getInt())
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
// Setup overlays
|
||||
updateShowFpsOverlay()
|
||||
updateThermalOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationState.updateSurface()
|
||||
|
||||
// Setup overlays
|
||||
updateShowFpsOverlay()
|
||||
updateThermalOverlay()
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.isEmulationStopping.collectLatest {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.isEmulationStopping.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.drawerOpen.collect {
|
||||
if (it) {
|
||||
binding.drawerLayout.open()
|
||||
binding.inGameMenu.requestFocus()
|
||||
} else {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.drawerOpen.collect(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.drawerLayout.open()
|
||||
binding.inGameMenu.requestFocus()
|
||||
} else {
|
||||
binding.drawerLayout.close()
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.programChanged.collect {
|
||||
if (it != 0) {
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
binding.drawerLayout.close()
|
||||
binding.drawerLayout
|
||||
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.programChanged.collect(viewLifecycleOwner) {
|
||||
if (it != 0) {
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
binding.drawerLayout.close()
|
||||
binding.drawerLayout
|
||||
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStopped.collect {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.emulationStopped.collect(viewLifecycleOwner) {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
|
||||
driverViewModel.isInteractionAllowed.collect(viewLifecycleOwner) {
|
||||
if (it) startEmulation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startEmulation(programIndex: Int = 0) {
|
||||
|
@ -487,14 +442,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
binding.drawerLayout.close()
|
||||
}
|
||||
if (showInputOverlay) {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
binding.surfaceInputOverlay.setVisible(visible = false, gone = false)
|
||||
}
|
||||
} else {
|
||||
if (showInputOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||
}
|
||||
binding.surfaceInputOverlay.setVisible(
|
||||
showInputOverlay && emulationViewModel.emulationStarted.value
|
||||
)
|
||||
if (!isInFoldableLayout) {
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
|
||||
|
@ -531,7 +484,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
private fun updateShowFpsOverlay() {
|
||||
if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
|
||||
val showOverlay = BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
|
||||
binding.showFpsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
val SYSTEM_FPS = 0
|
||||
val FPS = 1
|
||||
val FRAMETIME = 2
|
||||
|
@ -551,17 +506,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
||||
binding.showFpsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
binding.showFpsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateThermalOverlay() {
|
||||
if (BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()) {
|
||||
val showOverlay = BooleanSetting.SHOW_THERMAL_OVERLAY.getBoolean()
|
||||
binding.showThermalsText.setVisible(showOverlay)
|
||||
if (showOverlay) {
|
||||
thermalStatsUpdater = {
|
||||
if (emulationViewModel.emulationStarted.value &&
|
||||
!emulationViewModel.isEmulationStopping.value
|
||||
|
@ -583,12 +538,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
thermalStatsUpdateHandler.post(thermalStatsUpdater!!)
|
||||
binding.showThermalsText.visibility = View.VISIBLE
|
||||
} else {
|
||||
if (thermalStatsUpdater != null) {
|
||||
thermalStatsUpdateHandler.removeCallbacks(thermalStatsUpdater!!)
|
||||
}
|
||||
binding.showThermalsText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,7 +570,46 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
private fun updateScreenLayout() {
|
||||
binding.surfaceEmulation.setAspectRatio(null)
|
||||
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.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()
|
||||
updateOrientation()
|
||||
}
|
||||
|
@ -818,12 +810,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
}
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.doneControlConfig.setVisible(true)
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
}
|
||||
|
||||
private fun stopConfiguringControls() {
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
binding.doneControlConfig.setVisible(false)
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
// Unlock the orientation if it was locked for editing
|
||||
if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == EmulationOrientation.Unspecified.int) {
|
||||
|
|
|
@ -13,9 +13,6 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
|
@ -27,6 +24,7 @@ import org.yuzu.yuzu_emu.model.GamesViewModel
|
|||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
import org.yuzu.yuzu_emu.utils.collect
|
||||
|
||||
class GameFoldersFragment : Fragment() {
|
||||
private var _binding: FragmentFoldersBinding? = null
|
||||
|
@ -70,12 +68,8 @@ class GameFoldersFragment : Fragment() {
|
|||
adapter = FolderAdapter(requireActivity(), gamesViewModel)
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.folders.collect {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
}
|
||||
}
|
||||
gamesViewModel.folders.collect(viewLifecycleOwner) {
|
||||
(binding.listFolders.adapter as FolderAdapter).submitList(it)
|
||||
}
|
||||
|
||||
val mainActivity = requireActivity() as MainActivity
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding
|
|||
import org.yuzu.yuzu_emu.model.GameVerificationResult
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.GameMetadata
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
|
||||
import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins
|
||||
|
||||
class GameInfoFragment : Fragment() {
|
||||
|
@ -85,7 +86,7 @@ class GameInfoFragment : Fragment() {
|
|||
copyToClipboard(getString(R.string.developer), args.game.developer)
|
||||
}
|
||||
} else {
|
||||
developer.visibility = View.GONE
|
||||
developer.setVisible(false)
|
||||
}
|
||||
|
||||
version.setHint(R.string.version)
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -18,9 +16,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
|
@ -46,7 +42,9 @@ import org.yuzu.yuzu_emu.utils.FileUtil
|
|||
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
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.collect
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
|
||||
|
@ -76,8 +74,6 @@ class GamePropertiesFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
homeViewModel.setNavigationVisibility(visible = false, animated = true)
|
||||
|
@ -107,13 +103,7 @@ class GamePropertiesFragment : Fragment() {
|
|||
|
||||
GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen)
|
||||
binding.title.text = args.game.title
|
||||
binding.title.postDelayed(
|
||||
{
|
||||
binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
binding.title.isSelected = true
|
||||
},
|
||||
3000
|
||||
)
|
||||
binding.title.marquee()
|
||||
|
||||
binding.buttonStart.setOnClickListener {
|
||||
LaunchGameDialogFragment.newInstance(args.game)
|
||||
|
@ -122,28 +112,14 @@ class GamePropertiesFragment : Fragment() {
|
|||
|
||||
reloadList()
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.openImportSaves.collect {
|
||||
if (it) {
|
||||
importSaves.launch(arrayOf("application/zip"))
|
||||
homeViewModel.setOpenImportSaves(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
homeViewModel.reloadPropertiesList.collect {
|
||||
if (it) {
|
||||
reloadList()
|
||||
homeViewModel.reloadPropertiesList(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
homeViewModel.openImportSaves.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.setOpenImportSaves(false) }
|
||||
) { if (it) importSaves.launch(arrayOf("application/zip")) }
|
||||
homeViewModel.reloadPropertiesList.collect(
|
||||
viewLifecycleOwner,
|
||||
resetState = { homeViewModel.reloadPropertiesList(false) }
|
||||
) { if (it) reloadList() }
|
||||
|
||||
setInsets()
|
||||
}
|
||||
|
@ -243,7 +219,9 @@ class GamePropertiesFragment : Fragment() {
|
|||
requireActivity(),
|
||||
titleId = R.string.delete_save_data,
|
||||
descriptionId = R.string.delete_save_data_warning_description,
|
||||
positiveAction = {
|
||||
positiveButtonTitleId = android.R.string.cancel,
|
||||
negativeButtonTitleId = android.R.string.ok,
|
||||
negativeAction = {
|
||||
File(args.game.saveDir).deleteRecursively()
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue