android: Apply automatic kotlin formatting fixes

This commit is contained in:
OpenSauce04 2026-06-14 21:20:04 +01:00 committed by OpenSauce
parent a2a20fcc65
commit 2940069fe2
94 changed files with 1901 additions and 1223 deletions

View file

@ -80,7 +80,7 @@ android {
"-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
"-DENABLE_GDBSTUB=OFF" // Disable GDB stub
)
}
}
@ -147,7 +147,7 @@ android {
}
lint {
checkReleaseBuilds = false // Ditto
// The name of this property is misleading, this doesn't actually disable linting for the `release` build.
// ^- The name of this property is misleading, this doesn't actually disable linting for the `release` build.
}
}
@ -215,7 +215,9 @@ dependencies {
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
val downloadVulkanValidationLayers = tasks.register<Download>("downloadVulkanValidationLayers") {
src("https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip")
src(
"https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download/vulkan-sdk-1.4.313.0/android-binaries-1.4.313.0.zip"
)
dest(file("${layout.buildDirectory.get().asFile.path}/tmp/Vulkan-ValidationLayers.zip"))
onlyIfModified(true)
}
@ -266,7 +268,7 @@ fun getGitHash(): String =
fun getBranch(): String =
runGitCommand(ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")) ?: "dummy-branch"
fun runGitCommand(command: ProcessBuilder) : String? {
fun runGitCommand(command: ProcessBuilder): String? {
try {
command.directory(project.rootDir)
val process = command.start()
@ -292,7 +294,7 @@ android.applicationVariants.configureEach {
val variant = this
val capitalizedName = variant.name.capitalizeUS()
val copyTask = tasks.register("copyBundle${capitalizedName}") {
val copyTask = tasks.register("copyBundle$capitalizedName") {
doLast {
project.copy {
from(variant.outputs.first().outputFile.parentFile)
@ -306,5 +308,5 @@ android.applicationVariants.configureEach {
}
}
}
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }
tasks.named("bundle$capitalizedName").configure { finalizedBy(copyTask) }
}

View file

@ -13,9 +13,9 @@ import android.os.Build
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil
import org.citra.citra_emu.utils.PermissionsHandler
class CitraApplication : Application() {
private fun createNotificationChannel() {

View file

@ -25,6 +25,8 @@ import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
import java.util.Date
import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
@ -32,8 +34,6 @@ import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference
import java.util.Date
/**
* Class which contains methods that interact
@ -135,10 +135,7 @@ object NativeLibrary {
*/
external fun setUserDirectory(directory: String)
data class InstalledGame(
val path: String,
val mediaType: Game.MediaType
)
data class InstalledGame(val path: String, val mediaType: Game.MediaType)
fun getInstalledGamePaths(): Array<InstalledGame> {
val games = getInstalledGamePathsImpl()
@ -253,9 +250,8 @@ object NativeLibrary {
external fun playTimeManagerGetCurrentTitleId(): Long
private external fun uninstallTitle(titleId: Long, mediaType: Int): Boolean
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean {
return uninstallTitle(titleId, mediaType.value)
}
fun uninstallTitle(titleId: Long, mediaType: Game.MediaType): Boolean =
uninstallTitle(titleId, mediaType.value)
external fun nativeFileExists(path: String): Boolean
@ -352,7 +348,7 @@ object NativeLibrary {
@get:JvmStatic
val isPortraitMode: Boolean
get() = CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
Configuration.ORIENTATION_PORTRAIT
@Keep
@JvmStatic
@ -501,16 +497,20 @@ object NativeLibrary {
else -> {
title = getString(R.string.loader_error_generic_title)
message = getString(R.string.loader_error_generic,
getString(coreError.stringRes), coreError.value)
message = getString(
R.string.loader_error_generic,
getString(coreError.stringRes),
coreError.value
)
}
}
val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(
Html.fromHtml(message,
Html.FROM_HTML_MODE_LEGACY
Html.fromHtml(
message,
Html.FROM_HTML_MODE_LEGACY
)
)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
@ -641,7 +641,7 @@ object NativeLibrary {
fun loadStateIfAvailable(slot: Int): Boolean {
var available = false
getSavestateInfo()?.forEach {
if (it.slot == slot){
if (it.slot == slot) {
available = true
return@forEach
}
@ -679,19 +679,17 @@ object NativeLibrary {
// Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
fun compressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
compressFileNative(inputPath, outputPath)
)
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus =
CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath)
)
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@ -725,21 +723,19 @@ object NativeLibrary {
@Keep
@JvmStatic
fun openContentUri(path: String, openMode: String): Int =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.openContentUri(path, openMode)
} else {
FileUtil.openContentUri(path, openMode)
}
fun openContentUri(path: String, openMode: String): Int = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.openContentUri(path, openMode)
} else {
FileUtil.openContentUri(path, openMode)
}
@Keep
@JvmStatic
fun getFilesName(path: String): Array<String?> =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFilesName(path)
} else {
FileUtil.getFilesName(path)
}
fun getFilesName(path: String): Array<String?> = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFilesName(path)
} else {
FileUtil.getFilesName(path)
}
@Keep
@JvmStatic
@ -765,10 +761,14 @@ object NativeLibrary {
return primaryStoragePath + dirSep + virtualPath
} else { // User directory probably located on a removable storage device
val storageIdString = pathSegment.substringBefore(":")
val removablePath = RemovableStorageHelper.getRemovableStoragePath(CitraApplication.appContext, storageIdString)
val removablePath = RemovableStorageHelper.getRemovableStoragePath(
CitraApplication.appContext,
storageIdString
)
if (removablePath == null) {
android.util.Log.e("NativeLibrary",
android.util.Log.e(
"NativeLibrary",
"Unknown mount location for storage device '$storageIdString' (URI: $uri)"
)
return ""
@ -788,12 +788,11 @@ object NativeLibrary {
@Keep
@JvmStatic
fun getSize(path: String): Long =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFileSize(path)
} else {
FileUtil.getFileSize(path)
}
fun getSize(path: String): Long = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.getFileSize(path)
} else {
FileUtil.getFileSize(path)
}
@Keep
@JvmStatic
@ -801,21 +800,19 @@ object NativeLibrary {
@Keep
@JvmStatic
fun fileExists(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path)
} else {
FileUtil.exists(path)
}
fun fileExists(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.exists(path)
} else {
FileUtil.exists(path)
}
@Keep
@JvmStatic
fun isDirectory(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path)
} else {
FileUtil.isDirectory(path)
}
fun isDirectory(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.isDirectory(path)
} else {
FileUtil.isDirectory(path)
}
@Keep
@JvmStatic
@ -823,19 +820,18 @@ object NativeLibrary {
sourcePath: String,
destinationParentPath: String,
destinationFilename: String
): Boolean =
if (FileUtil.isNativePath(sourcePath) &&
FileUtil.isNativePath(destinationParentPath)
) {
CitraApplication.documentsTree
.copyFile(sourcePath, destinationParentPath, destinationFilename)
} else {
FileUtil.copyFile(
Uri.parse(sourcePath),
Uri.parse(destinationParentPath),
destinationFilename
)
}
): Boolean = if (FileUtil.isNativePath(sourcePath) &&
FileUtil.isNativePath(destinationParentPath)
) {
CitraApplication.documentsTree
.copyFile(sourcePath, destinationParentPath, destinationFilename)
} else {
FileUtil.copyFile(
Uri.parse(sourcePath),
Uri.parse(destinationParentPath),
destinationFilename
)
}
@Keep
@JvmStatic
@ -870,12 +866,11 @@ object NativeLibrary {
@Keep
@JvmStatic
fun deleteDocument(path: String): Boolean =
if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path)
} else {
FileUtil.deleteDocument(path)
}
fun deleteDocument(path: String): Boolean = if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.deleteDocument(path)
} else {
FileUtil.deleteDocument(path)
}
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
Success(0, R.string.core_error_success),
@ -898,9 +893,7 @@ object NativeLibrary {
ErrorUnknown(17, R.string.core_error_unknown);
companion object {
fun fromInt(value: Int): CoreError {
return entries.find { it.value == value } ?: ErrorUnknown
}
fun fromInt(value: Int): CoreError = entries.find { it.value == value } ?: ErrorUnknown
}
}
@ -952,7 +945,11 @@ object NativeLibrary {
const val MESSAGE = "message"
const val CAN_CONTINUE = "canContinue"
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
fun newInstance(
title: String,
message: String,
canContinue: Boolean
): CoreErrorDialogFragment {
val frag = CoreErrorDialogFragment()
val args = Bundle()
args.putString(TITLE, title)

View file

@ -46,9 +46,9 @@ import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.ControllerMappingHelper
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil
@ -334,11 +334,13 @@ class EmulationActivity : AppCompatActivity() {
}
return hotkeyUtility.handleKeyPress(event)
}
KeyEvent.ACTION_UP -> {
return hotkeyUtility.handleKeyRelease(event)
}
else -> {
return false;
return false
}
}
}
@ -358,7 +360,8 @@ class EmulationActivity : AppCompatActivity() {
// TODO: Move this check into native code - prevents crash if input pressed before starting emulation
if (!NativeLibrary.isRunning() ||
(event.source and InputDevice.SOURCE_CLASS_JOYSTICK == 0) ||
emulationFragment.isDrawerOpen()) {
emulationFragment.isDrawerOpen()
) {
return super.dispatchGenericMotionEvent(event)
}
@ -387,7 +390,10 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation =
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
val inverted = preferences.getBoolean(
InputBindingSetting.getInputAxisInvertedKey(axis),
false
)
if (nextMapping == -1 || guestOrientation == -1) {
// Axis is unmapped
continue
@ -396,7 +402,7 @@ class EmulationActivity : AppCompatActivity() {
// Skip joystick wobble
value = 0f
}
if (inverted) value = -value;
if (inverted) value = -value
when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> {
@ -573,7 +579,9 @@ class EmulationActivity : AppCompatActivity() {
registerForActivityResult(OpenFileResultContract()) { result: Intent? ->
if (result == null) return@registerForActivityResult
val selectedFiles = FileBrowserHelper.getSelectedFiles(
result, applicationContext, listOf<String>("bin")
result,
applicationContext,
listOf<String>("bin")
) ?: return@registerForActivityResult
if (BuildUtil.isGooglePlayBuild) {
onAmiiboSelected(selectedFiles[0])
@ -596,8 +604,6 @@ class EmulationActivity : AppCompatActivity() {
companion object {
private var instance: EmulationActivity? = null
fun isRunning(): Boolean {
return instance?.isEmulationRunning ?: false
}
fun isRunning(): Boolean = instance?.isEmulationRunning ?: false
}
}

View file

@ -15,9 +15,9 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.CardDriverOptionBinding
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.GpuDriverMetadata
import org.citra.citra_emu.viewmodel.DriverViewModel
import org.citra.citra_emu.utils.GpuDriverHelper
class DriverAdapter(private val driverViewModel: DriverViewModel) :
ListAdapter<Pair<Uri, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
@ -105,15 +105,11 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) :
override fun areItemsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean {
return oldItem.first == newItem.first
}
): Boolean = oldItem.first == newItem.first
override fun areContentsTheSame(
oldItem: Pair<Uri, GpuDriverMetadata>,
newItem: Pair<Uri, GpuDriverMetadata>
): Boolean {
return oldItem.second == newItem.second
}
): Boolean = oldItem.second == newItem.second
}
}

View file

@ -4,24 +4,25 @@
package org.citra.citra_emu.adapters
import android.graphics.drawable.Icon
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.SystemClock
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.content.Context
import android.content.SharedPreferences
import android.widget.TextView
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
import android.widget.Toast
import android.graphics.drawable.BitmapDrawable
import android.graphics.Bitmap
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.BitmapFactory
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
@ -29,24 +30,23 @@ import androidx.core.graphics.scale
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import android.widget.PopupMenu
import androidx.lifecycle.lifecycleScope
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineScope
import org.citra.citra_emu.HomeNavigationDirections
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.HomeNavigationDirections
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter.GameViewHolder
@ -65,10 +65,12 @@ class GameAdapter(
private val activity: AppCompatActivity,
private val inflater: LayoutInflater,
private val openImageLauncher: ActivityResultLauncher<String>?,
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener, View.OnLongClickListener {
private val onRequestCompressOrDecompress: (
(inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit
)? = null
) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener,
View.OnLongClickListener {
private var lastClickTime = 0L
private var imagePath: String? = null
private var dialogShortcutBinding: DialogShortcutBinding? = null
@ -212,15 +214,18 @@ class GameAdapter(
binding.textGameTitle.text = game.title
binding.textCompany.text = game.company
binding.textGameRegion.text = game.regions
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
View.GONE
} else {
View.VISIBLE
}
binding.imageCartridge.visibility =
if (preferences.getString("insertedCartridge", "") != game.path) {
View.GONE
} else {
View.VISIBLE
}
val backgroundColorId =
if (
isValidGame(game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase())
isValidGame(
game.filename.substring(game.filename.lastIndexOf(".") + 1).lowercase()
)
) {
R.attr.colorSurface
} else {
@ -263,13 +268,41 @@ class GameAdapter(
val basePath = "sdmc/Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000"
return GameDirectories(
gameDir = game.path.substringBeforeLast("/"),
saveDir = basePath + "/title/${String.format("%016x", game.titleId).lowercase().substring(0, 8)}/${String.format("%016x", game.titleId).lowercase().substring(8)}/data/00000001",
saveDir =
basePath +
"/title/${String.format(
"%016x",
game.titleId
).lowercase().substring(
0,
8
)}/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/data/00000001",
modsDir = "load/mods/${String.format("%016X", game.titleId)}",
texturesDir = "load/textures/${String.format("%016X", game.titleId)}",
appDir = game.path.substringBeforeLast("/").split("/").filter { it.isNotEmpty() }.joinToString("/"),
dlcDir = basePath + "/title/0004008c/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
updatesDir = basePath + "/title/0004000e/${String.format("%016x", game.titleId).lowercase().substring(8)}/content",
extraDir = basePath + "/extdata/00000000/${String.format("%016X", game.titleId).substring(8, 14).padStart(8, '0')}"
appDir = game.path.substringBeforeLast("/").split("/").filter {
it.isNotEmpty()
}.joinToString("/"),
dlcDir =
basePath +
"/title/0004008c/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
updatesDir =
basePath +
"/title/0004000e/${String.format(
"%016x",
game.titleId
).lowercase().substring(8)}/content",
extraDir =
basePath +
"/extdata/00000000/${String.format(
"%016X",
game.titleId
).substring(8, 14).padStart(8, '0')}"
)
}
@ -299,13 +332,36 @@ class GameAdapter(
.setType("*/*")
val uri = when (menuItem.itemId) {
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(dirs.appDir)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(dirs.saveDir)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(dirs.updatesDir)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(dirs.dlcDir)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(dirs.extraDir)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(dirs.texturesDir, true)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(dirs.modsDir, true)
R.id.game_context_open_app -> CitraApplication.documentsTree.folderUriHelper(
dirs.appDir
)
R.id.game_context_open_save_dir -> CitraApplication.documentsTree.folderUriHelper(
dirs.saveDir
)
R.id.game_context_open_updates -> CitraApplication.documentsTree.folderUriHelper(
dirs.updatesDir
)
R.id.game_context_open_dlc -> CitraApplication.documentsTree.folderUriHelper(
dirs.dlcDir
)
R.id.game_context_open_extra -> CitraApplication.documentsTree.folderUriHelper(
dirs.extraDir
)
R.id.game_context_open_textures -> CitraApplication.documentsTree.folderUriHelper(
dirs.texturesDir,
true
)
R.id.game_context_open_mods -> CitraApplication.documentsTree.folderUriHelper(
dirs.modsDir,
true
)
else -> null
}
@ -319,7 +375,11 @@ class GameAdapter(
popup.show()
}
private fun showUninstallContextMenu(view: View, game: Game, bottomSheetDialog: BottomSheetDialog) {
private fun showUninstallContextMenu(
view: View,
game: Game,
bottomSheetDialog: BottomSheetDialog
) {
val dirs = getGameDirectories(game)
val popup = PopupMenu(view.context, view).apply {
menuInflater.inflate(R.menu.game_context_menu_uninstall, menu)
@ -342,16 +402,38 @@ class GameAdapter(
popup.setOnMenuItemClickListener { menuItem ->
val uninstallAction: () -> Unit = {
when (menuItem.itemId) {
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(titleId, game.mediaType)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(dlcTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(updateTitleId, Game.MediaType.SDMC)
R.id.game_context_uninstall -> NativeLibrary.uninstallTitle(
titleId,
game.mediaType
)
R.id.game_context_uninstall_dlc -> NativeLibrary.uninstallTitle(
dlcTitleId,
Game.MediaType.SDMC
)
R.id.game_context_uninstall_updates -> NativeLibrary.uninstallTitle(
updateTitleId,
Game.MediaType.SDMC
)
}
ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
bottomSheetDialog.dismiss()
}
if (menuItem.itemId in listOf(R.id.game_context_uninstall, R.id.game_context_uninstall_dlc, R.id.game_context_uninstall_updates)) {
IndeterminateProgressDialogFragment.newInstance(activity, R.string.uninstalling, false, uninstallAction)
if (menuItem.itemId in
listOf(
R.id.game_context_uninstall,
R.id.game_context_uninstall_dlc,
R.id.game_context_uninstall_updates
)
) {
IndeterminateProgressDialogFragment.newInstance(
activity,
R.string.uninstalling,
false,
uninstallAction
)
.show(activity.supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
true
} else {
@ -362,7 +444,12 @@ class GameAdapter(
popup.show()
}
private fun showAboutGameDialog(context: Context, game: Game, holder: GameViewHolder, view: View) {
private fun showAboutGameDialog(
context: Context,
game: Game,
holder: GameViewHolder,
view: View
) {
val bottomSheetView = inflater.inflate(R.layout.dialog_about_game, null)
val bottomSheetDialog = BottomSheetDialog(context)
@ -374,12 +461,22 @@ class GameAdapter(
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text =
context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text =
context.getString(R.string.game_context_file) + " " + game.filename
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text =
context.getString(R.string.game_context_type) + " " + game.fileType
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
val insertButton = bottomSheetView.findViewById<MaterialButton>(
R.id.insert_cartridge_button
)
insertButton.text =
if (inserted) {
context.getString(R.string.game_context_eject)
} else {
context.getString(R.string.game_context_insert)
}
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
insertButton.setOnClickListener {
if (inserted) {
@ -420,7 +517,7 @@ class GameAdapter(
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
// Default to false for zoomed in shortcut icons
preferences.edit() {
preferences.edit {
putBoolean(
"shouldStretchIcon",
false
@ -452,7 +549,11 @@ class GameAdapter(
.setPositiveButton(android.R.string.ok) { _, _ ->
val shortcutName = dialogShortcutBinding!!.shortcutNameInput.text.toString()
if (shortcutName.isEmpty()) {
Toast.makeText(context, R.string.shortcut_name_empty, Toast.LENGTH_LONG).show()
Toast.makeText(
context,
R.string.shortcut_name_empty,
Toast.LENGTH_LONG
).show()
return@setPositiveButton
}
val iconBitmap = (dialogShortcutBinding!!.shortcutIcon.drawable as BitmapDrawable).bitmap
@ -463,9 +564,11 @@ class GameAdapter(
val shortcut = ShortcutInfo.Builder(context, shortcutName)
.setShortLabel(shortcutName)
.setIcon(icon)
.setIntent(game.launchIntent.apply {
putExtra("launchedFromShortcut", true)
})
.setIntent(
game.launchIntent.apply {
putExtra("launchedFromShortcut", true)
}
)
.build()
shortcutManager?.requestPinShortcut(shortcut, null)
@ -486,7 +589,9 @@ class GameAdapter(
bottomSheetDialog.dismiss()
}
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(
R.id.compress_decompress
)
if (game.isInstalled) {
compressDecompressButton.setOnClickListener {
Toast.makeText(
@ -499,31 +604,42 @@ class GameAdapter(
} else {
compressDecompressButton.setOnClickListener {
val shouldCompress = !game.isCompressed
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
val recommendedExt = NativeLibrary.getRecommendedExtension(
holder.game.path,
shouldCompress
)
val baseName = holder.game.filename.substringBeforeLast('.')
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
onRequestCompressOrDecompress?.invoke(
holder.game.path,
"$baseName.$recommendedExt",
shouldCompress
)
bottomSheetDialog.dismiss()
}
}
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
compressDecompressButton.text =
context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game)
}
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_uninstall).setOnClickListener {
bottomSheetView.findViewById<MaterialButton>(
R.id.menu_button_uninstall
).setOnClickListener {
showUninstallContextMenu(it, game, bottomSheetDialog)
}
bottomSheetView.findViewById<MaterialButton>(R.id.delete_cache).setOnClickListener {
val options = arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
val options =
arrayOf(context.getString(R.string.vulkan), context.getString(R.string.opengles))
var selectedIndex = -1
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_cache_select_backend)
.setSingleChoiceItems(options, -1) { dialog, which ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) {_, _ ->
.setPositiveButton(android.R.string.ok) { _, _ ->
val progToast = Toast.makeText(
CitraApplication.appContext,
R.string.deleting_shader_cache,
@ -532,11 +648,11 @@ class GameAdapter(
progToast.show()
activity.lifecycleScope.launch(Dispatchers.IO) {
when (selectedIndex) {
0 -> {
NativeLibrary.deleteVulkanShaderCache(game.titleId)
}
1 -> {
NativeLibrary.deleteOpenGLShaderCache(game.titleId)
}
@ -620,10 +736,8 @@ class GameAdapter(
}
}
private fun isValidGame(extension: String): Boolean {
return Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }
}
private fun isValidGame(extension: String): Boolean = Game.badExtensions.stream()
.noneMatch { extension == it.lowercase() }
private class DiffCallback : DiffUtil.ItemCallback<Game>() {
override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
@ -632,8 +746,6 @@ class GameAdapter(
return oldItem.titleId == newItem.titleId && oldItem.title == newItem.title
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
}
}

View file

@ -29,7 +29,8 @@ class HomeSettingAdapter(
private val activity: AppCompatActivity,
private val viewLifecycle: LifecycleOwner,
var options: List<HomeSetting>
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(), View.OnClickListener {
) : RecyclerView.Adapter<HomeSettingAdapter.HomeOptionViewHolder>(),
View.OnClickListener {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeOptionViewHolder {
val binding =
CardHomeOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -37,9 +38,7 @@ class HomeSettingAdapter(
return HomeOptionViewHolder(binding)
}
override fun getItemCount(): Int {
return options.size
}
override fun getItemCount(): Int = options.size
override fun onBindViewHolder(holder: HomeOptionViewHolder, position: Int) {
holder.bind(options[position])

View file

@ -14,12 +14,12 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.PageSetupBinding
import org.citra.citra_emu.model.ButtonState
import org.citra.citra_emu.model.PageState
import org.citra.citra_emu.model.SetupCallback
import org.citra.citra_emu.model.SetupPage
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
@ -35,7 +35,8 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root), SetupCallback {
RecyclerView.ViewHolder(binding.root),
SetupCallback {
lateinit var page: SetupPage
init {
@ -49,7 +50,9 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
onStepCompleted(0, pageFullyCompleted = true)
}
if (page.pageButtons != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) {
if (page.pageButtons != null &&
page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE
) {
for (pageButton in page.pageButtons) {
val pageButtonView = LayoutInflater.from(activity)
.inflate(
@ -108,9 +111,17 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
.alpha(0.38f)
.setDuration(200)
.start()
button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
button.setTextColor(
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
button.iconTint =
ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled))
ColorStateList.valueOf(
button.context.getColor(
com.google.android.material.R.color.material_on_surface_disabled
)
)
}
}
}

View file

@ -5,9 +5,9 @@
package org.citra.citra_emu.applets
import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.fragments.MiiSelectorDialogFragment
import java.io.Serializable
@Keep
object MiiSelector {
@ -43,5 +43,5 @@ object MiiSelector {
lateinit var miiNames: Array<String>
}
class MiiSelectorData (var returnCode: Long, var index: Int)
class MiiSelectorData(var returnCode: Long, var index: Int)
}

View file

@ -7,13 +7,13 @@ package org.citra.citra_emu.applets
import android.text.InputFilter
import android.text.Spanned
import androidx.annotation.Keep
import java.io.Serializable
import org.citra.citra_emu.CitraApplication.Companion.appContext
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.fragments.KeyboardDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.utils.Log
import java.io.Serializable
@Keep
object SoftwareKeyboard {
@ -81,17 +81,17 @@ object SoftwareKeyboard {
private external fun ValidateFilters(text: String): ValidationError
external fun ValidateInput(text: String): ValidationError
/// Corresponds to Frontend::ButtonConfig
// / Corresponds to Frontend::ButtonConfig
interface ButtonConfig {
companion object {
const val Single = 0 /// Ok button
const val Dual = 1 /// Cancel | Ok buttons
const val Triple = 2 /// Cancel | I Forgot | Ok buttons
const val None = 3 /// No button (returned by swkbdInputText in special cases)
const val Single = 0 // / Ok button
const val Dual = 1 // / Cancel | Ok buttons
const val Triple = 2 // / Cancel | I Forgot | Ok buttons
const val None = 3 // / No button (returned by swkbdInputText in special cases)
}
}
/// Corresponds to Frontend::ValidationError
// / Corresponds to Frontend::ValidationError
enum class ValidationError {
None,
@ -128,7 +128,7 @@ object SoftwareKeyboard {
lateinit var buttonText: Array<String>
}
/// Corresponds to Frontend::KeyboardData
// / Corresponds to Frontend::KeyboardData
class KeyboardData(var button: Int, var text: String)
class Filter : InputFilter {
override fun filter(

View file

@ -9,11 +9,10 @@ import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
class OpenFileResultContract : ActivityResultContract<Boolean?, Intent?>() {
override fun createIntent(context: Context, input: Boolean?): Intent {
return Intent(Intent.ACTION_OPEN_DOCUMENT)
override fun createIntent(context: Context, input: Boolean?): Intent =
Intent(Intent.ACTION_OPEN_DOCUMENT)
.setType("application/octet-stream")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Intent? = intent
}

View file

@ -4,15 +4,15 @@
package org.citra.citra_emu.display
import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.app.Activity
import android.view.WindowManager
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.EmulationMenuSettings
@ -20,7 +20,7 @@ import org.citra.citra_emu.utils.EmulationMenuSettings
class ScreenAdjustmentUtil(
private val context: Context,
private val windowManager: WindowManager,
private val settings: Settings,
private val settings: Settings
) {
fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens
@ -34,14 +34,15 @@ class ScreenAdjustmentUtil(
}
fun cycleLayouts() {
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list;
val landscapeLayoutsToCycle = IntListSetting.LAYOUTS_TO_CYCLE.list
val landscapeValues =
if (landscapeLayoutsToCycle.isNotEmpty())
if (landscapeLayoutsToCycle.isNotEmpty()) {
landscapeLayoutsToCycle.toIntArray()
else context.resources.getIntArray(
R.array.landscapeValues
)
} else {
context.resources.getIntArray(
R.array.landscapeValues
)
}
val portraitValues = context.resources.getIntArray(R.array.portraitValues)
if (NativeLibrary.isPortraitMode) {
@ -73,7 +74,7 @@ class ScreenAdjustmentUtil(
fun changeSecondaryOrientation(layoutOption: Int) {
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT,SettingsFile.FILE_NAME_CONFIG)
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
@ -102,6 +103,5 @@ class ScreenAdjustmentUtil(
settings.saveSetting(BooleanSetting.UPRIGHT_SCREEN, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
}

View file

@ -13,11 +13,8 @@ enum class ScreenLayout(val int: Int) {
HYBRID_SCREEN(4),
CUSTOM_LAYOUT(5);
companion object {
fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
fun from(int: Int): ScreenLayout = entries.firstOrNull { it.int == int } ?: LARGE_SCREEN
}
}
@ -32,9 +29,7 @@ enum class SmallScreenPosition(val int: Int) {
BELOW(7);
companion object {
fun from(int: Int): SmallScreenPosition {
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
fun from(int: Int): SmallScreenPosition = entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
}
@ -45,9 +40,8 @@ enum class PortraitScreenLayout(val int: Int) {
ORIGINAL(2);
companion object {
fun from(int: Int): PortraitScreenLayout {
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
fun from(int: Int): PortraitScreenLayout =
entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
}
@ -66,9 +60,7 @@ enum class SecondaryDisplayLayout(val int: Int) {
;
companion object {
fun from(int: Int): SecondaryDisplayLayout {
return entries.firstOrNull { it.int == int } ?: NONE
}
fun from(int: Int): SecondaryDisplayLayout = entries.firstOrNull { it.int == int } ?: NONE
}
}
@ -81,26 +73,22 @@ enum class StereoWhichDisplay(val int: Int) {
SECONDARY_ONLY(3);
companion object {
fun from(int: Int): StereoWhichDisplay {
return entries.firstOrNull { it.int == int } ?: NONE
}
fun from(int: Int): StereoWhichDisplay = entries.firstOrNull { it.int == int } ?: NONE
}
}
enum class StereoMode(val int: Int) {
// These must match what is defined in src/common/settings.h
// These must match what is defined in src/common/settings.h
OFF(0),
SIDE_BY_SIDE(1),
SIDE_BY_SIDE_FULL(2),
ANAGLYPH(3),
INTERLACED(4),
REVERSE_INTERLACED (5),
CARDBOARD_VR (6);
REVERSE_INTERLACED(5),
CARDBOARD_VR(6);
companion object {
fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
fun from(int: Int): StereoMode = entries.firstOrNull { it.int == int } ?: OFF
}
}
}

View file

@ -15,9 +15,9 @@ import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.WindowManager
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.utils.Log
class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
@ -55,26 +55,27 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
NativeLibrary.secondarySurfaceDestroyed()
}
private fun getSecondaryDisplays(): List<Display> {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display.displayId
} else {
@Suppress("DEPRECATION")
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.displayId
}
private fun getSecondaryDisplays(): List<Display> {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display.displayId
} else {
@Suppress("DEPRECATION")
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.displayId
}
val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = (it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
val isNotDefaultOrPresentable =
(it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
isNotDefaultOrPresentable &&
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
}
}
@ -88,23 +89,26 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
// Theoretically, the NONE option is no longer selectable, but
// I am leaving this in for backwards compatibility
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
! BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
) {
currentDisplayId = -1
vd.display
} else if (preferredDisplayId >=0 && availableDisplays.any { it.displayId == preferredDisplayId }) {
} else if (preferredDisplayId >= 0 &&
availableDisplays.any { it.displayId == preferredDisplayId }
) {
currentDisplayId = preferredDisplayId
availableDisplays.first { it.displayId == preferredDisplayId }
} else {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val default = dm.displays.first {it.displayId == Display.DEFAULT_DISPLAY}
val default = dm.displays.first { it.displayId == Display.DEFAULT_DISPLAY }
// prioritize displays that have a different name from the default display, as
// some devices such as the Odin 2 create a permanent virtual display with the same
// name as the default display that should be skipped in most cases
currentDisplayId = availableDisplays.firstOrNull{
it.name != default.name && !it.name.contains("Built",true)}?.displayId ?:
availableDisplays[0].displayId
availableDisplays.first{ it.displayId == currentDisplayId }
currentDisplayId = availableDisplays.firstOrNull {
it.name != default.name && !it.name.contains("Built", true)
}?.displayId
?: availableDisplays[0].displayId
availableDisplays.first { it.displayId == currentDisplayId }
}
// if our presentation is already on the right display, ignore
@ -151,7 +155,9 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
}
}
class SecondaryDisplayPresentation(
context: Context, display: Display, val parent: SecondaryDisplay
context: Context,
display: Display,
val parent: SecondaryDisplay
) : Presentation(context, display) {
private lateinit var surfaceView: SurfaceView
private var touchscreenPointerId = -1
@ -173,9 +179,12 @@ class SecondaryDisplayPresentation(
}
override fun surfaceChanged(
holder: SurfaceHolder, format: Int, width: Int, height: Int
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
Log.debug("SecondaryDisplay Surface changed: ${width}x${height}")
Log.debug("SecondaryDisplay Surface changed: ${width}x$height")
parent.updateSurface()
}
@ -225,7 +234,5 @@ class SecondaryDisplayPresentation(
}
// Publicly accessible method to get the SurfaceHolder
fun getSurfaceHolder(): SurfaceHolder {
return surfaceView.holder
}
fun getSurfaceHolder(): SurfaceHolder = surfaceView.holder
}

View file

@ -53,7 +53,7 @@ class CheatsViewModel : ViewModel() {
private var selectedCheatPosition = -1
fun initialize(titleId_: Long) {
titleId = titleId_;
titleId = titleId_
load()
}

View file

@ -170,24 +170,23 @@ class CheatDetailsFragment : Fragment() {
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View?, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View?, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarCheatDetails.layoutParams = mlpAppBar
val mlpAppBar = binding.toolbarCheatDetails.layoutParams as ViewGroup.MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarCheatDetails.layoutParams = mlpAppBar
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
binding.scrollView.updatePadding(left = leftInsets, right = rightInsets)
binding.buttonContainer.updatePadding(left = leftInsets, right = rightInsets)
windowInsets
}
windowInsets
}
}

View file

@ -128,7 +128,7 @@ class CheatListFragment : Fragment() {
left = leftInsets,
right = rightInsets,
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
resources.getDimensionPixelSize(R.dimen.spacing_fab_list)
)
val mlpFab = binding.fab.layoutParams as MarginLayoutParams

View file

@ -41,7 +41,8 @@ class CheatsAdapter(
}
inner class CheatViewHolder(private val binding: ListItemCheatBinding) :
RecyclerView.ViewHolder(binding.root), View.OnClickListener,
RecyclerView.ViewHolder(binding.root),
View.OnClickListener,
CompoundButton.OnCheckedChangeListener {
private lateinit var viewModel: CheatsViewModel
private lateinit var cheat: Cheat

View file

@ -32,7 +32,9 @@ import org.citra.citra_emu.ui.TwoPaneOnBackPressedCallback
import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.viewmodel.HomeViewModel
class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
class CheatsFragment :
Fragment(),
SlidingPaneLayout.PanelSlideListener {
private var cheatListLastFocus: View? = null
private var cheatDetailsLastFocus: View? = null
@ -238,7 +240,8 @@ class CheatsFragment : Fragment(), SlidingPaneLayout.PanelSlideListener {
binding.cheatDetailsContainer.layoutParams = mlpDetails
return insets
}
})
}
)
}
}
}

View file

@ -12,5 +12,5 @@ enum class Hotkey(val button: Int) {
QUICKSAVE(10005),
QUICKLOAD(10006),
TURBO_LIMIT(10007),
ENABLE(10008);
ENABLE(10008)
}

View file

@ -11,11 +11,11 @@ import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.Settings
class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
@ -41,7 +41,7 @@ class HotkeyUtility(
// Now process all internal buttons associated with this keypress
for (button in buttonSet) {
currentlyPressedButtons.add(button)
//option 1 - this is the enable command, which was already handled
// option 1 - this is the enable command, which was already handled
if (button == Hotkey.ENABLE.button) {
handled = true
}
@ -74,7 +74,8 @@ class HotkeyUtility(
val thisKeyIsHotkey =
!thisKeyIsEnableButton && Hotkey.entries.any { buttonSet.contains(it.button) }
if (thisKeyIsEnableButton) {
handled = true; hotkeyIsEnabled = false
handled = true
hotkeyIsEnabled = false
}
for (button in buttonSet) {
@ -109,10 +110,15 @@ class HotkeyUtility(
fun handleHotkey(bindedButton: Int): Boolean {
when (bindedButton) {
Hotkey.SWAP_SCREEN.button -> screenAdjustmentUtil.swapScreen()
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_LIMIT.button -> TurboHelper.toggleTurbo(true)
Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(

View file

@ -142,4 +142,4 @@ object SettingKeys {
external fun screen_orientation(): String
external fun performance_overlay_position(): String
external fun enable_secondary_display(): String
}
}

View file

@ -20,19 +20,63 @@ enum class BooleanSetting(
SWAP_SCREEN(SettingKeys.swap_screen(), Settings.SECTION_LAYOUT, false),
INSTANT_DEBUG_LOG(SettingKeys.instant_debug_log(), Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER(SettingKeys.enable_rpc_server(), Settings.SECTION_DEBUG, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(SettingKeys.toggle_unique_data_console_type(), Settings.SECTION_DEBUG, false),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(),Settings.SECTION_RENDERER, false),
TOGGLE_UNIQUE_DATA_CONSOLE_TYPE(
SettingKeys.toggle_unique_data_console_type(),
Settings.SECTION_DEBUG,
false
),
SWAP_EYES_3D(SettingKeys.swap_eyes_3d(), Settings.SECTION_RENDERER, false),
PERF_OVERLAY_ENABLE(SettingKeys.performance_overlay_enable(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS(SettingKeys.performance_overlay_show_fps(), Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_SHOW_FRAMETIME(SettingKeys.performance_overlay_show_frame_time(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_SPEED(SettingKeys.performance_overlay_show_speed(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(SettingKeys.performance_overlay_show_app_ram_usage(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(SettingKeys.performance_overlay_show_available_ram(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_BATTERY_TEMP(SettingKeys.performance_overlay_show_battery_temp(), Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_BACKGROUND(SettingKeys.performance_overlay_background(), Settings.SECTION_LAYOUT, false),
DELAY_START_LLE_MODULES(SettingKeys.delay_start_for_lle_modules(), Settings.SECTION_DEBUG, true),
DETERMINISTIC_ASYNC_OPERATIONS(SettingKeys.deterministic_async_operations(), Settings.SECTION_DEBUG, false),
REQUIRED_ONLINE_LLE_MODULES(SettingKeys.enable_required_online_lle_modules(), Settings.SECTION_SYSTEM, false),
PERF_OVERLAY_SHOW_FPS(
SettingKeys.performance_overlay_show_fps(),
Settings.SECTION_LAYOUT,
true
),
PERF_OVERLAY_SHOW_FRAMETIME(
SettingKeys.performance_overlay_show_frame_time(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_SPEED(
SettingKeys.performance_overlay_show_speed(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_APP_RAM_USAGE(
SettingKeys.performance_overlay_show_app_ram_usage(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_AVAILABLE_RAM(
SettingKeys.performance_overlay_show_available_ram(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_SHOW_BATTERY_TEMP(
SettingKeys.performance_overlay_show_battery_temp(),
Settings.SECTION_LAYOUT,
false
),
PERF_OVERLAY_BACKGROUND(
SettingKeys.performance_overlay_background(),
Settings.SECTION_LAYOUT,
false
),
DELAY_START_LLE_MODULES(
SettingKeys.delay_start_for_lle_modules(),
Settings.SECTION_DEBUG,
true
),
DETERMINISTIC_ASYNC_OPERATIONS(
SettingKeys.deterministic_async_operations(),
Settings.SECTION_DEBUG,
false
),
REQUIRED_ONLINE_LLE_MODULES(
SettingKeys.enable_required_online_lle_modules(),
Settings.SECTION_SYSTEM,
false
),
LLE_APPLETS(SettingKeys.lle_applets(), Settings.SECTION_SYSTEM, false),
NEW_3DS(SettingKeys.is_new_3ds(), Settings.SECTION_SYSTEM, true),
LINEAR_FILTERING(SettingKeys.filter_mode(), Settings.SECTION_RENDERER, true),
@ -44,23 +88,43 @@ enum class BooleanSetting(
PRELOAD_TEXTURES(SettingKeys.preload_textures(), Settings.SECTION_UTILITY, false),
ENABLE_AUDIO_STRETCHING(SettingKeys.enable_audio_stretching(), Settings.SECTION_AUDIO, true),
ENABLE_REALTIME_AUDIO(SettingKeys.enable_realtime_audio(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(SettingKeys.simulate_headphones_plugged(), Settings.SECTION_AUDIO, false),
SIMULATE_HEADPHONES_PLUGGED(
SettingKeys.simulate_headphones_plugged(),
Settings.SECTION_AUDIO,
false
),
CPU_JIT(SettingKeys.use_cpu_jit(), Settings.SECTION_CORE, true),
HW_SHADER(SettingKeys.use_hw_shader(), Settings.SECTION_RENDERER, true),
SHADER_JIT(SettingKeys.use_shader_jit(), Settings.SECTION_RENDERER, true),
VSYNC(SettingKeys.use_vsync(), Settings.SECTION_RENDERER, false),
USE_FRAME_LIMIT(SettingKeys.use_frame_limit(), Settings.SECTION_RENDERER, true),
DEBUG_RENDERER(SettingKeys.renderer_debug(), Settings.SECTION_DEBUG, false),
DISABLE_RIGHT_EYE_RENDER(SettingKeys.disable_right_eye_render(), Settings.SECTION_RENDERER, false),
USE_ARTIC_BASE_CONTROLLER(SettingKeys.use_artic_base_controller(), Settings.SECTION_CONTROLS, false),
DISABLE_RIGHT_EYE_RENDER(
SettingKeys.disable_right_eye_render(),
Settings.SECTION_RENDERER,
false
),
USE_ARTIC_BASE_CONTROLLER(
SettingKeys.use_artic_base_controller(),
Settings.SECTION_CONTROLS,
false
),
UPRIGHT_SCREEN(SettingKeys.upright_screen(), Settings.SECTION_LAYOUT, false),
COMPRESS_INSTALLED_CIA_CONTENT(SettingKeys.compress_cia_installs(), Settings.SECTION_STORAGE, false),
COMPRESS_INSTALLED_CIA_CONTENT(
SettingKeys.compress_cia_installs(),
Settings.SECTION_STORAGE,
false
),
ASYNC_FS_OPERATIONS(SettingKeys.async_fs_operations(), Settings.SECTION_STORAGE, true),
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
ENABLE_SECONDARY_DISPLAY(SettingKeys.enable_secondary_display(), Settings.SECTION_LAYOUT, true),
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
SIMULATE_3DS_GPU_TIMINGS(
SettingKeys.simulate_3ds_gpu_timings(),
Settings.SECTION_RENDERER,
true
);
override var boolean: Boolean = defaultValue
@ -80,7 +144,7 @@ enum class BooleanSetting(
companion object {
private val NOT_RUNTIME_EDITABLE = listOf(
PLUGIN_LOADER,
ALLOW_PLUGIN_LOADER,
ALLOW_PLUGIN_LOADER,
ASYNC_SHADERS,
DELAY_START_LLE_MODULES,
DETERMINISTIC_ASYNC_OPERATIONS,

View file

@ -11,8 +11,12 @@ enum class FloatSetting(
override val section: String,
override val defaultValue: Float
) : AbstractFloatSetting {
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(),Settings.SECTION_LAYOUT,2.25f),
SECOND_SCREEN_OPACITY(SettingKeys.custom_second_layer_opacity(), Settings.SECTION_RENDERER, 100f),
LARGE_SCREEN_PROPORTION(SettingKeys.large_screen_proportion(), Settings.SECTION_LAYOUT, 2.25f),
SECOND_SCREEN_OPACITY(
SettingKeys.custom_second_layer_opacity(),
Settings.SECTION_RENDERER,
100f
),
BACKGROUND_RED(SettingKeys.bg_red(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_BLUE(SettingKeys.bg_blue(), Settings.SECTION_RENDERER, 0f),
BACKGROUND_GREEN(SettingKeys.bg_green(), Settings.SECTION_RENDERER, 0f);

View file

@ -13,10 +13,15 @@ enum class IntListSetting(
val canBeEmpty: Boolean = true
) : AbstractListSetting<Int> {
LAYOUTS_TO_CYCLE(SettingKeys.layouts_to_cycle(), Settings.SECTION_LAYOUT, listOf(0, 1, 2, 3, 4, 5), canBeEmpty = false);
LAYOUTS_TO_CYCLE(
SettingKeys.layouts_to_cycle(),
Settings.SECTION_LAYOUT,
listOf(0, 1, 2, 3, 4, 5),
canBeEmpty = false
);
private var backingList: List<Int> = defaultValue
private var lastValidList : List<Int> = defaultValue
private var lastValidList: List<Int> = defaultValue
override var list: List<Int>
get() = backingList
@ -32,7 +37,6 @@ enum class IntListSetting(
override val valueAsString: String
get() = list.joinToString()
override val isRuntimeEditable: Boolean
get() {
for (setting in NOT_RUNTIME_EDITABLE) {
@ -46,8 +50,7 @@ enum class IntListSetting(
companion object {
private val NOT_RUNTIME_EDITABLE: List<IntListSetting> = emptyList()
fun from(key: String): IntListSetting? =
values().firstOrNull { it.key == key }
fun from(key: String): IntListSetting? = values().firstOrNull { it.key == key }
fun clear() = values().forEach { it.list = it.defaultValue }
}

View file

@ -26,26 +26,30 @@ enum class IntSetting(
CARDBOARD_X_SHIFT(SettingKeys.cardboard_x_shift(), Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT(SettingKeys.cardboard_y_shift(), Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT(SettingKeys.layout_option(), Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(),Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(),Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(),Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(),Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(),Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,4),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(),Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(),Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(),Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT(SettingKeys.custom_portrait_bottom_height(),Settings.SECTION_LAYOUT,480),
SMALL_SCREEN_POSITION(SettingKeys.small_screen_position(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_X(SettingKeys.custom_top_x(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_Y(SettingKeys.custom_top_y(), Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_WIDTH(SettingKeys.custom_top_width(), Settings.SECTION_LAYOUT, 800),
LANDSCAPE_TOP_HEIGHT(SettingKeys.custom_top_height(), Settings.SECTION_LAYOUT, 480),
LANDSCAPE_BOTTOM_X(SettingKeys.custom_bottom_x(), Settings.SECTION_LAYOUT, 80),
LANDSCAPE_BOTTOM_Y(SettingKeys.custom_bottom_y(), Settings.SECTION_LAYOUT, 480),
LANDSCAPE_BOTTOM_WIDTH(SettingKeys.custom_bottom_width(), Settings.SECTION_LAYOUT, 640),
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(), Settings.SECTION_LAYOUT, 480),
SCREEN_GAP(SettingKeys.screen_gap(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(), Settings.SECTION_LAYOUT, 0),
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(), Settings.SECTION_LAYOUT, 4),
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(), Settings.SECTION_LAYOUT, 0),
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(), Settings.SECTION_LAYOUT, 800),
PORTRAIT_TOP_HEIGHT(SettingKeys.custom_portrait_top_height(), Settings.SECTION_LAYOUT, 480),
PORTRAIT_BOTTOM_X(SettingKeys.custom_portrait_bottom_x(), Settings.SECTION_LAYOUT, 80),
PORTRAIT_BOTTOM_Y(SettingKeys.custom_portrait_bottom_y(), Settings.SECTION_LAYOUT, 480),
PORTRAIT_BOTTOM_WIDTH(SettingKeys.custom_portrait_bottom_width(), Settings.SECTION_LAYOUT, 640),
PORTRAIT_BOTTOM_HEIGHT(
SettingKeys.custom_portrait_bottom_height(),
Settings.SECTION_LAYOUT,
480
),
AUDIO_INPUT_TYPE(SettingKeys.input_type(), Settings.SECTION_AUDIO, 0),
CPU_CLOCK_SPEED(SettingKeys.cpu_clock_percentage(), Settings.SECTION_CORE, 100),
TEXTURE_FILTER(SettingKeys.texture_filter(), Settings.SECTION_RENDERER, 0),
@ -54,8 +58,12 @@ enum class IntSetting(
DELAY_RENDER_THREAD_US(SettingKeys.delay_game_render_thread_us(), Settings.SECTION_RENDERER, 0),
ORIENTATION_OPTION(SettingKeys.screen_orientation(), Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT(SettingKeys.turbo_limit(), Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION(SettingKeys.performance_overlay_position(), Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(),Settings.SECTION_RENDERER,0),
PERFORMANCE_OVERLAY_POSITION(
SettingKeys.performance_overlay_position(),
Settings.SECTION_LAYOUT,
0
),
RENDER_3D_WHICH_DISPLAY(SettingKeys.render_3d_which_display(), Settings.SECTION_RENDERER, 0),
ASPECT_RATIO(SettingKeys.aspect_ratio(), Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue
@ -78,7 +86,7 @@ enum class IntSetting(
EMULATED_REGION,
INIT_CLOCK,
GRAPHICS_API,
AUDIO_INPUT_TYPE,
AUDIO_INPUT_TYPE
)
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View file

@ -26,9 +26,7 @@ class SettingSection(val name: String) {
* @param key Used to retrieve the Setting.
* @return A Setting object (you should probably cast this before using)
*/
fun getSetting(key: String): AbstractSetting? {
return settings[key]
}
fun getSetting(key: String): AbstractSetting? = settings[key]
fun mergeSection(settingSection: SettingSection) {
for (setting in settingSection.settings.values) {

View file

@ -5,11 +5,11 @@
package org.citra.citra_emu.features.settings.model
import android.text.TextUtils
import java.util.TreeMap
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.ui.SettingsActivityView
import org.citra.citra_emu.features.settings.utils.SettingsFile
import java.util.TreeMap
class Settings {
private var gameId: String? = null
@ -33,9 +33,7 @@ class Settings {
var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
fun getSection(sectionName: String): SettingSection? {
return sections[sectionName]
}
fun getSection(sectionName: String): SettingSection? = sections[sectionName]
val isEmpty: Boolean
get() = sections.isEmpty()
@ -182,7 +180,7 @@ class Settings {
KEY_BUTTON_RIGHT
)
val axisTitles = listOf(
R.string.controller_axis_vertical,
R.string.controller_axis_vertical,
R.string.controller_axis_horizontal
)
val dPadTitles = listOf(
@ -250,4 +248,4 @@ class Settings {
)
}
}
}
}

View file

@ -15,9 +15,17 @@ enum class StringSetting(
CAMERA_INNER_NAME(SettingKeys.camera_inner_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_INNER_CONFIG(SettingKeys.camera_inner_config(), Settings.SECTION_CAMERA, "_front"),
CAMERA_OUTER_LEFT_NAME(SettingKeys.camera_outer_left_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_LEFT_CONFIG(SettingKeys.camera_outer_left_config(), Settings.SECTION_CAMERA, "_back"),
CAMERA_OUTER_LEFT_CONFIG(
SettingKeys.camera_outer_left_config(),
Settings.SECTION_CAMERA,
"_back"
),
CAMERA_OUTER_RIGHT_NAME(SettingKeys.camera_outer_right_name(), Settings.SECTION_CAMERA, "ndk"),
CAMERA_OUTER_RIGHT_CONFIG(SettingKeys.camera_outer_right_config(), Settings.SECTION_CAMERA, "_back");
CAMERA_OUTER_RIGHT_CONFIG(
SettingKeys.camera_outer_right_config(),
Settings.SECTION_CAMERA,
"_back"
);
override var string: String = defaultValue

View file

@ -4,6 +4,6 @@
package org.citra.citra_emu.features.settings.model.view
class HeaderSetting(titleId: Int,descId: Int = 0) : SettingsItem(null, titleId, descId) {
class HeaderSetting(titleId: Int, descId: Int = 0) : SettingsItem(null, titleId, descId) {
override val type = TYPE_HEADER
}

View file

@ -20,10 +20,8 @@ import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.Settings
class InputBindingSetting(
val abstractSetting: AbstractSetting,
titleId: Int
) : SettingsItem(abstractSetting, titleId, 0) {
class InputBindingSetting(val abstractSetting: AbstractSetting, titleId: Int) :
SettingsItem(abstractSetting, titleId, 0) {
private val context: Context get() = CitraApplication.appContext
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(context)
@ -39,74 +37,66 @@ class InputBindingSetting(
/**
* Returns true if this key is for the 3DS Circle Pad
*/
fun isCirclePad(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
fun isCirclePad(): Boolean = when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL -> true
else -> false
}
else -> false
}
/**
* Returns true if this key is for a horizontal axis for a 3DS analog stick or D-pad
*/
fun isHorizontalOrientation(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
fun isHorizontalOrientation(): Boolean = when (abstractSetting.key) {
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_HORIZONTAL -> true
else -> false
}
else -> false
}
/**
* Returns true if this key is for the 3DS C-Stick
*/
fun isCStick(): Boolean =
when (abstractSetting.key) {
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
fun isCStick(): Boolean = when (abstractSetting.key) {
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
Settings.KEY_CSTICK_AXIS_VERTICAL -> true
else -> false
}
else -> false
}
/**
* Returns true if this key is for the 3DS D-Pad
*/
fun isDPad(): Boolean =
when (abstractSetting.key) {
Settings.KEY_DPAD_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_VERTICAL -> true
fun isDPad(): Boolean = when (abstractSetting.key) {
Settings.KEY_DPAD_AXIS_HORIZONTAL,
Settings.KEY_DPAD_AXIS_VERTICAL -> true
else -> false
}
else -> false
}
/**
* Returns true if this key is for the 3DS L/R or ZL/ZR buttons. Note, these are not real
* triggers on the 3DS, but we support them as such on a physical gamepad.
*/
fun isTrigger(): Boolean =
when (abstractSetting.key) {
Settings.KEY_BUTTON_L,
Settings.KEY_BUTTON_R,
Settings.KEY_BUTTON_ZL,
Settings.KEY_BUTTON_ZR -> true
fun isTrigger(): Boolean = when (abstractSetting.key) {
Settings.KEY_BUTTON_L,
Settings.KEY_BUTTON_R,
Settings.KEY_BUTTON_ZL,
Settings.KEY_BUTTON_ZR -> true
else -> false
}
else -> false
}
/**
* Returns true if a gamepad axis can be used to map this key.
*/
fun isAxisMappingSupported(): Boolean {
return isCirclePad() || isCStick() || isDPad() || isTrigger()
}
fun isAxisMappingSupported(): Boolean = isCirclePad() || isCStick() || isDPad() || isTrigger()
/**
* Returns true if a gamepad button can be used to map this key.
*/
fun isButtonMappingSupported(): Boolean {
return !isAxisMappingSupported() || isTrigger()
}
fun isButtonMappingSupported(): Boolean = !isAxisMappingSupported() || isTrigger()
/**
* Returns the Citra button code for the settings key.
@ -177,10 +167,10 @@ class InputBindingSetting(
} catch (e: ClassCastException) {
// if this is an int pref, either old button or an axis, so just remove it
preferences.edit().remove(oldKey).apply()
return;
return
}
buttonCodes.remove(buttonCode.toString());
preferences.edit().putStringSet(oldKey,buttonCodes).apply()
buttonCodes.remove(buttonCode.toString())
preferences.edit().putStringSet(oldKey, buttonCodes).apply()
}
}
@ -197,7 +187,7 @@ class InputBindingSetting(
// Cleanup old mapping for this setting
removeOldMapping()
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) {it.toString()})
editor.putStringSet(key, buttonCodes.mapTo(mutableSetOf()) { it.toString() })
// Write next reverse mapping for future cleanup
editor.putString(reverseKey, key)
@ -217,7 +207,7 @@ class InputBindingSetting(
preferences.edit()
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
.putInt(getInputAxisButtonKey(axis), value)
.putBoolean(getInputAxisInvertedKey(axis),inverted)
.putBoolean(getInputAxisInvertedKey(axis), inverted)
// Write next reverse mapping for future cleanup
.putString(reverseKey, getInputAxisKey(axis))
.apply()
@ -271,9 +261,8 @@ class InputBindingSetting(
companion object {
private const val INPUT_MAPPING_PREFIX = "InputMapping"
private fun toTitleCase(raw: String): String =
raw.replace("_", " ").lowercase()
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
private fun toTitleCase(raw: String): String = raw.replace("_", " ").lowercase()
.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
private const val BUTTON_NAME_L3 = "Button L3"
private const val BUTTON_NAME_R3 = "Button R3"
@ -287,15 +276,15 @@ class InputBindingSetting(
LINUX_BTN_DPAD_RIGHT to "Dpad Right"
)
fun getButtonName(keyCode: Int): String =
buttonNameOverrides[keyCode]
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
fun getButtonName(keyCode: Int): String = buttonNameOverrides[keyCode]
?: toTitleCase(KeyEvent.keyCodeToString(keyCode).removePrefix("KEYCODE_"))
private data class DefaultButtonMapping(
val settingKey: String,
val hostKeyCode: Int,
val guestButtonCode: Int
)
// Auto-map always sets inverted = false. Users needing inverted axes should remap manually.
private data class DefaultAxisMapping(
val settingKey: String,
@ -306,45 +295,153 @@ class InputBindingSetting(
)
private val xboxFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_Y
)
)
private val nintendoFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
)
private val commonButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_L, KeyEvent.KEYCODE_BUTTON_L1, NativeLibrary.ButtonType.TRIGGER_L),
DefaultButtonMapping(Settings.KEY_BUTTON_R, KeyEvent.KEYCODE_BUTTON_R1, NativeLibrary.ButtonType.TRIGGER_R),
DefaultButtonMapping(Settings.KEY_BUTTON_ZL, KeyEvent.KEYCODE_BUTTON_L2, NativeLibrary.ButtonType.BUTTON_ZL),
DefaultButtonMapping(Settings.KEY_BUTTON_ZR, KeyEvent.KEYCODE_BUTTON_R2, NativeLibrary.ButtonType.BUTTON_ZR),
DefaultButtonMapping(Settings.KEY_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_SELECT, NativeLibrary.ButtonType.BUTTON_SELECT),
DefaultButtonMapping(Settings.KEY_BUTTON_START, KeyEvent.KEYCODE_BUTTON_START, NativeLibrary.ButtonType.BUTTON_START)
DefaultButtonMapping(
Settings.KEY_BUTTON_L,
KeyEvent.KEYCODE_BUTTON_L1,
NativeLibrary.ButtonType.TRIGGER_L
),
DefaultButtonMapping(
Settings.KEY_BUTTON_R,
KeyEvent.KEYCODE_BUTTON_R1,
NativeLibrary.ButtonType.TRIGGER_R
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZL,
KeyEvent.KEYCODE_BUTTON_L2,
NativeLibrary.ButtonType.BUTTON_ZL
),
DefaultButtonMapping(
Settings.KEY_BUTTON_ZR,
KeyEvent.KEYCODE_BUTTON_R2,
NativeLibrary.ButtonType.BUTTON_ZR
),
DefaultButtonMapping(
Settings.KEY_BUTTON_SELECT,
KeyEvent.KEYCODE_BUTTON_SELECT,
NativeLibrary.ButtonType.BUTTON_SELECT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_START,
NativeLibrary.ButtonType.BUTTON_START
)
)
private val dpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, KeyEvent.KEYCODE_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, KeyEvent.KEYCODE_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, KeyEvent.KEYCODE_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, KeyEvent.KEYCODE_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
DefaultButtonMapping(
Settings.KEY_BUTTON_UP,
KeyEvent.KEYCODE_DPAD_UP,
NativeLibrary.ButtonType.DPAD_UP
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
KeyEvent.KEYCODE_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
KeyEvent.KEYCODE_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
)
private val stickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_Z, NativeLibrary.ButtonType.STICK_C, 0, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RZ, NativeLibrary.ButtonType.STICK_C, 1, false)
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_X,
NativeLibrary.ButtonType.STICK_LEFT,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_Z,
NativeLibrary.ButtonType.STICK_C,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RZ,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
)
private val dpadAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_HAT_X, NativeLibrary.ButtonType.DPAD, 0, false),
DefaultAxisMapping(Settings.KEY_DPAD_AXIS_VERTICAL, MotionEvent.AXIS_HAT_Y, NativeLibrary.ButtonType.DPAD, 1, false)
DefaultAxisMapping(
Settings.KEY_DPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_HAT_X,
NativeLibrary.ButtonType.DPAD,
0,
false
),
DefaultAxisMapping(
Settings.KEY_DPAD_AXIS_VERTICAL,
MotionEvent.AXIS_HAT_Y,
NativeLibrary.ButtonType.DPAD,
1,
false
)
)
// Nintendo Switch Joy-Con specific mappings.
@ -360,37 +457,93 @@ class InputBindingSetting(
// KEYCODE_UNKNOWN with these scan codes because Android's input layer doesn't
// translate them to KEYCODE_DPAD_*. translateEventToKeyId() falls back to
// the scan code in that case.
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
private const val LINUX_BTN_DPAD_UP = 0x220 // 544
private const val LINUX_BTN_DPAD_DOWN = 0x221 // 545
private const val LINUX_BTN_DPAD_LEFT = 0x222 // 546
private const val LINUX_BTN_DPAD_RIGHT = 0x223 // 547
// Joy-Con face buttons: A/B are swapped by Android's evdev layer, but X/Y are not.
// This is different from both the standard Xbox table (full swap) and the
// Nintendo table (no swap).
private val joyconFaceButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, NativeLibrary.ButtonType.BUTTON_A),
DefaultButtonMapping(Settings.KEY_BUTTON_B, KeyEvent.KEYCODE_BUTTON_A, NativeLibrary.ButtonType.BUTTON_B),
DefaultButtonMapping(Settings.KEY_BUTTON_X, KeyEvent.KEYCODE_BUTTON_X, NativeLibrary.ButtonType.BUTTON_X),
DefaultButtonMapping(Settings.KEY_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Y, NativeLibrary.ButtonType.BUTTON_Y)
DefaultButtonMapping(
Settings.KEY_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
NativeLibrary.ButtonType.BUTTON_A
),
DefaultButtonMapping(
Settings.KEY_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_A,
NativeLibrary.ButtonType.BUTTON_B
),
DefaultButtonMapping(
Settings.KEY_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X,
NativeLibrary.ButtonType.BUTTON_X
),
DefaultButtonMapping(
Settings.KEY_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y,
NativeLibrary.ButtonType.BUTTON_Y
)
)
// Joy-Con D-pad: uses Linux scan codes because Android reports BTN_DPAD_* as KEYCODE_UNKNOWN
private val joyconDpadButtonMappings = listOf(
DefaultButtonMapping(Settings.KEY_BUTTON_UP, LINUX_BTN_DPAD_UP, NativeLibrary.ButtonType.DPAD_UP),
DefaultButtonMapping(Settings.KEY_BUTTON_DOWN, LINUX_BTN_DPAD_DOWN, NativeLibrary.ButtonType.DPAD_DOWN),
DefaultButtonMapping(Settings.KEY_BUTTON_LEFT, LINUX_BTN_DPAD_LEFT, NativeLibrary.ButtonType.DPAD_LEFT),
DefaultButtonMapping(Settings.KEY_BUTTON_RIGHT, LINUX_BTN_DPAD_RIGHT, NativeLibrary.ButtonType.DPAD_RIGHT)
DefaultButtonMapping(
Settings.KEY_BUTTON_UP,
LINUX_BTN_DPAD_UP,
NativeLibrary.ButtonType.DPAD_UP
),
DefaultButtonMapping(
Settings.KEY_BUTTON_DOWN,
LINUX_BTN_DPAD_DOWN,
NativeLibrary.ButtonType.DPAD_DOWN
),
DefaultButtonMapping(
Settings.KEY_BUTTON_LEFT,
LINUX_BTN_DPAD_LEFT,
NativeLibrary.ButtonType.DPAD_LEFT
),
DefaultButtonMapping(
Settings.KEY_BUTTON_RIGHT,
LINUX_BTN_DPAD_RIGHT,
NativeLibrary.ButtonType.DPAD_RIGHT
)
)
// Joy-Con sticks: left stick is AXIS_X/Y (standard), right stick is AXIS_RX/RY
// (not Z/RZ like most controllers). The horizontal axis is inverted relative to
// the standard orientation - verified empirically on paired Joy-Cons via Bluetooth.
private val joyconStickAxisMappings = listOf(
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL, MotionEvent.AXIS_X, NativeLibrary.ButtonType.STICK_LEFT, 0, false),
DefaultAxisMapping(Settings.KEY_CIRCLEPAD_AXIS_VERTICAL, MotionEvent.AXIS_Y, NativeLibrary.ButtonType.STICK_LEFT, 1, false),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_HORIZONTAL, MotionEvent.AXIS_RX, NativeLibrary.ButtonType.STICK_C, 0, true),
DefaultAxisMapping(Settings.KEY_CSTICK_AXIS_VERTICAL, MotionEvent.AXIS_RY, NativeLibrary.ButtonType.STICK_C, 1, false)
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_HORIZONTAL,
MotionEvent.AXIS_X,
NativeLibrary.ButtonType.STICK_LEFT,
0,
false
),
DefaultAxisMapping(
Settings.KEY_CIRCLEPAD_AXIS_VERTICAL,
MotionEvent.AXIS_Y,
NativeLibrary.ButtonType.STICK_LEFT,
1,
false
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_HORIZONTAL,
MotionEvent.AXIS_RX,
NativeLibrary.ButtonType.STICK_C,
0,
true
),
DefaultAxisMapping(
Settings.KEY_CSTICK_AXIS_VERTICAL,
MotionEvent.AXIS_RY,
NativeLibrary.ButtonType.STICK_C,
1,
false
)
)
/**
@ -417,9 +570,11 @@ class InputBindingSetting(
}
private val allBindingKeys: Set<String> by lazy {
(Settings.buttonKeys + Settings.triggerKeys +
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
Settings.dPadButtonKeys).toSet()
(
Settings.buttonKeys + Settings.triggerKeys +
Settings.circlePadKeys + Settings.cStickKeys + Settings.dPadAxisKeys +
Settings.dPadButtonKeys
).toSet()
}
fun clearAllBindings() {
@ -509,35 +664,37 @@ class InputBindingSetting(
/**
* Returns the settings key for the specified Citra button code.
*/
private fun getButtonKey(buttonCode: Int): String =
when (buttonCode) {
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
NativeLibrary.ButtonType.BUTTON_Y -> Settings.KEY_BUTTON_Y
NativeLibrary.ButtonType.TRIGGER_L -> Settings.KEY_BUTTON_L
NativeLibrary.ButtonType.TRIGGER_R -> Settings.KEY_BUTTON_R
NativeLibrary.ButtonType.BUTTON_ZL -> Settings.KEY_BUTTON_ZL
NativeLibrary.ButtonType.BUTTON_ZR -> Settings.KEY_BUTTON_ZR
NativeLibrary.ButtonType.BUTTON_SELECT -> Settings.KEY_BUTTON_SELECT
NativeLibrary.ButtonType.BUTTON_START -> Settings.KEY_BUTTON_START
NativeLibrary.ButtonType.BUTTON_HOME -> Settings.KEY_BUTTON_HOME
NativeLibrary.ButtonType.DPAD_UP -> Settings.KEY_BUTTON_UP
NativeLibrary.ButtonType.DPAD_DOWN -> Settings.KEY_BUTTON_DOWN
NativeLibrary.ButtonType.DPAD_LEFT -> Settings.KEY_BUTTON_LEFT
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
else -> ""
}
private fun getButtonKey(buttonCode: Int): String = when (buttonCode) {
NativeLibrary.ButtonType.BUTTON_A -> Settings.KEY_BUTTON_A
NativeLibrary.ButtonType.BUTTON_B -> Settings.KEY_BUTTON_B
NativeLibrary.ButtonType.BUTTON_X -> Settings.KEY_BUTTON_X
NativeLibrary.ButtonType.BUTTON_Y -> Settings.KEY_BUTTON_Y
NativeLibrary.ButtonType.TRIGGER_L -> Settings.KEY_BUTTON_L
NativeLibrary.ButtonType.TRIGGER_R -> Settings.KEY_BUTTON_R
NativeLibrary.ButtonType.BUTTON_ZL -> Settings.KEY_BUTTON_ZL
NativeLibrary.ButtonType.BUTTON_ZR -> Settings.KEY_BUTTON_ZR
NativeLibrary.ButtonType.BUTTON_SELECT -> Settings.KEY_BUTTON_SELECT
NativeLibrary.ButtonType.BUTTON_START -> Settings.KEY_BUTTON_START
NativeLibrary.ButtonType.BUTTON_HOME -> Settings.KEY_BUTTON_HOME
NativeLibrary.ButtonType.DPAD_UP -> Settings.KEY_BUTTON_UP
NativeLibrary.ButtonType.DPAD_DOWN -> Settings.KEY_BUTTON_DOWN
NativeLibrary.ButtonType.DPAD_LEFT -> Settings.KEY_BUTTON_LEFT
NativeLibrary.ButtonType.DPAD_RIGHT -> Settings.KEY_BUTTON_RIGHT
else -> ""
}
/**
* Get the mutable set of int button values this key should map to given an event
*/
fun getButtonSet(keyCode: KeyEvent):MutableSet<Int> {
fun getButtonSet(keyCode: KeyEvent): MutableSet<Int> {
val key = getInputButtonKey(keyCode)
val preferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val preferences = PreferenceManager.getDefaultSharedPreferences(
CitraApplication.appContext
)
var buttonCodes = try {
preferences.getStringSet(key, mutableSetOf<String>())
} catch (e: ClassCastException) {
val prefInt = preferences.getInt(key, -1);
val prefInt = preferences.getInt(key, -1)
val migratedSet = if (prefInt != -1) {
mutableSetOf(prefInt.toString())
} else {
@ -549,15 +706,17 @@ class InputBindingSetting(
return buttonCodes.mapNotNull { it.toIntOrNull() }.toMutableSet()
}
private fun getInputButtonKey(keyId: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${keyId}"
private fun getInputButtonKey(keyId: Int): String =
"${INPUT_MAPPING_PREFIX}_HostAxis_$keyId"
/** Falls back to the scan code when keyCode is KEYCODE_UNKNOWN. */
fun getInputButtonKey(event: KeyEvent): String = getInputButtonKey(translateEventToKeyId(event))
fun getInputButtonKey(event: KeyEvent): String =
getInputButtonKey(translateEventToKeyId(event))
/**
* Helper function to get the settings key for an gamepad axis.
*/
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_${axis}"
fun getInputAxisKey(axis: Int): String = "${INPUT_MAPPING_PREFIX}_HostAxis_$axis"
/**
* Helper function to get the settings key for an gamepad axis button (stick or trigger).
@ -575,7 +734,6 @@ class InputBindingSetting(
fun getInputAxisOrientationKey(axis: Int): String =
"${getInputAxisKey(axis)}_GuestOrientation"
/**
* This function translates a keyEvent into an "keyid"
* This key id is either the keyCode from the event, or
@ -585,12 +743,10 @@ class InputBindingSetting(
* This handles keys like the media-keys on google statia-controllers
* that don't have a conventional "mapping" and report as "unknown"
*/
fun translateEventToKeyId(event: KeyEvent): Int {
return if (event.keyCode == 0) {
event.scanCode
} else {
event.keyCode
}
fun translateEventToKeyId(event: KeyEvent): Int = if (event.keyCode == 0) {
event.scanCode
} else {
event.keyCode
}
}
}

View file

@ -25,7 +25,7 @@ class MultiChoiceSetting(
try {
val setting = setting as IntListSetting
return setting.list
}catch (_: ClassCastException) {
} catch (_: ClassCastException) {
}
return defaultValue!!
}
@ -42,5 +42,4 @@ class MultiChoiceSetting(
intSetting.list = selection
return intSetting
}
}

View file

@ -29,8 +29,11 @@ class SliderSetting(
val ret = when (setting) {
is AbstractIntSetting -> setting.int.toFloat()
is FloatSetting -> setting.float
is ScaledFloatSetting -> setting.float
else -> {
Log.error("[SliderSetting] Error casting setting type.")
-1f
@ -38,6 +41,7 @@ class SliderSetting(
}
return ret.coerceIn(min.toFloat(), max.toFloat())
}
/**
* Write a value to the backing int. If that int was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.

View file

@ -22,11 +22,11 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import java.io.IOException
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.ActivitySettingsBinding
import java.io.IOException
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntSetting
@ -35,13 +35,15 @@ import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.SettingsViewModel
import org.citra.citra_emu.features.settings.model.StringSetting
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.InsetsHelper
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.ThemeUtil
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
class SettingsActivity :
AppCompatActivity(),
SettingsActivityView {
private val presenter = SettingsActivityPresenter(this)
private lateinit var binding: ActivitySettingsBinding
@ -205,7 +207,7 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
presenter.onSettingsReset()
val controllerKeys = Settings.buttonKeys + Settings.circlePadKeys + Settings.cStickKeys +
Settings.dPadAxisKeys + Settings.dPadButtonKeys + Settings.triggerKeys
Settings.dPadAxisKeys + Settings.dPadButtonKeys + Settings.triggerKeys
val editor =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext).edit()
controllerKeys.forEach { editor.remove(it) }

View file

@ -12,11 +12,11 @@ import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.TurboHelper
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
@ -72,11 +72,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
val nomediaFileExists: Boolean
try {
dataDirTreeUri = PermissionsHandler.citraDirectory
dataDirDocument = DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
dataDirDocument =
DocumentFile.fromTreeUri(CitraApplication.appContext, dataDirTreeUri)!!
nomediaFileDocument = dataDirDocument.findFile(".nomedia")
nomediaFileExists = (nomediaFileDocument != null)
} catch (e: Exception) {
Log.error("[SettingsActivity]: Error occurred while trying to find .nomedia, error: " + e.message)
Log.error(
"[SettingsActivity]: Error occurred while trying to find .nomedia, error: " +
e.message
)
return
}
@ -95,7 +99,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
if (finishing && shouldSave) {
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView)
//added to ensure that layout changes take effect as soon as settings window closes
// added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
updateAndroidImageVisibility()

View file

@ -29,6 +29,9 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.DialogSliderBinding
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
@ -39,16 +42,16 @@ import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
import org.citra.citra_emu.features.settings.model.AbstractFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.SingleChoiceSetting
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SliderSetting
import org.citra.citra_emu.features.settings.model.view.StringInputSetting
import org.citra.citra_emu.features.settings.model.view.StringSingleChoiceSetting
@ -69,14 +72,10 @@ import org.citra.citra_emu.fragments.AutoMapDialogFragment
import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
import org.citra.citra_emu.utils.SystemSaveGame
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
public val context: Context
) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener,
class SettingsAdapter(private val fragmentView: SettingsFragmentView, public val context: Context) :
RecyclerView.Adapter<SettingViewHolder?>(),
DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private var settings: ArrayList<SettingsItem>? = null
private var clickedItem: SettingsItem? = null
@ -148,17 +147,11 @@ class SettingsAdapter(
getItem(position)?.let { holder.bind(it) }
}
private fun getItem(position: Int): SettingsItem? {
return settings?.get(position)
}
private fun getItem(position: Int): SettingsItem? = settings?.get(position)
override fun getItemCount(): Int {
return settings?.size ?: 0
}
override fun getItemCount(): Int = settings?.size ?: 0
override fun getItemViewType(position: Int): Int {
return getItem(position)?.type ?: -1
}
override fun getItemViewType(position: Int): Int = getItem(position)?.type ?: -1
fun setSettingsList(newSettings: ArrayList<SettingsItem>?) {
if (settings == null) {
@ -196,22 +189,28 @@ class SettingsAdapter(
}
SettingsItem.TYPE_SINGLE_CHOICE -> {
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
(oldItem as SingleChoiceSetting).isEnabled ==
(newItem as SingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_MULTI_CHOICE -> {
(oldItem as MultiChoiceSetting).isEnabled == (newItem as MultiChoiceSetting).isEnabled
(oldItem as MultiChoiceSetting).isEnabled ==
(newItem as MultiChoiceSetting).isEnabled
}
SettingsItem.TYPE_DATETIME_SETTING -> {
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
(oldItem as DateTimeSetting).isEnabled ==
(newItem as DateTimeSetting).isEnabled
}
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
(oldItem as StringSingleChoiceSetting).isEnabled ==
(newItem as StringSingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_STRING_INPUT -> {
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
(oldItem as StringInputSetting).isEnabled ==
(newItem as StringInputSetting).isEnabled
}
else -> {
@ -231,9 +230,10 @@ class SettingsAdapter(
fragmentView.onSettingChanged()
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
if (fragmentView.activityView != null)
// Reload the settings list to update the UI
if (fragmentView.activityView != null) {
// Reload the settings list to update the UI
fragmentView.loadSettingsList()
}
}
private fun onSingleChoiceClick(item: SingleChoiceSetting) {
@ -253,7 +253,7 @@ class SettingsAdapter(
private fun onMultiChoiceClick(item: MultiChoiceSetting) {
clickedItem = item
val value: BooleanArray = getSelectionForMultiChoiceValue(item);
val value: BooleanArray = getSelectionForMultiChoiceValue(item)
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setMultiChoiceItems(item.choicesId, value, this)
@ -298,7 +298,7 @@ class SettingsAdapter(
val time = item.value.substringAfter(" ")
val formatter = SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZZZZ")
val gmt = formatter.parse("${date}T${time}+0000")
val gmt = formatter.parse("${date}T$time+0000")
gmt!!.time
}
@ -354,7 +354,6 @@ class SettingsAdapter(
clickedPosition = position
sliderProgress = (item.selectedFloat * 100f).roundToInt() / 100f
val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
@ -376,9 +375,9 @@ class SettingsAdapter(
value = sliderProgress
textSliderValue?.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
var textValue = s.toString().toFloatOrNull();
var textValue = s.toString().toFloatOrNull()
if (item.setting !is FloatSetting) {
textValue = textValue?.roundToInt()?.toFloat();
textValue = textValue?.roundToInt()?.toFloat()
}
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout?.error = "Inappropriate value"
@ -425,6 +424,7 @@ class SettingsAdapter(
}
is FloatSetting -> (item.setting as FloatSetting).defaultValue
else -> item.defaultValue ?: 0f
}
onClick(dialog, which)
@ -495,7 +495,9 @@ class SettingsAdapter(
it.setSelectedValue(value)
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
else -> throw IllegalStateException(
"Unrecognized type used for SingleChoiceSetting!"
)
}
fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
@ -518,7 +520,9 @@ class SettingsAdapter(
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
}
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
else -> throw IllegalStateException(
"Unrecognized type used for StringSingleChoiceSetting!"
)
}
fragmentView?.putSetting(setting)
@ -569,13 +573,22 @@ class SettingsAdapter(
textInputValue = ""
}
//onclick for multichoice
// onclick for multichoice
override fun onClick(dialog: DialogInterface?, which: Int, isChecked: Boolean) {
val mcsetting = clickedItem as? MultiChoiceSetting
mcsetting?.let {
val value = getValueForMultiChoiceSelection(it, which)
if (it.selectedValues.contains(value) != isChecked) {
val setting = it.setSelectedValue((if (isChecked) it.selectedValues + value else it.selectedValues - value).sorted())
val setting = it.setSelectedValue(
(
if (isChecked) {
it.selectedValues + value
} else {
it.selectedValues -
value
}
).sorted()
)
fragmentView?.putSetting(setting)
fragmentView?.onSettingChanged()
}
@ -583,13 +596,13 @@ class SettingsAdapter(
}
}
fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.reset_setting_confirmation)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
when (setting) {
is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
is AbstractFloatSetting -> {
if (setting is ScaledFloatSetting) {
setting.float = setting.defaultValue * setting.scale
@ -599,7 +612,9 @@ class SettingsAdapter(
}
is AbstractIntSetting -> setting.int = setting.defaultValue as Int
is AbstractStringSetting -> setting.string = setting.defaultValue as String
is AbstractShortSetting -> setting.short = setting.defaultValue as Short
}
notifyItemChanged(position)
@ -628,14 +643,16 @@ class SettingsAdapter(
}
fun onClickDisabledSetting(isRuntimeDisabled: Boolean) {
val titleId = if (isRuntimeDisabled)
val titleId = if (isRuntimeDisabled) {
R.string.setting_not_editable
else
} else {
R.string.setting_disabled
val messageId = if (isRuntimeDisabled)
}
val messageId = if (isRuntimeDisabled) {
R.string.setting_not_editable_description
else
} else {
R.string.setting_disabled_description
}
MessageDialogFragment.newInstance(
titleId,
@ -652,7 +669,10 @@ class SettingsAdapter(
}
fun onLongClickAutoMap(): Boolean {
showConfirmationDialog(R.string.controller_clear_all, R.string.controller_clear_all_confirm) {
showConfirmationDialog(
R.string.controller_clear_all,
R.string.controller_clear_all_confirm
) {
InputBindingSetting.clearAllBindings()
fragmentView.loadSettingsList()
fragmentView.onSettingChanged()
@ -661,14 +681,20 @@ class SettingsAdapter(
}
fun onClickRegenerateConsoleId() {
showConfirmationDialog(R.string.regenerate_console_id, R.string.regenerate_console_id_description) {
showConfirmationDialog(
R.string.regenerate_console_id,
R.string.regenerate_console_id_description
) {
SystemSaveGame.regenerateConsoleId()
notifyDataSetChanged()
}
}
fun onClickRegenerateMAC() {
showConfirmationDialog(R.string.regenerate_mac_address, R.string.regenerate_mac_address_description) {
showConfirmationDialog(
R.string.regenerate_mac_address,
R.string.regenerate_mac_address_description
) {
SystemSaveGame.regenerateMac()
notifyDataSetChanged()
}
@ -732,18 +758,18 @@ class SettingsAdapter(
}
private fun getSelectionForMultiChoiceValue(item: MultiChoiceSetting): BooleanArray {
val value = item.selectedValues;
val valuesId = item.valuesId;
val value = item.selectedValues
val valuesId = item.valuesId
if (valuesId > 0) {
val valuesArray = context.resources.getIntArray(valuesId);
val res = BooleanArray(valuesArray.size){false}
val valuesArray = context.resources.getIntArray(valuesId)
val res = BooleanArray(valuesArray.size) { false }
for (index in valuesArray.indices) {
if (value.contains(valuesArray[index])) {
res[index] = true;
res[index] = true
}
}
return res;
return res
}
return BooleanArray(1){false};
return BooleanArray(1) { false }
}
}

View file

@ -19,7 +19,9 @@ import org.citra.citra_emu.databinding.FragmentSettingsBinding
import org.citra.citra_emu.features.settings.model.AbstractSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
class SettingsFragment : Fragment(), SettingsFragmentView {
class SettingsFragment :
Fragment(),
SettingsFragmentView {
override var activityView: SettingsActivityView? = null
private val fragmentPresenter = SettingsFragmentPresenter(this)

View file

@ -27,8 +27,8 @@ import org.citra.citra_emu.features.settings.model.AbstractShortSetting
import org.citra.citra_emu.features.settings.model.AbstractStringSetting
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.FloatSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.IntListSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.ScaledFloatSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.model.StringSetting
@ -94,18 +94,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
when (menuTag) {
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
Settings.SECTION_CORE -> addGeneralSettings(sl)
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
Settings.SECTION_CAMERA -> addCameraSettings(sl)
Settings.SECTION_CONTROLS -> addControlsSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_LAYOUT -> addLayoutSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_CUSTOM_LANDSCAPE -> addCustomLandscapeSettings(sl)
Settings.SECTION_CUSTOM_PORTRAIT -> addCustomPortraitSettings(sl)
Settings.SECTION_PERFORMANCE_OVERLAY -> addPerformanceOverlaySettings(sl)
else -> {
fragmentView.showToastMessage("Unimplemented menu", false)
return
@ -128,13 +141,9 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
}
private fun getSmallerDimension(): Int {
return getDimensions().min()
}
private fun getSmallerDimension(): Int = getDimensions().min()
private fun getLargerDimension(): Int {
return getDimensions().max()
}
private fun getLargerDimension(): Int = getDimensions().max()
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings))
@ -360,7 +369,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulated_region,
0,
R.array.regionNames,
R.array.regionValues,
R.array.regionValues
)
)
add(
@ -377,7 +386,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
get() {
val ret = SystemSaveGame.getCountryCode()
checkCountryCompatibility()
return ret;
return ret
}
set(value) {
SystemSaveGame.setCountryCode(value)
@ -626,20 +635,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
) {
continue // Legacy cameras cannot be used with the NDK
continue // Legacy cameras cannot be used with the NDK
}
supportedCameraIdList.add(id)
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
var stringId: Int = R.string.camera_facing_external
when (facing) {
CameraCharacteristics.LENS_FACING_FRONT -> stringId =
R.string.camera_facing_front
CameraCharacteristics.LENS_FACING_FRONT ->
stringId =
R.string.camera_facing_front
CameraCharacteristics.LENS_FACING_BACK -> stringId =
R.string.camera_facing_back
CameraCharacteristics.LENS_FACING_BACK ->
stringId =
R.string.camera_facing_back
CameraCharacteristics.LENS_FACING_EXTERNAL -> stringId =
R.string.camera_facing_external
CameraCharacteristics.LENS_FACING_EXTERNAL ->
stringId =
R.string.camera_facing_external
}
supportedCameraNameList.add(
String.format("%1\$s (%2\$s)", id, settingsActivity.getString(stringId))
@ -816,12 +828,22 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(InputBindingSetting(button, Settings.axisTitles[i]))
}
add(HeaderSetting(R.string.controller_dpad_axis,R.string.controller_dpad_axis_description))
add(
HeaderSetting(
R.string.controller_dpad_axis,
R.string.controller_dpad_axis_description
)
)
Settings.dPadAxisKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.axisTitles[i]))
}
add(HeaderSetting(R.string.controller_dpad_button,R.string.controller_dpad_button_description))
add(
HeaderSetting(
R.string.controller_dpad_button,
R.string.controller_dpad_button_description
)
)
Settings.dPadButtonKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.dPadTitles[i]))
@ -833,7 +855,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
add(InputBindingSetting(button, Settings.triggerTitles[i]))
}
add(HeaderSetting(R.string.controller_hotkeys,R.string.controller_hotkeys_description))
add(HeaderSetting(R.string.controller_hotkeys, R.string.controller_hotkeys_description))
Settings.hotKeys.forEachIndexed { i: Int, key: String ->
val button = getInputObject(key)
add(InputBindingSetting(button, Settings.hotkeyTitles[i]))
@ -851,8 +873,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
}
private fun getInputObject(key: String): AbstractStringSetting {
return object : AbstractStringSetting {
private fun getInputObject(key: String): AbstractStringSetting =
object : AbstractStringSetting {
override var string: String
get() = preferences.getString(key, "")!!
set(value) {
@ -866,7 +888,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
override val valueAsString = preferences.getString(key, "")!!
override val defaultValue = ""
}
}
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
@ -889,7 +910,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.spirv_shader_gen,
R.string.spirv_shader_gen_description,
BooleanSetting.SPIRV_SHADER_GEN.key,
BooleanSetting.SPIRV_SHADER_GEN.defaultValue,
BooleanSetting.SPIRV_SHADER_GEN.defaultValue
)
)
add(
@ -898,7 +919,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.disable_spirv_optimizer,
R.string.disable_spirv_optimizer_description,
BooleanSetting.DISABLE_SPIRV_OPTIMIZER.key,
BooleanSetting.DISABLE_SPIRV_OPTIMIZER.defaultValue,
BooleanSetting.DISABLE_SPIRV_OPTIMIZER.defaultValue
)
)
add(
@ -921,7 +942,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.RESOLUTION_FACTOR.defaultValue
)
)
add(
add(
SwitchSetting(
BooleanSetting.USE_INTEGER_SCALING,
R.string.use_integer_scaling,
@ -1002,7 +1023,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.array.render3dValues,
IntSetting.STEREOSCOPIC_3D_MODE.key,
IntSetting.STEREOSCOPIC_3D_MODE.defaultValue,
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
isEnabled =
IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
)
)
@ -1035,7 +1057,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.swap_eyes_3d_description,
BooleanSetting.SWAP_EYES_3D.key,
BooleanSetting.SWAP_EYES_3D.defaultValue,
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
isEnabled =
IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
)
)
@ -1201,7 +1224,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
)
)
add (
add(
SwitchSetting(
BooleanSetting.ENABLE_SECONDARY_DISPLAY,
R.string.emulation_secondary_display_enable,
@ -1231,7 +1254,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.array.aspectRatioValues,
IntSetting.ASPECT_RATIO.key,
IntSetting.ASPECT_RATIO.defaultValue,
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.SINGLE_SCREEN.int,
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.SINGLE_SCREEN.int
)
)
add(
@ -1288,7 +1311,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
get() = (FloatSetting.BACKGROUND_RED.float * 255).toInt()
set(value) {
FloatSetting.BACKGROUND_RED.float = value.toFloat() / 255
settings.saveSetting(FloatSetting.BACKGROUND_RED, SettingsFile.FILE_NAME_CONFIG)
settings.saveSetting(
FloatSetting.BACKGROUND_RED,
SettingsFile.FILE_NAME_CONFIG
)
}
override val key = null
override val section = null
@ -1311,7 +1337,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
get() = (FloatSetting.BACKGROUND_GREEN.float * 255).toInt()
set(value) {
FloatSetting.BACKGROUND_GREEN.float = value.toFloat() / 255
settings.saveSetting(FloatSetting.BACKGROUND_GREEN, SettingsFile.FILE_NAME_CONFIG)
settings.saveSetting(
FloatSetting.BACKGROUND_GREEN,
SettingsFile.FILE_NAME_CONFIG
)
}
override val key = null
override val section = null
@ -1334,7 +1363,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
get() = (FloatSetting.BACKGROUND_BLUE.float * 255).toInt()
set(value) {
FloatSetting.BACKGROUND_BLUE.float = value.toFloat() / 255
settings.saveSetting(FloatSetting.BACKGROUND_BLUE, SettingsFile.FILE_NAME_CONFIG)
settings.saveSetting(
FloatSetting.BACKGROUND_BLUE,
SettingsFile.FILE_NAME_CONFIG
)
}
override val key = null
override val section = null
@ -1380,9 +1412,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addPerformanceOverlaySettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.performance_overlay_options))
settingsActivity.setToolbarTitle(
settingsActivity.getString(R.string.performance_overlay_options)
)
sl.apply {
add(HeaderSetting(R.string.visibility))
add(
@ -1411,11 +1444,10 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.performance_overlay_position,
R.string.performance_overlay_position_description,
R.array.statsPosition,
R.array.statsPositionValues,
R.array.statsPositionValues
)
)
add(HeaderSetting(R.string.information))
add(
@ -1481,7 +1513,9 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
private fun addCustomLandscapeSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_landscape_custom_layout))
settingsActivity.setToolbarTitle(
settingsActivity.getString(R.string.emulation_landscape_custom_layout)
)
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
@ -1582,11 +1616,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
)
}
}
private fun addCustomPortraitSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_portrait_custom_layout))
settingsActivity.setToolbarTitle(
settingsActivity.getString(R.string.emulation_portrait_custom_layout)
)
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
@ -1687,7 +1722,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
@ -1881,7 +1915,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
BooleanSetting.DETERMINISTIC_ASYNC_OPERATIONS.defaultValue
)
)
}
}

View file

@ -6,16 +6,16 @@ package org.citra.citra_emu.features.settings.ui.viewholder
import android.annotation.SuppressLint
import android.view.View
import org.citra.citra_emu.databinding.ListItemSettingBinding
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import org.citra.citra_emu.databinding.ListItemSettingBinding
import org.citra.citra_emu.features.settings.model.view.DateTimeSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
import java.text.SimpleDateFormat
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@ -39,7 +39,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
val time = setting.value.substringAfter(" ")
val formatter = SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssZZZZ")
val gmt = formatter.parse("${date}T${time}+0000")
val gmt = formatter.parse("${date}T$time+0000")
gmt!!.time / 1000
}
val instant = Instant.ofEpochMilli(epochTime * 1000)

View file

@ -21,7 +21,7 @@ class HeaderViewHolder(val binding: ListItemSettingsHeaderBinding, adapter: Sett
if (item.descriptionId != 0) {
binding.textHeaderDescription.visibility = View.VISIBLE
binding.textHeaderDescription.setText(item.descriptionId)
}else {
} else {
binding.textHeaderDescription.visibility = View.GONE
}
}

View file

@ -6,8 +6,8 @@ package org.citra.citra_emu.features.settings.ui.viewholder
import android.view.View
import org.citra.citra_emu.databinding.ListItemSettingBinding
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.model.view.MultiChoiceSetting
import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
@ -42,13 +42,13 @@ class MultiChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Settin
is MultiChoiceSetting -> {
val resMgr = binding.textSettingDescription.context.resources
val values = resMgr.getIntArray(item.valuesId)
var resList:List<String> = emptyList();
var resList: List<String> = emptyList()
values.forEachIndexed { i: Int, value: Int ->
if ((setting as MultiChoiceSetting).selectedValues.contains(value)) {
resList = resList + resMgr.getStringArray(item.choicesId)[i];
resList = resList + resMgr.getStringArray(item.choicesId)[i]
}
}
return resList.joinToString();
return resList.joinToString()
}
else -> return ""

View file

@ -10,7 +10,9 @@ import org.citra.citra_emu.features.settings.model.view.SettingsItem
import org.citra.citra_emu.features.settings.ui.SettingsAdapter
abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) :
RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
RecyclerView.ViewHolder(itemView),
View.OnClickListener,
View.OnLongClickListener {
init {
itemView.setOnClickListener(this)

View file

@ -31,7 +31,9 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
binding.textSettingValue.text = when (setting.setting) {
is ScaledFloatSetting ->
"${(setting.setting as ScaledFloatSetting).float.toInt()}${setting.units}"
is FloatSetting -> "${(setting.setting as AbstractFloatSetting).float}${setting.units}"
else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}"
}

View file

@ -7,6 +7,11 @@ package org.citra.citra_emu.features.settings.utils
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStreamReader
import java.util.TreeMap
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractSetting
@ -23,12 +28,6 @@ import org.citra.citra_emu.utils.BiMap
import org.citra.citra_emu.utils.DirectoryInitialization.userDirectory
import org.citra.citra_emu.utils.Log
import org.ini4j.Wini
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStreamReader
import java.util.TreeMap
/**
* Contains static methods for interacting with .ini files in which settings are stored.
@ -90,9 +89,8 @@ object SettingsFile {
return sections
}
fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
return readFile(getSettingsFile(fileName), false, view)
}
fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> =
readFile(getSettingsFile(fileName), false, view)
fun readFile(fileName: String): HashMap<String, SettingSection?> = readFile(fileName, null)
@ -107,9 +105,7 @@ object SettingsFile {
fun readCustomGameSettings(
gameId: String,
view: SettingsActivityView?
): HashMap<String, SettingSection?> {
return readFile(getCustomGameSettingsFile(gameId), true, view)
}
): HashMap<String, SettingSection?> = readFile(getCustomGameSettingsFile(gameId), true, view)
/**
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
@ -143,15 +139,13 @@ object SettingsFile {
Log.error("[SettingsFile] File not found: $fileName.ini: ${e.message}")
view.showToastMessage(
CitraApplication.appContext
.getString(R.string.error_saving, fileName, e.message), false
.getString(R.string.error_saving, fileName, e.message),
false
)
}
}
fun saveFile(
fileName: String,
setting: AbstractSetting
) {
fun saveFile(fileName: String, setting: AbstractSetting) {
val ini = getSettingsFile(fileName)
try {
val context: Context = CitraApplication.appContext
@ -168,21 +162,19 @@ object SettingsFile {
}
}
private fun mapSectionNameFromIni(generalSectionName: String): String? {
return if (sectionsMap.getForward(generalSectionName) != null) {
private fun mapSectionNameFromIni(generalSectionName: String): String? =
if (sectionsMap.getForward(generalSectionName) != null) {
sectionsMap.getForward(generalSectionName)
} else {
generalSectionName
}
}
private fun mapSectionNameToIni(generalSectionName: String): String {
return if (sectionsMap.getBackward(generalSectionName) != null) {
private fun mapSectionNameToIni(generalSectionName: String): String =
if (sectionsMap.getBackward(generalSectionName) != null) {
sectionsMap.getBackward(generalSectionName).toString()
} else {
generalSectionName
}
}
fun getSettingsFile(fileName: String): DocumentFile {
val root = DocumentFile.fromTreeUri(CitraApplication.appContext, Uri.parse(userDirectory))

View file

@ -96,28 +96,27 @@ class AboutFragment : Fragment() {
startActivity(intent)
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpAppBar = binding.toolbarAbout.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarAbout.layoutParams = mlpAppBar
val mlpAppBar = binding.toolbarAbout.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarAbout.layoutParams = mlpAppBar
val mlpScrollAbout = binding.scrollAbout.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.scrollAbout.layoutParams = mlpScrollAbout
val mlpScrollAbout = binding.scrollAbout.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.scrollAbout.layoutParams = mlpScrollAbout
binding.contentAbout.updatePadding(bottom = barInsets.bottom)
binding.contentAbout.updatePadding(bottom = barInsets.bottom)
windowInsets
}
windowInsets
}
}

View file

@ -90,7 +90,9 @@ class AutoMapDialogFragment : BottomSheetDialogFragment() {
// Nintendo layout: east position sends KEYCODE_BUTTON_A (96)
val isNintendoLayout = when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A -> true
KeyEvent.KEYCODE_BUTTON_B -> false
else -> {
// Unrecognized button - ignore and wait for a valid press
Log.warning("[AutoMap] Ignoring unrecognized keycode $keyCode, waiting for A or B")
@ -117,9 +119,7 @@ class AutoMapDialogFragment : BottomSheetDialogFragment() {
companion object {
const val TAG = "AutoMapDialogFragment"
fun newInstance(
onComplete: () -> Unit
): AutoMapDialogFragment {
fun newInstance(onComplete: () -> Unit): AutoMapDialogFragment {
val dialog = AutoMapDialogFragment()
dialog.onComplete = onComplete
return dialog

View file

@ -60,7 +60,9 @@ class CitraDirectoryDialogFragment : DialogFragment() {
}
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
if (!PermissionsHandler.hasWriteAccess(requireContext())) {
PermissionsHandler.compatibleSelectDirectory((requireActivity() as MainActivity).openCitraDirectory)
PermissionsHandler.compatibleSelectDirectory(
(requireActivity() as MainActivity).openCitraDirectory
)
}
}
.show()

View file

@ -9,16 +9,16 @@ import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.Lifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.NativeLibrary
class CompressProgressDialogFragment : DialogFragment() {
private lateinit var progressBar: ProgressBar
@ -37,14 +37,27 @@ class CompressProgressDialogFragment : DialogFragment() {
val view = layoutInflater.inflate(R.layout.dialog_compress_progress, null)
progressBar = view.findViewById(R.id.compress_progress)
val label = view.findViewById<android.widget.TextView>(R.id.compress_label)
label.text = if (isCompressing) getString(R.string.compressing) else getString(R.string.decompressing)
label.text =
if (isCompressing) {
getString(
R.string.compressing
)
} else {
getString(R.string.decompressing)
}
isCancelable = false
progressBar.isIndeterminate = true
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(CompressProgressDialogViewModel.total, CompressProgressDialogViewModel.progress) { total, progress ->
combine(
CompressProgressDialogViewModel.total,
CompressProgressDialogViewModel.progress
) {
total,
progress
->
total to progress
}.collectLatest { (total, progress) ->
if (total <= 0) {
@ -63,7 +76,10 @@ class CompressProgressDialogFragment : DialogFragment() {
val builder = MaterialAlertDialogBuilder(requireContext())
.setView(view)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel) { _: android.content.DialogInterface, _: Int ->
.setNegativeButton(android.R.string.cancel) {
_: android.content.DialogInterface,
_: Int
->
outputPath?.let { path ->
NativeLibrary.deleteDocument(path)
}
@ -77,7 +93,10 @@ class CompressProgressDialogFragment : DialogFragment() {
private const val ARG_IS_COMPRESSING = "isCompressing"
private const val ARG_OUTPUT_PATH = "outputPath"
fun newInstance(isCompressing: Boolean, outputPath: String?): CompressProgressDialogFragment {
fun newInstance(
isCompressing: Boolean,
outputPath: String?
): CompressProgressDialogFragment {
val frag = CompressProgressDialogFragment()
val args = Bundle()
args.putBoolean(ARG_IS_COMPRESSING, isCompressing)

View file

@ -52,9 +52,7 @@ class CopyDirProgressDialog : DialogFragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return binding.root
}
): View = binding.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -144,7 +142,8 @@ class CopyDirProgressDialog : DialogFragment() {
callback?.onStepCompleted(0, false)
viewModel.setCopyComplete(true)
}
})
}
)
}
}
return CopyDirProgressDialog()

View file

@ -19,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.transition.MaterialSharedAxis
import java.io.IOException
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.citra.citra_emu.R
@ -27,9 +28,8 @@ import org.citra.citra_emu.databinding.FragmentDriverManagerBinding
import org.citra.citra_emu.utils.FileUtil.asDocumentFile
import org.citra.citra_emu.utils.FileUtil.inputStream
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.viewmodel.HomeViewModel
import org.citra.citra_emu.viewmodel.DriverViewModel
import java.io.IOException
import org.citra.citra_emu.viewmodel.HomeViewModel
class DriverManagerFragment : Fragment() {
private var _binding: FragmentDriverManagerBinding? = null
@ -110,41 +110,40 @@ class DriverManagerFragment : Fragment() {
driverViewModel.onCloseDriverManager()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarDrivers.layoutParams = mlpAppBar
val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarDrivers.layoutParams = mlpAppBar
val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
mlplistDrivers.leftMargin = leftInsets
mlplistDrivers.rightMargin = rightInsets
binding.listDrivers.layoutParams = mlplistDrivers
val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
mlplistDrivers.leftMargin = leftInsets
mlplistDrivers.rightMargin = rightInsets
binding.listDrivers.layoutParams = mlplistDrivers
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonInstall.layoutParams = mlpFab
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
val mlpFab =
binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
mlpFab.leftMargin = leftInsets + fabSpacing
mlpFab.rightMargin = rightInsets + fabSpacing
mlpFab.bottomMargin = barInsets.bottom + fabSpacing
binding.buttonInstall.layoutParams = mlpFab
binding.listDrivers.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
binding.listDrivers.updatePadding(
bottom = barInsets.bottom +
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
)
windowInsets
}
windowInsets
}
private val getDriver =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->

View file

@ -38,9 +38,9 @@ import androidx.activity.OnBackPressedCallback
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.get
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.get
import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import androidx.fragment.app.Fragment
@ -78,15 +78,18 @@ import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.BuildUtil
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.GameHelper
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.ViewUtils
import org.citra.citra_emu.viewmodel.EmulationViewModel
class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback {
class EmulationFragment :
Fragment(),
SurfaceHolder.Callback,
Choreographer.FrameCallback {
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
@ -117,7 +120,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is EmulationActivity) {
NativeLibrary.setEmulationActivity(context)
NativeLibrary.setEmulationActivity(context)
} else {
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
}
@ -145,10 +148,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
if (!BuildUtil.isGooglePlayBuild) {
val intentUriString = intentUri.toString()
// We need to build a special path as the incoming URI may be SAF exclusive
Log.warning("[EmulationFragment] Cannot determine native path of URI \"" +
intentUriString + "\", using file descriptor instead.")
Log.warning(
"[EmulationFragment] Cannot determine native path of URI \"" +
intentUriString + "\", using file descriptor instead."
)
if (!intentUriString.startsWith("!")) {
gameFd = requireContext().contentResolver.openFileDescriptor(intentUri, "r")?.detachFd()
gameFd =
requireContext().contentResolver.openFileDescriptor(
intentUri,
"r"
)?.detachFd()
intentUri = if (gameFd != null) {
Uri.parse("fd://" + gameFd.toString())
} else {
@ -159,7 +168,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
intentGame =
intentUri?.let {
// isInstalled, addedToLibrary and mediaType do not matter here
GameHelper.getGame(it, isInstalled = false, addedToLibrary = false, mediaType = Game.MediaType.GAME_CARD)
GameHelper.getGame(
it,
isInstalled = false,
addedToLibrary = false,
mediaType = Game.MediaType.GAME_CARD
)
}
}
@ -199,10 +213,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
emulationActivity.secondaryDisplayManager.availableDisplays.isNotEmpty()
binding.inGameMenu.menu.findItem(R.id.menu_landscape_screen_layout).isVisible =
CitraApplication.appContext.resources.configuration.orientation !=
Configuration.ORIENTATION_PORTRAIT
Configuration.ORIENTATION_PORTRAIT
binding.inGameMenu.menu.findItem(R.id.menu_portrait_screen_layout).isVisible =
CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
Configuration.ORIENTATION_PORTRAIT
return binding.root
}
@ -486,7 +500,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
ViewUtils.showView(binding.surfaceInputOverlay)
binding.inGameMenu.menu.findItem(R.id.menu_emulation_savestates)
.setVisible(NativeLibrary.getSavestateInfo() != null)
binding.drawerLayout.setDrawerLockMode(EmulationMenuSettings.drawerLockMode)
binding.drawerLayout.setDrawerLockMode(
EmulationMenuSettings.drawerLockMode
)
}
}
}
@ -496,9 +512,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
setInsets()
}
fun isDrawerOpen(): Boolean {
return binding.drawerLayout.isOpen
}
fun isDrawerOpen(): Boolean = binding.drawerLayout.isOpen
private fun togglePause() {
if (emulationState.isPaused) {
@ -612,7 +626,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
private fun showStateSubmenu(isSaving: Boolean) {
val savestates = NativeLibrary.getSavestateInfo()
val popupMenu = PopupMenu(
@ -1023,7 +1036,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
else ->
R.id.menu_portrait_layout_top_full
}
popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true)
@ -1031,12 +1043,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_portrait_layout_top_full -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.TOP_FULL_WIDTH.int)
screenAdjustmentUtil.changePortraitOrientation(
PortraitScreenLayout.TOP_FULL_WIDTH.int
)
true
}
R.id.menu_portrait_layout_original -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.ORIGINAL.int)
screenAdjustmentUtil.changePortraitOrientation(
PortraitScreenLayout.ORIGINAL.int
)
true
}
@ -1046,7 +1062,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
R.string.emulation_adjust_custom_layout,
Toast.LENGTH_LONG
).show()
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int)
screenAdjustmentUtil.changePortraitOrientation(
PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int
)
true
}
@ -1071,12 +1089,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val displays =
emulationActivity.secondaryDisplayManager.availableDisplays
if (selectedLayout == SecondaryDisplayLayout.NONE.int || !BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean) {
if (selectedLayout == SecondaryDisplayLayout.NONE.int ||
!BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
) {
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
enableSecondaryCheckbox.isChecked = false
chooserMenu.isVisible = false
popupMenu.menu.setGroupEnabled(R.id.menu_secondary_layout_group, false)
} else {
popupMenu.menu.setGroupEnabled(R.id.menu_secondary_layout_group, true)
chooserMenu.isVisible = (displays.size > 1)
@ -1144,37 +1163,51 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
R.id.menu_secondary_layout_opposite -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.REVERSE_PRIMARY.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.REVERSE_PRIMARY.int
)
true
}
R.id.menu_secondary_layout_top -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.TOP_SCREEN.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.TOP_SCREEN.int
)
true
}
R.id.menu_secondary_layout_bottom -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.BOTTOM_SCREEN.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.BOTTOM_SCREEN.int
)
true
}
R.id.menu_secondary_layout_side_by_side -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.SIDE_BY_SIDE.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.SIDE_BY_SIDE.int
)
true
}
R.id.menu_secondary_layout_hybrid -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.HYBRID.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.HYBRID.int
)
true
}
R.id.menu_secondary_layout_original -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.ORIGINAL.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.ORIGINAL.int
)
true
}
R.id.menu_secondary_layout_largescreen -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.LARGE_SCREEN.int)
screenAdjustmentUtil.changeSecondaryOrientation(
SecondaryDisplayLayout.LARGE_SCREEN.int
)
true
}
@ -1222,7 +1255,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.emulation_toggle_controls)
.setMultiChoiceItems(
R.array.n3dsButtons, enabledButtons
R.array.n3dsButtons,
enabledButtons
) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean ->
editor.putBoolean("buttonToggle$indexSelected", isChecked)
}
@ -1267,14 +1301,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
slider.addOnChangeListener(
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
Slider.OnChangeListener {
slider: Slider,
progress: Float,
_: Boolean
->
if (textValue.text.toString() != (slider.value + 50).toInt().toString()) {
textValue.setText((slider.value + 50).toInt().toString())
textValue.setSelection(textValue.length())
setControlScale(slider.value.toInt(), target)
}
})
}
)
textInput.suffixText = "%"
}
val previousProgress = sliderBinding.slider.value.toInt()
@ -1318,7 +1356,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
slider.addOnChangeListener { _: Slider, value: Float, _: Boolean ->
if (textValue.text.toString() != slider.value.toInt().toString()) {
@ -1451,7 +1488,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
(perfStats[TIME_SWAP] * 1000.0f).toFloat(),
(perfStats[TIME_IPC] * 1000.0f).toFloat(),
(perfStats[TIME_SVC] * 1000.0f).toFloat(),
(perfStats[TIME_REM] * 1000.0f).toFloat(),
(perfStats[TIME_REM] * 1000.0f).toFloat()
)
)
}
@ -1469,7 +1506,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
if (BooleanSetting.PERF_OVERLAY_SHOW_APP_RAM_USAGE.boolean) {
if (sb.isNotEmpty()) sb.append(dividerString)
val appRamUsage =
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 / 1000000
File("/proc/self/statm").readLines()[0].split(' ')[1].toLong() * 4096 /
1000000
sb.append("Process\u00A0RAM:\u00A0$appRamUsage\u00A0MB")
}
@ -1494,7 +1532,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
if (BooleanSetting.PERF_OVERLAY_BACKGROUND.boolean) {
binding.performanceOverlayShowText.setBackgroundResource(R.color.citra_transparent_black)
binding.performanceOverlayShowText.setBackgroundResource(
R.color.citra_transparent_black
)
} else {
binding.performanceOverlayShowText.setBackgroundResource(0)
}
@ -1558,10 +1598,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
}
private fun celsiusToFahrenheit(celsius: Float): Float {
return (celsius * 9 / 5) + 32
}
private fun celsiusToFahrenheit(celsius: Float): Float = (celsius * 9 / 5) + 32
override fun surfaceCreated(holder: SurfaceHolder) {
// We purposely don't do anything here.

View file

@ -59,7 +59,13 @@ class GamesFragment : Fragment() {
private var pendingCompressInvocation: String? = null
companion object {
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
fun doCompression(
fragment: Fragment,
gamesViewModel: GamesViewModel,
inputPath: String?,
outputUri: Uri?,
shouldCompress: Boolean
) {
if (outputUri != null) {
val outputPath: String =
if (!BuildUtil.isGooglePlayBuild) {
@ -247,41 +253,40 @@ class GamesFragment : Fragment() {
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
binding.gridGames.updatePadding(
top = barInsets.top + extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
)
binding.gridGames.updatePadding(
top = barInsets.top + extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
)
binding.swipeRefresh.setProgressViewEndTarget(
false,
barInsets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
)
binding.swipeRefresh.setProgressViewEndTarget(
false,
barInsets.top + resources.getDimensionPixelSize(R.dimen.spacing_refresh_end)
)
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpSwipe = binding.swipeRefresh.layoutParams as MarginLayoutParams
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
mlpSwipe.leftMargin = leftInsets + spacingNavigationRail
mlpSwipe.rightMargin = rightInsets
} else {
mlpSwipe.leftMargin = leftInsets
mlpSwipe.rightMargin = rightInsets + spacingNavigationRail
}
binding.swipeRefresh.layoutParams = mlpSwipe
binding.noticeText.updatePadding(bottom = spacingNavigation)
windowInsets
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpSwipe = binding.swipeRefresh.layoutParams as MarginLayoutParams
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
mlpSwipe.leftMargin = leftInsets + spacingNavigationRail
mlpSwipe.rightMargin = rightInsets
} else {
mlpSwipe.leftMargin = leftInsets
mlpSwipe.rightMargin = rightInsets + spacingNavigationRail
}
binding.swipeRefresh.layoutParams = mlpSwipe
binding.noticeText.updatePadding(bottom = spacingNavigation)
windowInsets
}
}

View file

@ -44,8 +44,6 @@ class GrantMissingFilesystemPermissionFragment : DialogFragment() {
{ permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) }
}
return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.filesystem_permission_warning)
.setMessage(R.string.filesystem_permission_lost)

View file

@ -32,19 +32,19 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.HomeSettingAdapter
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.SettingKeys
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.model.HomeSetting
import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.utils.GameHelper
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.viewmodel.HomeViewModel
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.viewmodel.DriverViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
class HomeSettingsFragment : Fragment() {
private var _binding: FragmentHomeSettingsBinding? = null
@ -89,7 +89,10 @@ class HomeSettingsFragment : Fragment() {
{
val inflater = LayoutInflater.from(context)
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
var textInputValue: String = preferences.getString(
SettingKeys.last_artic_base_addr(),
""
)!!
inputBinding.editTextInput.setText(textInputValue)
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
@ -103,7 +106,10 @@ class HomeSettingsFragment : Fragment() {
.setPositiveButton(android.R.string.ok) { _, _ ->
if (textInputValue.isNotEmpty()) {
preferences.edit()
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
.putString(
SettingKeys.last_artic_base_addr(),
textInputValue
)
.apply()
val menu = Game(
title = getString(R.string.artic_base),
@ -115,7 +121,7 @@ class HomeSettingsFragment : Fragment() {
binding.root.findNavController().navigate(action)
}
}
.setNegativeButton(android.R.string.cancel) {_, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
}
@ -267,37 +273,36 @@ class HomeSettingsFragment : Fragment() {
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
binding.scrollViewSettings.updatePadding(
top = barInsets.top,
bottom = barInsets.bottom
)
binding.scrollViewSettings.updatePadding(
top = barInsets.top,
bottom = barInsets.bottom
)
val mlpScrollSettings = binding.scrollViewSettings.layoutParams as MarginLayoutParams
mlpScrollSettings.leftMargin = leftInsets
mlpScrollSettings.rightMargin = rightInsets
binding.scrollViewSettings.layoutParams = mlpScrollSettings
val mlpScrollSettings = binding.scrollViewSettings.layoutParams as MarginLayoutParams
mlpScrollSettings.leftMargin = leftInsets
mlpScrollSettings.rightMargin = rightInsets
binding.scrollViewSettings.layoutParams = mlpScrollSettings
binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation)
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
} else {
binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
}
windowInsets
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail)
} else {
binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail)
}
windowInsets
}
}

View file

@ -56,9 +56,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return binding.root
}
): View = binding.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

View file

@ -78,21 +78,27 @@ class KeyboardDialogFragment : DialogFragment() {
return@setOnClickListener
}
dismiss()
synchronized(SoftwareKeyboard.finishLock) { SoftwareKeyboard.finishLock.notifyAll() }
synchronized(SoftwareKeyboard.finishLock) {
SoftwareKeyboard.finishLock.notifyAll()
}
}
}
if (alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL) != null) {
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener {
SoftwareKeyboard.data.button = 1
dismiss()
synchronized(SoftwareKeyboard.finishLock) { SoftwareKeyboard.finishLock.notifyAll() }
synchronized(SoftwareKeyboard.finishLock) {
SoftwareKeyboard.finishLock.notifyAll()
}
}
}
if (alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener {
SoftwareKeyboard.data.button = 0
dismiss()
synchronized(SoftwareKeyboard.finishLock) { SoftwareKeyboard.finishLock.notifyAll() }
synchronized(SoftwareKeyboard.finishLock) {
SoftwareKeyboard.finishLock.notifyAll()
}
}
}

View file

@ -57,9 +57,7 @@ class LicenseBottomSheetDialogFragment : BottomSheetDialogFragment() {
const val LICENSE = "License"
fun newInstance(
license: License
): LicenseBottomSheetDialogFragment {
fun newInstance(license: License): LicenseBottomSheetDialogFragment {
val dialog = LicenseBottomSheetDialogFragment()
val bundle = Bundle()
bundle.putParcelable(LICENSE, license)

View file

@ -174,28 +174,27 @@ class LicensesFragment : Fragment() {
setInsets()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val leftInsets = barInsets.left + cutoutInsets.left
val rightInsets = barInsets.right + cutoutInsets.right
val mlpAppBar = binding.toolbarLicenses.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarLicenses.layoutParams = mlpAppBar
val mlpAppBar = binding.toolbarLicenses.layoutParams as MarginLayoutParams
mlpAppBar.leftMargin = leftInsets
mlpAppBar.rightMargin = rightInsets
binding.toolbarLicenses.layoutParams = mlpAppBar
val mlpScrollAbout = binding.listLicenses.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.listLicenses.layoutParams = mlpScrollAbout
val mlpScrollAbout = binding.listLicenses.layoutParams as MarginLayoutParams
mlpScrollAbout.leftMargin = leftInsets
mlpScrollAbout.rightMargin = rightInsets
binding.listLicenses.layoutParams = mlpScrollAbout
binding.listLicenses.updatePadding(bottom = barInsets.bottom)
binding.listLicenses.updatePadding(bottom = barInsets.bottom)
windowInsets
}
windowInsets
}
}

View file

@ -23,11 +23,22 @@ class MiiSelectorDialogFragment : DialogFragment() {
list.add(getString(R.string.standard_mii))
list.addAll(config.miiNames)
val initialIndex =
if (config.initiallySelectedMiiIndex < list.size) config.initiallySelectedMiiIndex.toInt() else 0
if (config.initiallySelectedMiiIndex <
list.size
) {
config.initiallySelectedMiiIndex.toInt()
} else {
0
}
MiiSelector.data.index = initialIndex
val builder = MaterialAlertDialogBuilder(requireActivity())
.setTitle(if (config.title!!.isEmpty()) getString(R.string.mii_selector) else config.title)
.setSingleChoiceItems(list.toTypedArray(), initialIndex) { _: DialogInterface?, which: Int ->
.setTitle(
if (config.title!!.isEmpty()) getString(R.string.mii_selector) else config.title
)
.setSingleChoiceItems(list.toTypedArray(), initialIndex) {
_: DialogInterface?,
which: Int
->
MiiSelector.data.index = which
}
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->

View file

@ -14,11 +14,11 @@ import android.view.View
import android.view.ViewGroup
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlin.math.abs
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.DialogInputBinding
import org.citra.citra_emu.features.settings.model.view.InputBindingSetting
import org.citra.citra_emu.utils.Log
import kotlin.math.abs
class MotionBottomSheetDialogFragment : BottomSheetDialogFragment() {
private var _binding: DialogInputBinding? = null

View file

@ -28,19 +28,19 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import java.time.temporal.ChronoField
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter
import org.citra.citra_emu.databinding.FragmentSearchBinding
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
import java.time.temporal.ChronoField
import java.util.Locale
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
@ -61,7 +61,13 @@ class SearchFragment : Fragment() {
private val onCompressDecompressLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri: Uri? ->
GamesFragment.doCompression(this, gamesViewModel, pendingCompressInvocation, uri, shouldCompress)
GamesFragment.doCompression(
this,
gamesViewModel,
pendingCompressInvocation,
uri,
shouldCompress
)
pendingCompressInvocation = null
}
@ -184,14 +190,16 @@ class SearchFragment : Fragment() {
R.id.chip_recently_played -> {
baseList.filter {
val lastPlayedTime = preferences.getLong(it.keyLastPlayedTime, 0L)
lastPlayedTime > (System.currentTimeMillis() - ChronoField.MILLI_OF_DAY.range().maximum)
lastPlayedTime >
(System.currentTimeMillis() - ChronoField.MILLI_OF_DAY.range().maximum)
}
}
R.id.chip_recently_added -> {
baseList.filter {
val addedTime = preferences.getLong(it.keyAddedToLibraryTime, 0L)
addedTime > (System.currentTimeMillis() - ChronoField.MILLI_OF_DAY.range().maximum)
addedTime >
(System.currentTimeMillis() - ChronoField.MILLI_OF_DAY.range().maximum)
}
}
@ -242,54 +250,53 @@ class SearchFragment : Fragment() {
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
val spacingNavigationRail =
resources.getDimensionPixelSize(R.dimen.spacing_navigation_rail)
val chipSpacing = resources.getDimensionPixelSize(R.dimen.spacing_chip)
binding.constraintSearch.updatePadding(
left = barInsets.left + cutoutInsets.left,
top = barInsets.top,
right = barInsets.right + cutoutInsets.right
binding.constraintSearch.updatePadding(
left = barInsets.left + cutoutInsets.left,
top = barInsets.top,
right = barInsets.right + cutoutInsets.right
)
binding.gridGamesSearch.updatePadding(
top = extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
)
binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom)
val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.frameSearch.updatePadding(left = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(left = spacingNavigationRail)
binding.noResultsView.updatePadding(left = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = chipSpacing + spacingNavigationRail,
right = chipSpacing
)
binding.gridGamesSearch.updatePadding(
top = extraListSpacing,
bottom = barInsets.bottom + spacingNavigation + extraListSpacing
mlpDivider.leftMargin = chipSpacing + spacingNavigationRail
mlpDivider.rightMargin = chipSpacing
} else {
binding.frameSearch.updatePadding(right = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(right = spacingNavigationRail)
binding.noResultsView.updatePadding(right = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = chipSpacing,
right = chipSpacing + spacingNavigationRail
)
binding.noResultsView.updatePadding(bottom = spacingNavigation + barInsets.bottom)
val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
binding.frameSearch.updatePadding(left = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(left = spacingNavigationRail)
binding.noResultsView.updatePadding(left = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = chipSpacing + spacingNavigationRail,
right = chipSpacing
)
mlpDivider.leftMargin = chipSpacing + spacingNavigationRail
mlpDivider.rightMargin = chipSpacing
} else {
binding.frameSearch.updatePadding(right = spacingNavigationRail)
binding.gridGamesSearch.updatePadding(right = spacingNavigationRail)
binding.noResultsView.updatePadding(right = spacingNavigationRail)
binding.chipGroup.updatePadding(
left = chipSpacing,
right = chipSpacing + spacingNavigationRail
)
mlpDivider.leftMargin = chipSpacing
mlpDivider.rightMargin = chipSpacing + spacingNavigationRail
}
binding.divider.layoutParams = mlpDivider
windowInsets
mlpDivider.leftMargin = chipSpacing
mlpDivider.rightMargin = chipSpacing + spacingNavigationRail
}
binding.divider.layoutParams = mlpDivider
windowInsets
}
}

View file

@ -16,11 +16,15 @@ import org.citra.citra_emu.ui.main.MainActivity
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.viewmodel.HomeViewModel
class SelectUserDirectoryDialogFragment(titleOverride: Int? = null, descriptionOverride: Int? = null) : DialogFragment() {
class SelectUserDirectoryDialogFragment(
titleOverride: Int? = null,
descriptionOverride: Int? = null
) : DialogFragment() {
private lateinit var mainActivity: MainActivity
private val title = titleOverride ?: R.string.select_citra_user_folder
private val description = descriptionOverride ?: R.string.selecting_user_directory_without_write_permissions
private val description =
descriptionOverride ?: R.string.selecting_user_directory_without_write_permissions
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
mainActivity = requireActivity() as MainActivity
@ -31,7 +35,9 @@ class SelectUserDirectoryDialogFragment(titleOverride: Int? = null, descriptionO
.setTitle(title)
.setMessage(description)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
PermissionsHandler.compatibleSelectDirectory(mainActivity.openCitraDirectoryLostPermission)
PermissionsHandler.compatibleSelectDirectory(
mainActivity.openCitraDirectoryLostPermission
)
}
.show()
}
@ -39,8 +45,11 @@ class SelectUserDirectoryDialogFragment(titleOverride: Int? = null, descriptionO
companion object {
const val TAG = "SelectUserDirectoryDialogFragment"
fun newInstance(activity: FragmentActivity, titleOverride: Int? = null, descriptionOverride: Int? = null):
SelectUserDirectoryDialogFragment {
fun newInstance(
activity: FragmentActivity,
titleOverride: Int? = null,
descriptionOverride: Int? = null
): SelectUserDirectoryDialogFragment {
ViewModelProvider(activity)[HomeViewModel::class.java].setPickingUserDir(true)
return SelectUserDirectoryDialogFragment(titleOverride, descriptionOverride)
}

View file

@ -162,7 +162,9 @@ class SetupFragment : Fragment() {
)
)
} else {
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
permissionLauncher.launch(
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
},
buttonState = {
@ -187,7 +189,7 @@ class SetupFragment : Fragment() {
isUnskippable = true,
hasWarning = true,
R.string.filesystem_permission_warning,
R.string.filesystem_permission_warning_description,
R.string.filesystem_permission_warning_description
)
)
}
@ -199,7 +201,9 @@ class SetupFragment : Fragment() {
R.string.notifications_description,
buttonAction = {
pageButtonCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
permissionLauncher.launch(
Manifest.permission.POST_NOTIFICATIONS
)
},
buttonState = {
if (NotificationManagerCompat.from(requireContext())
@ -213,7 +217,7 @@ class SetupFragment : Fragment() {
isUnskippable = false,
hasWarning = true,
R.string.notification_warning,
R.string.notification_warning_description,
R.string.notification_warning_description
)
)
}
@ -236,7 +240,7 @@ class SetupFragment : Fragment() {
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
}
)
)
add(
@ -258,10 +262,10 @@ class SetupFragment : Fragment() {
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
}
},
}
)
)
},
}
) {
var permissionsComplete =
// Microphone
@ -269,14 +273,14 @@ class SetupFragment : Fragment() {
requireContext(),
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED &&
// Camera
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED &&
// Notifications
NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
// Camera
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED &&
// Notifications
NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
// External Storage
if (!BuildUtil.isGooglePlayBuild) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@ -284,10 +288,12 @@ class SetupFragment : Fragment() {
(permissionsComplete && Environment.isExternalStorageManager())
} else {
permissionsComplete =
(permissionsComplete && ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED)
(
permissionsComplete && ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
)
}
}
@ -315,7 +321,9 @@ class SetupFragment : Fragment() {
R.string.select_citra_user_folder_description,
buttonAction = {
pageButtonCallback = it
PermissionsHandler.compatibleSelectDirectory(mainActivity.setupOpenCitraDirectory)
PermissionsHandler.compatibleSelectDirectory(
mainActivity.setupOpenCitraDirectory
)
},
buttonState = {
if (PermissionsHandler.hasWriteAccess(requireContext())) {
@ -344,7 +352,11 @@ class SetupFragment : Fragment() {
)
},
buttonState = {
if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
if (preferences.getString(
GameHelper.KEY_GAME_PATH,
""
)!!.isNotEmpty()
) {
ButtonState.BUTTON_ACTION_COMPLETE
} else {
ButtonState.BUTTON_ACTION_INCOMPLETE
@ -353,17 +365,16 @@ class SetupFragment : Fragment() {
isUnskippable = false,
hasWarning = true,
R.string.add_games_warning,
R.string.add_games_warning_description,
R.string.add_games_warning_description
)
)
},
}
) {
if (
PermissionsHandler.hasWriteAccess(requireContext()) &&
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
) {
PageState.PAGE_STEPS_COMPLETE
} else {
PageState.PAGE_STEPS_INCOMPLETE
}
@ -483,7 +494,8 @@ class SetupFragment : Fragment() {
if (savedInstanceState == null) {
hasBeenWarned = BooleanArray(pages.size)
} else {
hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED) ?: BooleanArray(pages.size)
hasBeenWarned =
savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED) ?: BooleanArray(pages.size)
}
updateNavigationButtons(binding.viewPager2.currentItem)
@ -590,8 +602,11 @@ class SetupFragment : Fragment() {
}
}
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(result,
null, checkForButtonState)
CitraDirectoryHelper(requireActivity(), true).showCitraDirectoryDialog(
result,
null,
checkForButtonState
)
}
private fun onGetGamesDirectory(result: Uri) {
@ -632,33 +647,32 @@ class SetupFragment : Fragment() {
hasBeenWarned[page] = true
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftPadding = barInsets.left + cutoutInsets.left
val topPadding = barInsets.top + cutoutInsets.top
val rightPadding = barInsets.right + cutoutInsets.right
val bottomPadding = barInsets.bottom + cutoutInsets.bottom
val leftPadding = barInsets.left + cutoutInsets.left
val topPadding = barInsets.top + cutoutInsets.top
val rightPadding = barInsets.right + cutoutInsets.right
val bottomPadding = barInsets.bottom + cutoutInsets.bottom
if (resources.getBoolean(R.bool.small_layout)) {
binding.viewPager2
.updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
binding.constraintButtons
.updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
} else {
binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
binding.constraintButtons
.setPadding(
leftPadding + rightPadding,
topPadding,
rightPadding + leftPadding,
bottomPadding
)
}
windowInsets
if (resources.getBoolean(R.bool.small_layout)) {
binding.viewPager2
.updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
binding.constraintButtons
.updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
} else {
binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
binding.constraintButtons
.setPadding(
leftPadding + rightPadding,
topPadding,
rightPadding + leftPadding,
bottomPadding
)
}
windowInsets
}
}

View file

@ -38,8 +38,8 @@ import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
import org.citra.citra_emu.databinding.FragmentSystemFilesBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.SettingKeys
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.viewmodel.GamesViewModel
@ -163,10 +163,12 @@ class SystemFilesFragment : Fragment() {
binding.buttonUnlinkConsoleData.setOnClickListener {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.delete_system_files)
.setMessage(HtmlCompat.fromHtml(
requireContext().getString(R.string.delete_system_files_description),
HtmlCompat.FROM_HTML_MODE_COMPACT
))
.setMessage(
HtmlCompat.fromHtml(
requireContext().getString(R.string.delete_system_files_description),
HtmlCompat.FROM_HTML_MODE_COMPACT
)
)
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
NativeLibrary.unlinkConsole()
binding.buttonUnlinkConsoleData.isEnabled = NativeLibrary.isFullConsoleLinked()
@ -178,7 +180,10 @@ class SystemFilesFragment : Fragment() {
binding.buttonSetUpSystemFiles.setOnClickListener {
val inflater = LayoutInflater.from(context)
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
var textInputValue: String = preferences.getString(SettingKeys.last_artic_base_addr(), "")!!
var textInputValue: String = preferences.getString(
SettingKeys.last_artic_base_addr(),
""
)!!
val progressDialog = showProgressDialog(
getText(R.string.setup_system_files),
@ -273,9 +278,14 @@ class SystemFilesFragment : Fragment() {
.setView(inputBinding.root)
.setTitle(getString(R.string.setup_system_files_enter_address))
.setPositiveButton(android.R.string.ok) { diag, _ ->
if (textInputValue.isNotEmpty() && !(!buttonO3ds.isChecked && !buttonN3ds.isChecked)) {
if (textInputValue.isNotEmpty() &&
!(!buttonO3ds.isChecked && !buttonN3ds.isChecked)
) {
preferences.edit()
.putString(SettingKeys.last_artic_base_addr(), textInputValue)
.putString(
SettingKeys.last_artic_base_addr(),
textInputValue
)
.apply()
val menu = Game(
title = getString(R.string.artic_base),

View file

@ -37,8 +37,8 @@ class UpdateUserDirectoryDialogFragment : DialogFragment() {
isCancelable = false
val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
val ld = preferences.getString("LIME3DS_DIRECTORY","")
val cd = preferences.getString("CITRA_DIRECTORY","")
val ld = preferences.getString("LIME3DS_DIRECTORY", "")
val cd = preferences.getString("CITRA_DIRECTORY", "")
val dialogView = LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_select_which_directory, null)
@ -97,7 +97,12 @@ class UpdateUserDirectoryDialogFragment : DialogFragment() {
}
ViewModelProvider(mainActivity)[HomeViewModel::class.java].setPickingUserDir(false)
ViewModelProvider(mainActivity)[HomeViewModel::class.java].setUserDir(this.requireActivity(),PermissionsHandler.citraDirectory.path!!)
ViewModelProvider(
mainActivity
)[HomeViewModel::class.java].setUserDir(
this.requireActivity(),
PermissionsHandler.citraDirectory.path!!
)
}
.show()
}

View file

@ -4,9 +4,9 @@
package org.citra.citra_emu.model
import android.os.Parcelable
import android.content.Intent
import android.net.Uri
import android.os.Parcelable
import androidx.core.net.toUri
import java.io.File
import java.io.IOException
@ -36,7 +36,7 @@ class Game(
val icon: IntArray? = null,
val fileType: String = "",
val isCompressed: Boolean = false,
val filename: String,
val filename: String
) : Parcelable {
val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime"
val keyLastPlayedTime get() = "${filename}_LastPlayed"
@ -51,7 +51,9 @@ class Game(
val nativePath = NativeLibrary.getUserDirectory() + "/" + path
val nativeFile = File(nativePath)
if (!nativeFile.exists()) {
throw IOException("Attempting to create shortcut for an executable that doesn't exist: $nativePath")
throw IOException(
"Attempting to create shortcut for an executable that doesn't exist: $nativePath"
)
}
appUri = Uri.fromFile(nativeFile)
}
@ -89,9 +91,7 @@ class Game(
GAME_CARD(2);
companion object {
fun fromInt(value: Int): MediaType? {
return MediaType.entries.find { it.value == value }
}
fun fromInt(value: Int): MediaType? = MediaType.entries.find { it.value == value }
}
}

View file

@ -12,7 +12,7 @@ data class SetupPage(
val leftAlignedIcon: Boolean,
val buttonTextId: Int,
val pageButtons: List<PageButton>? = null,
val pageSteps: () -> PageState = { PageState.PAGE_STEPS_UNDEFINED },
val pageSteps: () -> PageState = { PageState.PAGE_STEPS_UNDEFINED }
)
data class PageButton(
@ -29,7 +29,7 @@ data class PageButton(
)
interface SetupCallback {
fun onStepCompleted(pageButtonId : Int, pageFullyCompleted: Boolean)
fun onStepCompleted(pageButtonId: Int, pageFullyCompleted: Boolean)
}
enum class PageState {

View file

@ -21,13 +21,13 @@ import android.view.View
import android.view.View.OnTouchListener
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import java.lang.NullPointerException
import kotlin.math.min
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.utils.TurboHelper
import java.lang.NullPointerException
import kotlin.math.min
/**
* Draws the interactive input overlay on top of the
@ -36,7 +36,8 @@ import kotlin.math.min
* @param context The current [Context].
* @param attrs [AttributeSet] for parsing XML attributes.
*/
class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs),
class InputOverlay(context: Context?, attrs: AttributeSet?) :
SurfaceView(context, attrs),
OnTouchListener {
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
@ -87,9 +88,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
)
}
fun hapticFeedback(type:Int){
if(EmulationMenuSettings.hapticFeedback)
fun hapticFeedback(type: Int) {
if (EmulationMenuSettings.hapticFeedback) {
performHapticFeedback(type)
}
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
@ -138,7 +140,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
}
var hasActiveJoystick = false
if(!hasActiveButtons && !hasActiveDpad){
if (!hasActiveButtons && !hasActiveDpad) {
for (joystick in overlayJoysticks) {
if (joystick.trackId == pointerId) {
hasActiveJoystick = true
@ -161,18 +163,26 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
var anyOverlayStateChanged = false
var shouldUpdateView = false
if(!hasActiveDpad && !hasActiveJoystick) {
if (!hasActiveDpad && !hasActiveJoystick) {
for (button in overlayButtons) {
val stateChanged = button.updateStatus(event, pointerIndex, hasActiveButtons, this)
val stateChanged = button.updateStatus(
event,
pointerIndex,
hasActiveButtons,
this
)
if (!stateChanged) {
continue
}
anyOverlayStateChanged = true
if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP && button.status == NativeLibrary.ButtonState.PRESSED) {
if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP &&
button.status == NativeLibrary.ButtonState.PRESSED
) {
swapScreen()
}
else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
} else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO &&
button.status == NativeLibrary.ButtonState.PRESSED
) {
TurboHelper.toggleTurbo(true)
}
@ -186,7 +196,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
}
}
if(!hasActiveButtons && !hasActiveJoystick) {
if (!hasActiveButtons && !hasActiveJoystick) {
for (dpad in overlayDpads) {
val stateChanged = dpad.updateStatus(
event,
@ -225,9 +235,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
}
}
if(!hasActiveDpad && !hasActiveButtons) {
if (!hasActiveDpad && !hasActiveButtons) {
for (joystick in overlayJoysticks) {
val stateChanged = joystick.updateStatus(event, pointerIndex, hasActiveJoystick, this)
val stateChanged = joystick.updateStatus(
event,
pointerIndex,
hasActiveJoystick,
this
)
if (!stateChanged) {
continue
}
@ -281,7 +296,6 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
if (!isActionMove) {
break
}
}
return true
}
@ -291,7 +305,13 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
val fingerPositionX = event.getX(pointerIndex).toInt()
val fingerPositionY = event.getY(pointerIndex).toInt()
val orientation =
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) "-Portrait" else ""
if (resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
) {
"-Portrait"
} else {
""
}
// Maybe combine Button and Joystick as subclasses of the same parent?
// Or maybe create an interface like IMoveableHUDControl?
@ -313,12 +333,15 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
return true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured == it) {
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured ==
it
) {
// Persist button position by saving new place.
saveControlPosition(
buttonBeingConfigured!!.id,
buttonBeingConfigured!!.bounds.left,
buttonBeingConfigured!!.bounds.top, orientation
buttonBeingConfigured!!.bounds.top,
orientation
)
buttonBeingConfigured = null
}
@ -347,7 +370,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
// Persist button position by saving new place.
saveControlPosition(
dpadBeingConfigured!!.upId,
dpadBeingConfigured!!.bounds.left, dpadBeingConfigured!!.bounds.top,
dpadBeingConfigured!!.bounds.left,
dpadBeingConfigured!!.bounds.top,
orientation
)
dpadBeingConfigured = null
@ -374,7 +398,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
saveControlPosition(
joystickBeingConfigured!!.joystickId,
joystickBeingConfigured!!.bounds.left,
joystickBeingConfigured!!.bounds.top, orientation
joystickBeingConfigured!!.bounds.top,
orientation
)
joystickBeingConfigured = null
}
@ -935,9 +960,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
.apply()
}
override fun isInEditMode(): Boolean {
return isInEditMode
}
override fun isInEditMode(): Boolean = isInEditMode
companion object {
private val preferences
@ -1044,6 +1067,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
NativeLibrary.ButtonType.BUTTON_START,
NativeLibrary.ButtonType.BUTTON_SELECT,
NativeLibrary.ButtonType.BUTTON_SWAP -> 0.08f
NativeLibrary.ButtonType.BUTTON_TURBO -> 0.10f
NativeLibrary.ButtonType.TRIGGER_L,
@ -1056,17 +1080,22 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
scale *= (preferences.getInt("controlScale", 50) + 50).toFloat()
scale /= 100f
scale *= (preferences.getInt("controlScale-$buttonId", 50) + 50).toFloat()
scale /= 100f
val opacity: Int = preferences.getInt("controlOpacity", 50) * 255 / 100
// Initialize the InputOverlayDrawableButton.
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
val pressedStateBitmap = getBitmap(context, pressedResId, scale)
val overlayDrawable =
InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId, opacity)
InputOverlayDrawableButton(
res,
defaultStateBitmap,
pressedStateBitmap,
buttonId,
opacity
)
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
// These were set in the input overlay configuration menu.
@ -1118,19 +1147,22 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
scale *= (preferences.getInt("controlScale", 50) + 50).toFloat()
scale /= 100f
scale *= (preferences.getInt(
"controlScale-" + NativeLibrary.ButtonType.DPAD,
50
) + 50).toFloat()
scale *= (
preferences.getInt(
"controlScale-" + NativeLibrary.ButtonType.DPAD,
50
) + 50
).toFloat()
scale /= 100f
val opacity: Int = preferences.getInt("controlOpacity", 50) * 255 / 100
// Initialize the InputOverlayDrawableDpad.
val defaultStateBitmap = getBitmap(context, defaultResId, scale)
val pressedOneDirectionStateBitmap = getBitmap(context, pressedOneDirectionResId, scale)
val pressedTwoDirectionsStateBitmap = getBitmap(context, pressedTwoDirectionsResId, scale)
val pressedTwoDirectionsStateBitmap =
getBitmap(context, pressedTwoDirectionsResId, scale)
val overlayDrawable = InputOverlayDrawableDpad(
res,
defaultStateBitmap,

View file

@ -70,7 +70,12 @@ class InputOverlayDrawableButton(
*
* @return true if value was changed
*/
fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
fun updateStatus(
event: MotionEvent,
pointerIndex: Int,
hasActiveButtons: Boolean,
overlay: InputOverlay
): Boolean {
val buttonSliding = EmulationMenuSettings.buttonSlide
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()

View file

@ -63,7 +63,13 @@ class InputOverlayDrawableDpad(
trackId = -1
}
fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, dpadSlide: Boolean, overlay: InputOverlay): Boolean {
fun updateStatus(
event: MotionEvent,
pointerIndex: Int,
hasActiveButtons: Boolean,
dpadSlide: Boolean,
overlay: InputOverlay
): Boolean {
var isDown = false
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
@ -125,12 +131,16 @@ class InputOverlayDrawableDpad(
leftButtonState = xAxis < -VIRT_AXIS_DEADZONE
rightButtonState = xAxis > VIRT_AXIS_DEADZONE
val stateChanged = upState != upButtonState || downState != downButtonState || leftState != leftButtonState || rightState != rightButtonState
val stateChanged =
upState != upButtonState || downState != downButtonState ||
leftState != leftButtonState ||
rightState != rightButtonState
if(stateChanged)
if (stateChanged) {
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
else if(isDown)
} else if (isDown) {
overlay.hapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
}
return stateChanged
}
@ -259,7 +269,9 @@ class InputOverlayDrawableDpad(
controlPositionX += fingerPositionX - previousTouchX
controlPositionY += fingerPositionY - previousTouchY
setBounds(
controlPositionX, controlPositionY, width + controlPositionX,
controlPositionX,
controlPositionY,
width + controlPositionX,
height + controlPositionY
)
previousTouchX = fingerPositionX

View file

@ -11,12 +11,12 @@ import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.EmulationMenuSettings
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.EmulationMenuSettings
/**
* Custom [BitmapDrawable] that is capable
@ -93,7 +93,12 @@ class InputOverlayDrawableJoystick(
currentStateBitmapDrawable.draw(canvas)
}
fun updateStatus(event: MotionEvent, pointerIndex: Int, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
fun updateStatus(
event: MotionEvent,
pointerIndex: Int,
hasActiveButtons: Boolean,
overlay: InputOverlay
): Boolean {
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex)
@ -171,8 +176,9 @@ class InputOverlayDrawableJoystick(
this.yAxis = sin(angle.toDouble()).toFloat() * radius
setInnerBounds()
if (kotlin.math.abs(oldRadius - radius) > .34f
|| radius > .5f && kotlin.math.abs(oldAngle - angle) > kotlin.math.PI / 8) {
if (kotlin.math.abs(oldRadius - radius) > .34f ||
radius > .5f && kotlin.math.abs(oldAngle - angle) > kotlin.math.PI / 8
) {
this.radius = radius
this.angle = angle
@ -237,14 +243,22 @@ class InputOverlayDrawableJoystick(
private fun setInnerBounds() {
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
if (x > virtBounds.centerX() + virtBounds.width() / 2) x =
virtBounds.centerX() + virtBounds.width() / 2
if (x < virtBounds.centerX() - virtBounds.width() / 2) x =
virtBounds.centerX() - virtBounds.width() / 2
if (y > virtBounds.centerY() + virtBounds.height() / 2) y =
virtBounds.centerY() + virtBounds.height() / 2
if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
virtBounds.centerY() - virtBounds.height() / 2
if (x > virtBounds.centerX() + virtBounds.width() / 2) {
x =
virtBounds.centerX() + virtBounds.width() / 2
}
if (x < virtBounds.centerX() - virtBounds.width() / 2) {
x =
virtBounds.centerX() - virtBounds.width() / 2
}
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
y =
virtBounds.centerY() + virtBounds.height() / 2
}
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
y =
virtBounds.centerY() - virtBounds.height() / 2
}
val width = pressedStateInnerBitmap.bounds.width() / 2
val height = pressedStateInnerBitmap.bounds.height() / 2
defaultStateInnerBitmap.setBounds(x - width, y - height, x + width, y + height)

View file

@ -61,13 +61,15 @@ import org.citra.citra_emu.utils.CitraDirectoryUtils
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.FileBrowserHelper
import org.citra.citra_emu.utils.InsetsHelper
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.RefreshRateUtil
import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
class MainActivity : AppCompatActivity(), ThemeProvider {
class MainActivity :
AppCompatActivity(),
ThemeProvider {
private lateinit var binding: ActivityMainBinding
private val homeViewModel: HomeViewModel by viewModels()
@ -87,14 +89,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
CitraDirectoryUtils.attemptAutomaticUpdateDirectory()
splashScreen.setKeepOnScreenCondition {
!DirectoryInitialization.areCitraDirectoriesReady() &&
PermissionsHandler.hasWriteAccess(this) &&
!CitraDirectoryUtils.needToUpdateManually()
PermissionsHandler.hasWriteAccess(this) &&
!CitraDirectoryUtils.needToUpdateManually()
}
if (PermissionsHandler.hasWriteAccess(applicationContext) &&
DirectoryInitialization.areCitraDirectoriesReady() &&
!CitraDirectoryUtils.needToUpdateManually()) {
!CitraDirectoryUtils.needToUpdateManually()
) {
settingsViewModel.settings.loadSettings()
}
@ -152,7 +154,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
gamesViewModel.setShouldScrollToTop(true)
}
R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
R.id.homeSettingsFragment -> SettingsActivity.launch(
this,
SettingsFile.FILE_NAME_CONFIG,
@ -229,7 +233,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
GrantMissingFilesystemPermissionFragment.newInstance()
.show(supportFragmentManager, GrantMissingFilesystemPermissionFragment.TAG)
if (supportFragmentManager.findFragmentByTag(GrantMissingFilesystemPermissionFragment.TAG) == null) {
if (supportFragmentManager.findFragmentByTag(
GrantMissingFilesystemPermissionFragment.TAG
) ==
null
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
requestMissingFilesystemPermission()
@ -256,12 +264,14 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return
} else if (CitraDirectoryUtils.needToUpdateManually()) {
UpdateUserDirectoryDialogFragment.newInstance(this)
.show(supportFragmentManager,UpdateUserDirectoryDialogFragment.TAG)
.show(supportFragmentManager, UpdateUserDirectoryDialogFragment.TAG)
return
}
if (!BuildUtil.isGooglePlayBuild) {
if (supportFragmentManager.findFragmentByTag(SelectUserDirectoryDialogFragment.TAG) == null) {
if (supportFragmentManager.findFragmentByTag(SelectUserDirectoryDialogFragment.TAG) ==
null
) {
if (NativeLibrary.getUserDirectory() == "") {
SelectUserDirectoryDialogFragment.newInstance(this)
.show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG)
@ -365,28 +375,29 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}.start()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
mlpStatusShade.height = insets.top
binding.statusBarShade.layoutParams = mlpStatusShade
private fun setInsets() = ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
mlpStatusShade.height = insets.top
binding.statusBarShade.layoutParams = mlpStatusShade
// The only situation where we care to have a nav bar shade is when it's at the bottom
// of the screen where scrolling list elements can go behind it.
val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
mlpNavShade.height = insets.bottom
binding.navigationBarShade.layoutParams = mlpNavShade
// The only situation where we care to have a nav bar shade is when it's at the bottom
// of the screen where scrolling list elements can go behind it.
val mlpNavShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
mlpNavShade.height = insets.bottom
binding.navigationBarShade.layoutParams = mlpNavShade
windowInsets
}
windowInsets
}
private fun createOpenCitraDirectoryLauncher(
permissionsLost: Boolean
): ActivityResultLauncher<Uri?> {
return registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result: Uri? ->
return registerForActivityResult(
ActivityResultContracts.OpenDocumentTree()
) { result: Uri? ->
if (result == null) {
return@registerForActivityResult
}
@ -427,7 +438,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val workManager = WorkManager.getInstance(applicationContext)
workManager.enqueueUniqueWork(
"installCiaWork", ExistingWorkPolicy.APPEND_OR_REPLACE,
"installCiaWork",
ExistingWorkPolicy.APPEND_OR_REPLACE,
OneTimeWorkRequest.Builder(CiaInstallWorker::class.java)
.setInputData(
Data.Builder().putStringArray("CIA_FILES", selectedFiles)
@ -439,7 +451,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
val setupOpenCitraDirectory = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
ActivityResultContracts.OpenDocumentTree()
) { result: Uri? ->
homeViewModel.selectedCitraDirectory = result
}

View file

@ -9,6 +9,7 @@ import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.net.toUri
import androidx.work.ForegroundInfo
import androidx.work.Worker
import androidx.work.WorkerParameters
@ -16,12 +17,8 @@ import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.NativeLibrary.InstallStatus
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.FileUtil.getFilename
import androidx.core.net.toUri
class CiaInstallWorker(
val context: Context,
params: WorkerParameters
) : Worker(context, params) {
class CiaInstallWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
private val GROUP_KEY_CIA_INSTALL_STATUS = "org.citra.citra_emu.CIA_INSTALL_STATUS"
private var lastNotifiedTime: Long = 0
private val SUMMARY_NOTIFICATION_ID = 0xC1A0000
@ -123,7 +120,8 @@ class CiaInstallWorker(
val selectedFiles = inputData.getStringArray("CIA_FILES")!!
val toastText: CharSequence = context.resources.getQuantityString(
R.plurals.cia_install_toast,
selectedFiles.size, selectedFiles.size
selectedFiles.size,
selectedFiles.size
)
context.mainExecutor.execute {
Toast.makeText(context, toastText, Toast.LENGTH_LONG).show()

View file

@ -16,8 +16,15 @@ import org.citra.citra_emu.viewmodel.HomeViewModel
/**
* Citra directory initialization ui flow controller.
*/
class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity, private val lostPermission: Boolean) {
fun showCitraDirectoryDialog(result: Uri, callback: SetupCallback? = null, buttonState: () -> Unit) {
class CitraDirectoryHelper(
private val fragmentActivity: FragmentActivity,
private val lostPermission: Boolean
) {
fun showCitraDirectoryDialog(
result: Uri,
callback: SetupCallback? = null,
buttonState: () -> Unit
) {
val citraDirectoryDialog = CitraDirectoryDialogFragment.newInstance(
fragmentActivity,
result.toString(),
@ -29,7 +36,7 @@ class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity, priva
}
val takeFlags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
Intent.FLAG_GRANT_READ_URI_PERMISSION
fragmentActivity.contentResolver.takePersistableUriPermission(
path,
takeFlags
@ -46,7 +53,8 @@ class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity, priva
// If user check move data, show copy progress dialog.
CopyDirProgressDialog.newInstance(fragmentActivity, previous, path, callback)
?.show(fragmentActivity.supportFragmentManager, CopyDirProgressDialog.TAG)
})
}
)
citraDirectoryDialog.show(
fragmentActivity.supportFragmentManager,
CitraDirectoryDialogFragment.TAG

View file

@ -16,24 +16,26 @@ object CitraDirectoryUtils {
fun needToUpdateManually(): Boolean {
val directoryString = preferences.getString(CITRA_DIRECTORY, "")
val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY,"")
return (directoryString != "" && limeDirectoryString != "" && directoryString != limeDirectoryString)
val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY, "")
return (
directoryString != "" && limeDirectoryString != "" &&
directoryString != limeDirectoryString
)
}
fun attemptAutomaticUpdateDirectory() {
val directoryString = preferences.getString(CITRA_DIRECTORY, "")
val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY,"")
val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY, "")
if (needToUpdateManually()) {
return;
return
}
if (directoryString == "" && limeDirectoryString != "") {
if (directoryString == "" && limeDirectoryString != "") {
// Upgrade from Lime3DS to Azahar
PermissionsHandler.setCitraDirectory(limeDirectoryString)
PermissionsHandler.setCitraDirectory(limeDirectoryString)
removeLimeDirectoryPreference()
DirectoryInitialization.resetCitraDirectoryState()
DirectoryInitialization.start()
} else if (directoryString != "" && directoryString == limeDirectoryString) {
} else if (directoryString != "" && directoryString == limeDirectoryString) {
// Both the Lime3DS and Azahar directories are the same,
// so delete the obsolete Lime3DS value.
removeLimeDirectoryPreference()

View file

@ -15,13 +15,14 @@ object ControllerMappingHelper {
/**
* Some controllers report extra button presses that can be ignored.
*/
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
return if (isDualShock4(inputDevice)) {
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean =
if (isDualShock4(inputDevice)) {
// The two analog triggers generate analog motion events as well as a keycode.
// We always prefer to use the analog values, so throw away the button press
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
} else false
}
} else {
false
}
/**
* Scale an axis to be zero-centered with a proper range.

View file

@ -7,16 +7,16 @@ package org.citra.citra_emu.utils
import android.content.Context
import android.net.Uri
import androidx.preference.PreferenceManager
import org.citra.citra_emu.BuildConfig
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.PermissionsHandler.hasWriteAccess
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.concurrent.atomic.AtomicBoolean
import org.citra.citra_emu.BuildConfig
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.PermissionsHandler.hasWriteAccess
/**
* A service that spawns its own thread in order to copy several binary and shader files
@ -70,9 +70,8 @@ object DirectoryInitialization {
}
@JvmStatic
fun areCitraDirectoriesReady(): Boolean {
return directoryState == DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED
}
fun areCitraDirectoriesReady(): Boolean =
directoryState == DirectoryInitializationState.CITRA_DIRECTORIES_INITIALIZED
fun resetCitraDirectoryState() {
directoryState = null
@ -130,18 +129,22 @@ object DirectoryInitialization {
createdFolder = true
}
copyAssetFolder(
assetFolder + File.separator + file, File(outputFolder, file),
overwrite, context
assetFolder + File.separator + file,
File(outputFolder, file),
overwrite,
context
)
copyAsset(
assetFolder + File.separator + file, File(outputFolder, file), overwrite,
assetFolder + File.separator + file,
File(outputFolder, file),
overwrite,
context
)
}
} catch (e: IOException) {
Log.error(
"[DirectoryInitialization] Failed to copy asset folder: $assetFolder" +
e.message
e.message
)
}
}

View file

@ -33,6 +33,7 @@ object DiskShaderCacheProgress {
emulationActivity.runOnUiThread {
when (stage) {
LoadCallbackStage.Prepare -> prepareViewModel()
LoadCallbackStage.Decompile -> emulationViewModel.updateProgress(
emulationActivity.getString(R.string.preparing_shaders),
progress,
@ -40,7 +41,7 @@ object DiskShaderCacheProgress {
)
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
emulationActivity.getString(R.string.building_shaders, obj ),
emulationActivity.getString(R.string.building_shaders, obj),
progress,
max
)

View file

@ -8,14 +8,14 @@ import android.net.Uri
import android.provider.DocumentsContract
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.model.CheapDocument
import org.citra.citra_emu.utils.BuildUtil
import java.io.IOException
import java.net.URLDecoder
import java.nio.file.Paths
import java.util.StringTokenizer
import java.util.concurrent.ConcurrentHashMap
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.model.CheapDocument
import org.citra.citra_emu.utils.BuildUtil
/**
* A cached document tree for Citra user directory.
@ -127,7 +127,8 @@ class DocumentsTree {
// Create directory if it doesn't exist and creation is enabled
if (child == null && createIfNotExists) {
try {
val createdDir = FileUtil.createDir(current.uri.toString(), component) ?: return null
val createdDir =
FileUtil.createDir(current.uri.toString(), component) ?: return null
child = DocumentsNode(createdDir, true).apply {
parent = current
}
@ -152,9 +153,7 @@ class DocumentsTree {
}
@Synchronized
fun exists(filepath: String): Boolean {
return resolvePath(filepath) != null
}
fun exists(filepath: String): Boolean = resolvePath(filepath) != null
@Synchronized
fun copyFile(
@ -200,7 +199,11 @@ class DocumentsTree {
val node = resolvePath(filepath) ?: return false
try {
val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD)
val newUri = DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename)
val newUri = DocumentsContract.renameDocument(
context.contentResolver,
node.uri!!,
filename
)
node.rename(filename, newUri)
return true
} catch (e: Exception) {
@ -214,7 +217,12 @@ class DocumentsTree {
val sourceDirNode = resolvePath(sourceDirPath) ?: return false
val destDirNode = resolvePath(destDirPath) ?: return false
try {
val newUri = DocumentsContract.moveDocument(context.contentResolver, sourceFileNode.uri!!, sourceDirNode.uri!!, destDirNode.uri!!)
val newUri = DocumentsContract.moveDocument(
context.contentResolver,
sourceFileNode.uri!!,
sourceDirNode.uri!!,
destDirNode.uri!!
)
updateDocumentLocation("$sourceDirPath/$filename", "$destDirPath/$filename")
return true
} catch (e: Exception) {
@ -359,8 +367,7 @@ class DocumentsTree {
fun findChild(filename: String) = children[filename.lowercase()]
@Synchronized
fun getChildNames(): Array<String?> =
children.mapNotNull { it.value!!.name }.toTypedArray()
fun getChildNames(): Array<String?> = children.mapNotNull { it.value!!.name }.toTypedArray()
}
companion object {
@ -368,7 +375,7 @@ class DocumentsTree {
val kotlinDirectoryAccessWhitelist = arrayOf(
"/config/",
"/log/",
"/gpu_drivers/",
"/gpu_drivers/"
)
}
}

View file

@ -8,7 +8,6 @@ object EmulationLifecycleUtil {
private var shutdownHooks: MutableList<Runnable> = ArrayList()
private var pauseResumeHooks: MutableList<Runnable> = ArrayList()
fun closeGame() {
shutdownHooks.forEach(Runnable::run)
}
@ -19,7 +18,9 @@ object EmulationLifecycleUtil {
fun addShutdownHook(hook: Runnable) {
if (shutdownHooks.contains(hook)) {
Log.warning("[EmulationLifecycleUtil] Tried to add shutdown hook for function that already existed. Skipping.")
Log.warning(
"[EmulationLifecycleUtil] Tried to add shutdown hook for function that already existed. Skipping."
)
} else {
shutdownHooks.add(hook)
}
@ -27,7 +28,9 @@ object EmulationLifecycleUtil {
fun addPauseResumeHook(hook: Runnable) {
if (pauseResumeHooks.contains(hook)) {
Log.warning("[EmulationLifecycleUtil] Tried to add pause resume hook for function that already existed. Skipping.")
Log.warning(
"[EmulationLifecycleUtil] Tried to add pause resume hook for function that already existed. Skipping."
)
} else {
pauseResumeHooks.add(hook)
}

View file

@ -28,7 +28,10 @@ object EmulationMenuSettings {
.apply()
}
var buttonSlide: Int
get() = preferences.getInt("EmulationMenuSettings_ButtonSlideMode", ButtonSlidingMode.Disabled.int)
get() = preferences.getInt(
"EmulationMenuSettings_ButtonSlideMode",
ButtonSlidingMode.Disabled.int
)
set(value) {
preferences.edit()
.putInt("EmulationMenuSettings_ButtonSlideMode", value)
@ -39,8 +42,8 @@ object EmulationMenuSettings {
get() = preferences.getBoolean("EmulationMenuSettings_HapticFeedback", true)
set(value) {
preferences.edit()
.putBoolean("EmulationMenuSettings_HapticFeedback", value)
.apply()
.putBoolean("EmulationMenuSettings_HapticFeedback", value)
.apply()
}
var swapScreens: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_SwapScreens", false)

View file

@ -4,7 +4,6 @@
package org.citra.citra_emu.utils
import okio.ByteString.Companion.readByteString
import android.content.Context
import android.database.Cursor
import android.net.Uri
@ -13,8 +12,6 @@ import android.system.Os
import android.util.Pair
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.model.CheapDocument
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
@ -25,6 +22,9 @@ import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import okio.ByteString.Companion.readByteString
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.model.CheapDocument
object FileUtil {
const val PATH_TREE = "tree"
@ -228,7 +228,7 @@ object FileUtil {
if (uri.scheme == "file") {
BuildUtil.assertNotGooglePlay()
val file = File(uri.path!!);
val file = File(uri.path!!)
return file.name
}
@ -289,11 +289,7 @@ object FileUtil {
}
@JvmStatic
fun copyFile(
sourceUri: Uri,
destinationUri: Uri,
destinationFilename: String
): Boolean {
fun copyFile(sourceUri: Uri, destinationUri: Uri, destinationFilename: String): Boolean {
try {
val destinationParent =
DocumentFile.fromTreeUri(context, destinationUri) ?: return false
@ -362,11 +358,7 @@ object FileUtil {
return false
}
fun copyDir(
sourcePath: String,
destinationPath: String,
listener: CopyDirListener?
) {
fun copyDir(sourcePath: String, destinationPath: String, listener: CopyDirListener?) {
try {
val sourceUri = Uri.parse(sourcePath)
val destinationUri = Uri.parse(destinationPath)
@ -451,7 +443,12 @@ object FileUtil {
val sourceFileUri = ("$sourceDirUriString%2F$filename").toUri()
val sourceDirUri = sourceDirUriString.toUri()
val destDirUri = destDirUriString.toUri()
DocumentsContract.moveDocument(context.contentResolver, sourceFileUri, sourceDirUri, destDirUri)
DocumentsContract.moveDocument(
context.contentResolver,
sourceFileUri,
sourceDirUri,
destDirUri
)
return true
} catch (e: Exception) {
Log.error("[FileUtil]: Cannot move file, error: " + e.message)
@ -529,10 +526,7 @@ object FileUtil {
}
}
fun copyToExternalStorage(
sourceFile: Uri,
destinationDir: DocumentFile
): DocumentFile? {
fun copyToExternalStorage(sourceFile: Uri, destinationDir: DocumentFile): DocumentFile? {
val filename = getFilename(sourceFile)
val destinationFile = destinationDir.createFile("application/zip", filename)!!
destinationFile.outputStream().use { os ->
@ -555,21 +549,20 @@ object FileUtil {
false
}
fun getFreeSpace(context: Context, uri: Uri?): Double =
try {
val docTreeUri = DocumentsContract.buildDocumentUriUsingTree(
uri,
DocumentsContract.getTreeDocumentId(uri)
)
val pfd = context.contentResolver.openFileDescriptor(docTreeUri, "r")!!
val stats = Os.fstatvfs(pfd.fileDescriptor)
val spaceInGigaBytes = stats.f_bavail * stats.f_bsize / 1024.0 / 1024 / 1024
pfd.close()
spaceInGigaBytes
} catch (e: Exception) {
Log.error("[FileUtil] Cannot get storage size.")
0.0
}
fun getFreeSpace(context: Context, uri: Uri?): Double = try {
val docTreeUri = DocumentsContract.buildDocumentUriUsingTree(
uri,
DocumentsContract.getTreeDocumentId(uri)
)
val pfd = context.contentResolver.openFileDescriptor(docTreeUri, "r")!!
val stats = Os.fstatvfs(pfd.fileDescriptor)
val spaceInGigaBytes = stats.f_bavail * stats.f_bsize / 1024.0 / 1024 / 1024
pfd.close()
spaceInGigaBytes
} catch (e: Exception) {
Log.error("[FileUtil] Cannot get storage size.")
0.0
}
fun closeQuietly(closeable: AutoCloseable?) {
if (closeable != null) {
@ -589,8 +582,7 @@ object FileUtil {
}
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8)
fun getStringFromFile(file: File): String = String(file.readBytes(), StandardCharsets.UTF_8)
@Throws(IOException::class)
fun getStringFromInputStream(stream: InputStream, length: Long = 0L): String =

View file

@ -7,6 +7,7 @@ package org.citra.citra_emu.utils
import android.content.SharedPreferences
import android.net.Uri
import androidx.preference.PreferenceManager
import java.io.IOException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.citra.citra_emu.CitraApplication
@ -15,7 +16,6 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.model.CheapDocument
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.model.GameInfo
import java.io.IOException
object GameHelper {
const val KEY_GAME_PATH = "game_path"
@ -32,7 +32,9 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
NativeLibrary.getInstalledGamePaths().forEach {
games.add(getGame(Uri.parse(it.path), isInstalled = true, addedToLibrary = true, it.mediaType))
games.add(
getGame(Uri.parse(it.path), isInstalled = true, addedToLibrary = true, it.mediaType)
)
}
// Cache list of games found on disk
@ -62,24 +64,38 @@ object GameHelper {
addGamesRecursive(games, FileUtil.listFiles(it.uri), depth - 1)
} else {
if (Game.allExtensions.contains(FileUtil.getExtension(it.uri))) {
games.add(getGame(it.uri, isInstalled = false, addedToLibrary = true, Game.MediaType.GAME_CARD))
games.add(
getGame(
it.uri,
isInstalled = false,
addedToLibrary = true,
Game.MediaType.GAME_CARD
)
)
}
}
}
}
fun getGame(uri: Uri, isInstalled: Boolean, addedToLibrary: Boolean, mediaType: Game.MediaType): Game {
fun getGame(
uri: Uri,
isInstalled: Boolean,
addedToLibrary: Boolean,
mediaType: Game.MediaType
): Game {
val filePath = uri.toString()
var nativePath: String? = null
var gameInfo: GameInfo?
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) || filePath.startsWith("!")) {
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) ||
filePath.startsWith("!")
) {
gameInfo = GameInfo(filePath)
} else {
nativePath = if (uri.scheme == "fd") {
uri.toString()
} else {
"!" + NativeLibrary.getNativePath(uri)
};
}
gameInfo = GameInfo(nativePath)
}
@ -92,10 +108,16 @@ object GameHelper {
val newGame = Game(
valid,
(gameInfo?.getTitle() ?: FileUtil.getFilename(uri)).replace("[\\t\\n\\r]+".toRegex(), " "),
(
gameInfo?.getTitle() ?: FileUtil.getFilename(
uri
)
).replace("[\\t\\n\\r]+".toRegex(), " "),
filePath.replace("\n", " "),
// TODO: This next line can be deduplicated but I don't want to right now -OS
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) || filePath.startsWith("!")) {
if (BuildUtil.isGooglePlayBuild || FileUtil.isNativePath(filePath) ||
filePath.startsWith("!")
) {
filePath
} else {
nativePath!!
@ -103,7 +125,12 @@ object GameHelper {
gameInfo?.getTitleID() ?: 0,
mediaType,
gameInfo?.getCompany() ?: "",
if (isEncrypted) { CitraApplication.appContext.getString(R.string.unsupported_encrypted) } else { gameInfo?.getRegions() ?: "" },
if (isEncrypted) {
CitraApplication.appContext.getString(R.string.unsupported_encrypted)
} else {
gameInfo?.getRegions()
?: ""
},
isInstalled,
gameInfo?.isSystemTitle() ?: false,
gameInfo?.getIsVisibleSystemTitle() ?: false,

View file

@ -18,21 +18,16 @@ import coil.memory.MemoryCache
import coil.request.ImageRequest
import coil.request.Options
import coil.transform.RoundedCornersTransformation
import java.nio.IntBuffer
import org.citra.citra_emu.R
import org.citra.citra_emu.model.Game
import java.nio.IntBuffer
class GameIconFetcher(
private val game: Game,
private val options: Options
) : Fetcher {
override suspend fun fetch(): FetchResult {
return DrawableResult(
drawable = getGameIcon(game.icon)!!.toDrawable(options.context.resources),
isSampled = false,
dataSource = DataSource.DISK
)
}
class GameIconFetcher(private val game: Game, private val options: Options) : Fetcher {
override suspend fun fetch(): FetchResult = DrawableResult(
drawable = getGameIcon(game.icon)!!.toDrawable(options.context.resources),
isSampled = false,
dataSource = DataSource.DISK
)
private fun getGameIcon(vector: IntArray?): Bitmap? {
val bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565)

View file

@ -7,10 +7,6 @@ package org.citra.citra_emu.utils
import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.FileUtil.asDocumentFile
import org.citra.citra_emu.utils.FileUtil.inputStream
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
@ -19,6 +15,10 @@ import java.lang.IllegalStateException
import java.util.zip.ZipEntry
import java.util.zip.ZipException
import java.util.zip.ZipInputStream
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.utils.FileUtil.asDocumentFile
import org.citra.citra_emu.utils.FileUtil.inputStream
object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json"

View file

@ -4,11 +4,11 @@
package org.citra.citra_emu.utils
import java.io.File
import java.io.IOException
import java.io.InputStream
import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.InputStream
class GpuDriverMetadata {
/**
@ -76,12 +76,12 @@ class GpuDriverMetadata {
}
return other.name == name &&
other.description == description &&
other.author == author &&
other.vendor == vendor &&
other.version == version &&
other.minApi == minApi &&
other.libraryName == libraryName
other.description == description &&
other.author == author &&
other.vendor == vendor &&
other.version == version &&
other.minApi == minApi &&
other.libraryName == libraryName
}
override fun hashCode(): Int {

View file

@ -6,10 +6,10 @@ package org.citra.citra_emu.utils
import android.app.ActivityManager
import android.content.Context
import android.os.Build
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import java.util.Locale
import kotlin.math.ceil
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
object MemoryUtil {
private val context get() = CitraApplication.appContext
@ -24,59 +24,64 @@ object MemoryUtil {
const val Pb = Tb * 1024
const val Eb = Pb * 1024
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
when {
size < Kb -> {
context.getString(
R.string.memory_formatted,
size.hundredths,
context.getString(R.string.memory_byte_shorthand)
)
}
size < Mb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
context.getString(R.string.memory_kilobyte)
)
}
size < Gb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
context.getString(R.string.memory_megabyte)
)
}
size < Tb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
context.getString(R.string.memory_gigabyte)
)
}
size < Pb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
context.getString(R.string.memory_terabyte)
)
}
size < Eb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
context.getString(R.string.memory_petabyte)
)
}
else -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
context.getString(R.string.memory_exabyte)
)
}
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String = when {
size < Kb -> {
context.getString(
R.string.memory_formatted,
size.hundredths,
context.getString(R.string.memory_byte_shorthand)
)
}
size < Mb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
context.getString(R.string.memory_kilobyte)
)
}
size < Gb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
context.getString(R.string.memory_megabyte)
)
}
size < Tb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
context.getString(R.string.memory_gigabyte)
)
}
size < Pb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
context.getString(R.string.memory_terabyte)
)
}
size < Eb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
context.getString(R.string.memory_petabyte)
)
}
else -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
context.getString(R.string.memory_exabyte)
)
}
}
val totalMemory: Float
get() {
val memInfo = ActivityManager.MemoryInfo()
@ -91,16 +96,15 @@ object MemoryUtil {
}
}
fun isLessThan(minimum: Int, size: Float): Boolean =
when (size) {
Kb -> totalMemory < Mb && totalMemory < minimum
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
Eb -> totalMemory / Eb < minimum
else -> totalMemory < Kb && totalMemory < minimum
}
fun isLessThan(minimum: Int, size: Float): Boolean = when (size) {
Kb -> totalMemory < Mb && totalMemory < minimum
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
Eb -> totalMemory / Eb < minimum
else -> totalMemory < Kb && totalMemory < minimum
}
// Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
// the potential error created by memInfo.totalMem

View file

@ -11,8 +11,8 @@ import android.net.Uri
import android.os.Build
import android.provider.DocumentsContract
import androidx.activity.result.ActivityResultLauncher
import androidx.preference.PreferenceManager
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
object PermissionsHandler {
@ -38,7 +38,10 @@ object PermissionsHandler {
context.contentResolver.releasePersistableUriPermission(uri, takeFlags)
} catch (e: Exception) {
// Do not use native library logging, as the native library may not be loaded yet
android.util.Log.e("PermissionsHandler", "Cannot check citra data directory permission, error: ${e.message}")
android.util.Log.e(
"PermissionsHandler",
"Cannot check citra data directory permission, error: ${e.message}"
)
}
return false
}
@ -62,6 +65,5 @@ object PermissionsHandler {
)
activityLauncher.launch(initialUri)
}
}
}

View file

@ -50,4 +50,4 @@ object RefreshRateUtil {
window.attributes.preferredDisplayModeId = newModeId
}
}
}
}

View file

@ -16,11 +16,11 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import kotlin.math.roundToInt
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.ui.main.ThemeProvider
import kotlin.math.roundToInt
object ThemeUtil {
const val SYSTEM_BAR_ALPHA = 0.9f
@ -75,18 +75,19 @@ object ThemeUtil {
false -> setLightModeSystemBars(windowController)
true -> setDarkModeSystemBars(windowController)
}
AppCompatDelegate.MODE_NIGHT_NO -> setLightModeSystemBars(windowController)
AppCompatDelegate.MODE_NIGHT_YES -> setDarkModeSystemBars(windowController)
}
}
private fun isNightMode(activity: AppCompatActivity): Boolean {
return when (activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
private fun isNightMode(activity: AppCompatActivity): Boolean =
when (activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_NO -> false
Configuration.UI_MODE_NIGHT_YES -> true
else -> false
}
}
private fun setLightModeSystemBars(windowController: WindowInsetsControllerCompat) {
windowController.isAppearanceLightStatusBars = true
@ -107,21 +108,24 @@ object ThemeUtil {
}
@ColorInt
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
return Color.argb(
(alphaFactor * Color.alpha(color)).roundToInt(),
Color.red(color),
Color.green(color),
Color.blue(color)
)
}
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int = Color.argb(
(alphaFactor * Color.alpha(color)).roundToInt(),
Color.red(color),
Color.green(color),
Color.blue(color)
)
// Listener that detects if the theme keys are being changed from the setting menu and recreates the activity
private var listener: SharedPreferences.OnSharedPreferenceChangeListener? = null
fun ThemeChangeListener(activity: AppCompatActivity) {
listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
val relevantKeys = listOf(Settings.PREF_STATIC_THEME_COLOR, Settings.PREF_MATERIAL_YOU, Settings.PREF_BLACK_BACKGROUNDS)
val relevantKeys =
listOf(
Settings.PREF_STATIC_THEME_COLOR,
Settings.PREF_MATERIAL_YOU,
Settings.PREF_BLACK_BACKGROUNDS
)
if (key in relevantKeys) {
activity.recreate()
}

View file

@ -13,9 +13,7 @@ import org.citra.citra_emu.features.settings.model.IntSetting
object TurboHelper {
private var turboSpeedEnabled = false
fun isTurboSpeedEnabled(): Boolean {
return turboSpeedEnabled
}
fun isTurboSpeedEnabled(): Boolean = turboSpeedEnabled
fun reloadTurbo(showToast: Boolean) {
val context = CitraApplication.appContext

View file

@ -8,7 +8,7 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
object CompressProgressDialogViewModel: ViewModel() {
object CompressProgressDialogViewModel : ViewModel() {
private val _progress = MutableStateFlow(0)
val progress = _progress.asStateFlow()
@ -30,4 +30,4 @@ object CompressProgressDialogViewModel: ViewModel() {
_total.value = 0
_message.value = ""
}
}
}

View file

@ -16,8 +16,8 @@ import kotlinx.coroutines.withContext
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.FileUtil.asDocumentFile
import org.citra.citra_emu.utils.GpuDriverMetadata
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.GpuDriverMetadata
class DriverViewModel : ViewModel() {
val areDriversLoading get() = _areDriversLoading.asStateFlow()

View file

@ -70,13 +70,17 @@ class HomeViewModel : ViewModel() {
val selectedCitraDirectoryLiveData: LiveData<Uri?> = _selectedCitraDirectory
var selectedCitraDirectory: Uri?
get() = _selectedCitraDirectory.value
set(value) { _selectedCitraDirectory.value = value }
set(value) {
_selectedCitraDirectory.value = value
}
private val _selectedGamesDirectory = MutableLiveData<Uri?>()
val selectedGamesDirectoryLiveData: LiveData<Uri?> = _selectedGamesDirectory
var selectedGamesDirectory: Uri?
get() = _selectedGamesDirectory.value
set(value) { _selectedGamesDirectory.value = value }
set(value) {
_selectedGamesDirectory.value = value
}
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
if (_navigationVisible.value.first == visible) {