early-access version 3629
This commit is contained in:
parent
ab55aa0871
commit
5d6ba5745f
@ -1,7 +1,7 @@
|
||||
yuzu emulator early access
|
||||
=============
|
||||
|
||||
This is the source code for early-access 3628.
|
||||
This is the source code for early-access 3629.
|
||||
|
||||
## Legal Notice
|
||||
|
||||
|
1283
dist/languages/ca.ts
vendored
1283
dist/languages/ca.ts
vendored
File diff suppressed because it is too large
Load Diff
1283
dist/languages/cs.ts
vendored
1283
dist/languages/cs.ts
vendored
File diff suppressed because it is too large
Load Diff
1283
dist/languages/da.ts
vendored
1283
dist/languages/da.ts
vendored
File diff suppressed because it is too large
Load Diff
1396
dist/languages/de.ts
vendored
1396
dist/languages/de.ts
vendored
File diff suppressed because it is too large
Load Diff
1349
dist/languages/el.ts
vendored
1349
dist/languages/el.ts
vendored
File diff suppressed because it is too large
Load Diff
1293
dist/languages/es.ts
vendored
1293
dist/languages/es.ts
vendored
File diff suppressed because it is too large
Load Diff
1271
dist/languages/fr.ts
vendored
1271
dist/languages/fr.ts
vendored
File diff suppressed because it is too large
Load Diff
1285
dist/languages/id.ts
vendored
1285
dist/languages/id.ts
vendored
File diff suppressed because it is too large
Load Diff
1267
dist/languages/it.ts
vendored
1267
dist/languages/it.ts
vendored
File diff suppressed because it is too large
Load Diff
1331
dist/languages/ja_JP.ts
vendored
1331
dist/languages/ja_JP.ts
vendored
File diff suppressed because it is too large
Load Diff
1332
dist/languages/ko_KR.ts
vendored
1332
dist/languages/ko_KR.ts
vendored
File diff suppressed because it is too large
Load Diff
2286
dist/languages/nb.ts
vendored
2286
dist/languages/nb.ts
vendored
File diff suppressed because it is too large
Load Diff
1275
dist/languages/nl.ts
vendored
1275
dist/languages/nl.ts
vendored
File diff suppressed because it is too large
Load Diff
1284
dist/languages/pl.ts
vendored
1284
dist/languages/pl.ts
vendored
File diff suppressed because it is too large
Load Diff
1267
dist/languages/pt_BR.ts
vendored
1267
dist/languages/pt_BR.ts
vendored
File diff suppressed because it is too large
Load Diff
1267
dist/languages/pt_PT.ts
vendored
1267
dist/languages/pt_PT.ts
vendored
File diff suppressed because it is too large
Load Diff
1273
dist/languages/ru_RU.ts
vendored
1273
dist/languages/ru_RU.ts
vendored
File diff suppressed because it is too large
Load Diff
1283
dist/languages/sv.ts
vendored
1283
dist/languages/sv.ts
vendored
File diff suppressed because it is too large
Load Diff
1679
dist/languages/tr_TR.ts
vendored
1679
dist/languages/tr_TR.ts
vendored
File diff suppressed because it is too large
Load Diff
1486
dist/languages/uk.ts
vendored
1486
dist/languages/uk.ts
vendored
File diff suppressed because it is too large
Load Diff
2069
dist/languages/vi.ts
vendored
2069
dist/languages/vi.ts
vendored
File diff suppressed because it is too large
Load Diff
2069
dist/languages/vi_VN.ts
vendored
2069
dist/languages/vi_VN.ts
vendored
File diff suppressed because it is too large
Load Diff
1273
dist/languages/zh_CN.ts
vendored
1273
dist/languages/zh_CN.ts
vendored
File diff suppressed because it is too large
Load Diff
1293
dist/languages/zh_TW.ts
vendored
1293
dist/languages/zh_TW.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
@ -31,6 +32,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.fragments.EmulationFragment
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
@ -128,6 +130,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
super.onResume()
|
||||
nfcReader.startScanning()
|
||||
startMotionSensorListener()
|
||||
|
||||
NativeLibrary.notifyOrientationChange(
|
||||
EmulationMenuSettings.landscapeScreenLayout,
|
||||
getAdjustedRotation()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@ -233,6 +240,23 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
||||
|
||||
private fun getAdjustedRotation():Int {
|
||||
val rotation = windowManager.defaultDisplay.rotation;
|
||||
val config: Configuration = resources.configuration
|
||||
|
||||
if ((config.screenLayout and Configuration.SCREENLAYOUT_LONG_YES) != 0 ||
|
||||
(config.screenLayout and Configuration.SCREENLAYOUT_LONG_NO) == 0) {
|
||||
return rotation;
|
||||
}
|
||||
when (rotation) {
|
||||
Surface.ROTATION_0 -> return Surface.ROTATION_90;
|
||||
Surface.ROTATION_90 -> return Surface.ROTATION_0;
|
||||
Surface.ROTATION_180 -> return Surface.ROTATION_270;
|
||||
Surface.ROTATION_270 -> return Surface.ROTATION_180;
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
|
||||
private fun restoreState(savedInstanceState: Bundle) {
|
||||
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
|
||||
}
|
||||
@ -252,39 +276,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
}
|
||||
|
||||
private fun editControlsPlacement() {
|
||||
if (emulationFragment!!.isConfiguringControls) {
|
||||
emulationFragment!!.stopConfiguringControls()
|
||||
} else {
|
||||
emulationFragment!!.startConfiguringControls()
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustScale() {
|
||||
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
|
||||
sliderBinding.slider.valueTo = 150F
|
||||
sliderBinding.slider.value =
|
||||
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
|
||||
sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ ->
|
||||
sliderBinding.textValue.text = value.toString()
|
||||
setControlScale(value.toInt())
|
||||
})
|
||||
sliderBinding.textValue.text = sliderBinding.slider.value.toString()
|
||||
sliderBinding.textUnits.text = "%"
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.emulation_control_scale)
|
||||
.setView(sliderBinding.root)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
setControlScale(sliderBinding.slider.value.toInt())
|
||||
}
|
||||
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
|
||||
setControlScale(50)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun startMotionSensorListener() {
|
||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
@ -302,22 +293,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
sensorManager.unregisterListener(this, accelSensor)
|
||||
}
|
||||
|
||||
private fun setControlScale(scale: Int) {
|
||||
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
|
||||
.putInt(Settings.PREF_CONTROL_SCALE, scale)
|
||||
.apply()
|
||||
emulationFragment!!.refreshInputOverlay()
|
||||
}
|
||||
|
||||
private fun resetOverlay() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.emulation_touch_overlay_reset))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationFragment!!.resetInputOverlay() }
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||
|
||||
|
@ -111,6 +111,7 @@ class Settings {
|
||||
|
||||
const val PREF_OVERLAY_INIT = "OverlayInit"
|
||||
const val PREF_CONTROL_SCALE = "controlScale"
|
||||
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||
const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0"
|
||||
const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1"
|
||||
|
@ -118,12 +118,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
presenter.onStop(isFinishing)
|
||||
|
||||
// Update framebuffer layout when closing the settings
|
||||
NativeLibrary.notifyOrientationChange(
|
||||
EmulationMenuSettings.landscapeScreenLayout,
|
||||
windowManager.defaultDisplay.rotation
|
||||
)
|
||||
}
|
||||
|
||||
override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
@ -21,10 +23,12 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
@ -168,14 +172,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
fun refreshInputOverlay() {
|
||||
private fun refreshInputOverlay() {
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
|
||||
fun resetInputOverlay() {
|
||||
// Reset button scale
|
||||
private fun resetInputOverlay() {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_CONTROL_SCALE, 50)
|
||||
.remove(Settings.PREF_CONTROL_SCALE)
|
||||
.remove(Settings.PREF_CONTROL_OPACITY)
|
||||
.apply()
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
|
||||
}
|
||||
@ -251,6 +255,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_adjust_overlay -> {
|
||||
adjustOverlay()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_toggle_controls -> {
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
@ -278,9 +287,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
// Override normal behaviour so the dialog doesn't close
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
.setOnClickListener {
|
||||
val isChecked = !optionsArray[0];
|
||||
val isChecked = !optionsArray[0]
|
||||
for (i in 0..14) {
|
||||
optionsArray[i] = isChecked;
|
||||
optionsArray[i] = isChecked
|
||||
dialog.listView.setItemChecked(i, isChecked)
|
||||
preferences.edit()
|
||||
.putBoolean("buttonToggle$i", isChecked)
|
||||
@ -328,18 +337,64 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
popup.show()
|
||||
}
|
||||
|
||||
fun startConfiguringControls() {
|
||||
private fun startConfiguringControls() {
|
||||
binding.doneControlConfig.visibility = View.VISIBLE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||
}
|
||||
|
||||
fun stopConfiguringControls() {
|
||||
private fun stopConfiguringControls() {
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||
}
|
||||
|
||||
val isConfiguringControls: Boolean
|
||||
get() = binding.surfaceInputOverlay.isInEditMode
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun adjustOverlay() {
|
||||
val adjustBinding = DialogOverlayAdjustBinding.inflate(layoutInflater)
|
||||
adjustBinding.apply {
|
||||
inputScaleSlider.apply {
|
||||
valueTo = 150F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
setControlScale(value.toInt())
|
||||
})
|
||||
}
|
||||
inputOpacitySlider.apply {
|
||||
valueTo = 100F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
setControlOpacity(value.toInt())
|
||||
})
|
||||
}
|
||||
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
|
||||
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.emulation_control_adjust)
|
||||
.setView(adjustBinding.root)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
|
||||
setControlScale(50)
|
||||
setControlOpacity(100)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setControlScale(scale: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_CONTROL_SCALE, scale)
|
||||
.apply()
|
||||
refreshInputOverlay()
|
||||
}
|
||||
|
||||
private fun setControlOpacity(opacity: Int) {
|
||||
preferences.edit()
|
||||
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
|
||||
.apply()
|
||||
refreshInputOverlay()
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
@ -415,8 +470,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
if (state != State.PAUSED) {
|
||||
Log.debug("[EmulationFragment] Pausing emulation.")
|
||||
|
||||
// Release the surface before pausing, since emulation has to be running for that.
|
||||
NativeLibrary.surfaceDestroyed()
|
||||
NativeLibrary.pauseEmulation()
|
||||
|
||||
state = State.PAUSED
|
||||
@ -461,7 +514,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
Log.debug("[EmulationFragment] Surface destroyed.")
|
||||
when (state) {
|
||||
State.RUNNING -> {
|
||||
NativeLibrary.surfaceDestroyed()
|
||||
state = State.PAUSED
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
R.drawable.ic_add
|
||||
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
HomeSetting(
|
||||
R.string.import_export_saves,
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
|
||||
|
@ -68,19 +68,21 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return if (savesFolderRoot == "") {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.import_export_saves)
|
||||
.setTitle(R.string.manage_save_data)
|
||||
.setMessage(R.string.import_export_saves_no_profile)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.import_export_saves)
|
||||
.setPositiveButton(R.string.export_saves) { _, _ ->
|
||||
.setTitle(R.string.manage_save_data)
|
||||
.setMessage(R.string.manage_save_data_description)
|
||||
.setNegativeButton(R.string.export_saves) { _, _ ->
|
||||
exportSave()
|
||||
}
|
||||
.setNeutralButton(R.string.import_saves) { _, _ ->
|
||||
.setPositiveButton(R.string.import_saves) { _, _ ->
|
||||
documentPicker.launch(arrayOf("application/zip"))
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@ -95,7 +97,10 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
tempFolder.mkdirs()
|
||||
val saveFolder = File(savesFolderRoot)
|
||||
val outputZipFile = File(
|
||||
tempFolder, "yuzu saves - ${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))}.zip"
|
||||
tempFolder,
|
||||
"yuzu saves - ${
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
}.zip"
|
||||
)
|
||||
outputZipFile.createNewFile()
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
||||
@ -206,11 +211,10 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (!validZip) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.save_file_invalid_zip_structure),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.save_file_invalid_zip_structure,
|
||||
R.string.save_file_invalid_zip_structure_description
|
||||
).show(childFragmentManager, MessageDialogFragment.TAG)
|
||||
return@withContext
|
||||
}
|
||||
Toast.makeText(
|
||||
|
@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
|
||||
class MessageDialogFragment : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val titleId = requireArguments().getInt(TITLE)
|
||||
val descriptionId = requireArguments().getInt(DESCRIPTION)
|
||||
val helpLinkId = requireArguments().getInt(HELP_LINK)
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setPositiveButton(R.string.close, null)
|
||||
.setTitle(titleId)
|
||||
.setMessage(descriptionId)
|
||||
|
||||
if (helpLinkId != 0) {
|
||||
dialog.setNeutralButton(R.string.learn_more) { _, _ ->
|
||||
openLink(getString(helpLinkId))
|
||||
}
|
||||
}
|
||||
|
||||
return dialog.show()
|
||||
}
|
||||
|
||||
private fun openLink(link: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MessageDialogFragment"
|
||||
|
||||
private const val TITLE = "Title"
|
||||
private const val DESCRIPTION = "Description"
|
||||
private const val HELP_LINK = "Link"
|
||||
|
||||
fun newInstance(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
helpLinkId: Int = 0
|
||||
): MessageDialogFragment {
|
||||
val dialog = MessageDialogFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.apply {
|
||||
putInt(TITLE, titleId)
|
||||
putInt(DESCRIPTION, descriptionId)
|
||||
putInt(HELP_LINK, helpLinkId)
|
||||
}
|
||||
dialog.arguments = bundle
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
@ -48,9 +49,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
private lateinit var windowInsets: WindowInsets
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
@ -343,10 +341,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
}
|
||||
|
||||
private fun addOverlayControls(orientation: String) {
|
||||
val windowSize = getSafeScreenSize(context)
|
||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_a,
|
||||
R.drawable.facebutton_a_depressed,
|
||||
ButtonType.BUTTON_A,
|
||||
@ -358,6 +358,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_b,
|
||||
R.drawable.facebutton_b_depressed,
|
||||
ButtonType.BUTTON_B,
|
||||
@ -369,6 +370,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_x,
|
||||
R.drawable.facebutton_x_depressed,
|
||||
ButtonType.BUTTON_X,
|
||||
@ -380,6 +382,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_y,
|
||||
R.drawable.facebutton_y_depressed,
|
||||
ButtonType.BUTTON_Y,
|
||||
@ -391,6 +394,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.l_shoulder,
|
||||
R.drawable.l_shoulder_depressed,
|
||||
ButtonType.TRIGGER_L,
|
||||
@ -402,6 +406,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.r_shoulder,
|
||||
R.drawable.r_shoulder_depressed,
|
||||
ButtonType.TRIGGER_R,
|
||||
@ -413,6 +418,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.zl_trigger,
|
||||
R.drawable.zl_trigger_depressed,
|
||||
ButtonType.TRIGGER_ZL,
|
||||
@ -424,6 +430,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.zr_trigger,
|
||||
R.drawable.zr_trigger_depressed,
|
||||
ButtonType.TRIGGER_ZR,
|
||||
@ -435,6 +442,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_plus,
|
||||
R.drawable.facebutton_plus_depressed,
|
||||
ButtonType.BUTTON_PLUS,
|
||||
@ -446,6 +454,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_minus,
|
||||
R.drawable.facebutton_minus_depressed,
|
||||
ButtonType.BUTTON_MINUS,
|
||||
@ -457,6 +466,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayDpads.add(
|
||||
initializeOverlayDpad(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.dpad_standard,
|
||||
R.drawable.dpad_standard_cardinal_depressed,
|
||||
R.drawable.dpad_standard_diagonal_depressed,
|
||||
@ -468,6 +478,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayJoysticks.add(
|
||||
initializeOverlayJoystick(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.joystick_range,
|
||||
R.drawable.joystick,
|
||||
R.drawable.joystick_depressed,
|
||||
@ -481,6 +492,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayJoysticks.add(
|
||||
initializeOverlayJoystick(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.joystick_range,
|
||||
R.drawable.joystick,
|
||||
R.drawable.joystick_depressed,
|
||||
@ -494,6 +506,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_home,
|
||||
R.drawable.facebutton_home_depressed,
|
||||
ButtonType.BUTTON_HOME,
|
||||
@ -505,6 +518,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
overlayButtons.add(
|
||||
initializeOverlayButton(
|
||||
context,
|
||||
windowSize,
|
||||
R.drawable.facebutton_screenshot,
|
||||
R.drawable.facebutton_screenshot_depressed,
|
||||
ButtonType.BUTTON_CAPTURE,
|
||||
@ -530,9 +544,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
}
|
||||
|
||||
private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) {
|
||||
val windowSize = getSafeScreenSize(context)
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||
.putFloat("$sharedPrefsId$orientation-X", x.toFloat())
|
||||
.putFloat("$sharedPrefsId$orientation-Y", y.toFloat())
|
||||
.putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
|
||||
.putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
|
||||
.apply()
|
||||
}
|
||||
|
||||
@ -557,170 +574,129 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
}
|
||||
|
||||
private fun defaultOverlayLandscape() {
|
||||
// Get screen size
|
||||
val windowMetrics =
|
||||
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context as Activity)
|
||||
var maxY = windowMetrics.bounds.height().toFloat()
|
||||
var maxX = windowMetrics.bounds.width().toFloat()
|
||||
var minY = 0
|
||||
var minX = 0
|
||||
|
||||
// If we have API access, calculate the safe area to draw the overlay
|
||||
var cutoutLeft = 0
|
||||
var cutoutBottom = 0
|
||||
val insets = windowInsets.displayCutout
|
||||
if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
maxY =
|
||||
if (insets.boundingRectTop.bottom != 0) insets.boundingRectTop.bottom.toFloat() else maxY
|
||||
maxX =
|
||||
if (insets.boundingRectRight.left != 0) insets.boundingRectRight.left.toFloat() else maxX
|
||||
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
|
||||
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
|
||||
|
||||
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
|
||||
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
|
||||
}
|
||||
|
||||
// This makes sure that if we have an inset on one side of the screen, we mirror it on
|
||||
// the other side. Since removing space from one of the max values messes with the scale,
|
||||
// we also have to account for it using our min values.
|
||||
if (maxX.toInt() != windowMetrics.bounds.width()) minX += cutoutLeft
|
||||
if (maxY.toInt() != windowMetrics.bounds.height()) minY += cutoutBottom
|
||||
if (minX > 0 && maxX.toInt() == windowMetrics.bounds.width()) {
|
||||
maxX -= (minX * 2)
|
||||
} else if (minX > 0) {
|
||||
maxX -= minX
|
||||
}
|
||||
if (minY > 0 && maxY.toInt() == windowMetrics.bounds.height()) {
|
||||
maxY -= (minY * 2)
|
||||
} else if (minY > 0) {
|
||||
maxY -= minY
|
||||
}
|
||||
|
||||
// Each value is a percent from max X/Y stored as an int. Have to bring that value down
|
||||
// to a decimal before multiplying by MAX X/Y.
|
||||
// Each value represents the position of the button in relation to the screen size without insets.
|
||||
preferences.edit()
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_A.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_A_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_B.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_B_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_X.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_X_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_Y.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_Y_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZL.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZL_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_ZR.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_ZR_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.DPAD_UP.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_DPAD_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_L.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_L_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.TRIGGER_R.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_TRIGGER_R_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_PLUS.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_PLUS_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_MINUS.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_MINUS_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_HOME.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_HOME_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
|
||||
.toFloat() / 1000 * maxX + minX
|
||||
.toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
|
||||
.toFloat() / 1000 * maxY + minY
|
||||
.toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_R.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_STICK_R_Y).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-X",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000 * maxX + minX
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_X).toFloat() / 1000
|
||||
)
|
||||
.putFloat(
|
||||
ButtonType.STICK_L.toString() + "-Y",
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000 * maxY + minY
|
||||
resources.getInteger(R.integer.SWITCH_STICK_L_Y).toFloat() / 1000
|
||||
)
|
||||
.apply()
|
||||
}
|
||||
@ -730,6 +706,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
|
||||
/**
|
||||
* Resizes a [Bitmap] by a given scale factor
|
||||
*
|
||||
@ -766,6 +745,59 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
return scaledBitmap
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the safe screen size for drawing the overlay
|
||||
*
|
||||
* @param context Context for getting the window metrics
|
||||
* @return A pair of points, the first being the top left corner of the safe area,
|
||||
* the second being the bottom right corner of the safe area
|
||||
*/
|
||||
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||
// Get screen size
|
||||
val windowMetrics =
|
||||
WindowMetricsCalculator.getOrCreate()
|
||||
.computeCurrentWindowMetrics(context as Activity)
|
||||
var maxY = windowMetrics.bounds.height().toFloat()
|
||||
var maxX = windowMetrics.bounds.width().toFloat()
|
||||
var minY = 0
|
||||
var minX = 0
|
||||
|
||||
// If we have API access, calculate the safe area to draw the overlay
|
||||
var cutoutLeft = 0
|
||||
var cutoutBottom = 0
|
||||
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
|
||||
if (insets != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
|
||||
insets.boundingRectTop.bottom.toFloat() else maxY
|
||||
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
|
||||
insets.boundingRectRight.left.toFloat() else maxX
|
||||
|
||||
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
|
||||
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
|
||||
|
||||
cutoutLeft = insets.boundingRectRight.right - insets.boundingRectRight.left
|
||||
cutoutBottom = insets.boundingRectTop.top - insets.boundingRectTop.bottom
|
||||
}
|
||||
|
||||
// This makes sure that if we have an inset on one side of the screen, we mirror it on
|
||||
// the other side. Since removing space from one of the max values messes with the scale,
|
||||
// we also have to account for it using our min values.
|
||||
if (maxX.toInt() != windowMetrics.bounds.width()) minX += cutoutLeft
|
||||
if (maxY.toInt() != windowMetrics.bounds.height()) minY += cutoutBottom
|
||||
if (minX > 0 && maxX.toInt() == windowMetrics.bounds.width()) {
|
||||
maxX -= (minX * 2)
|
||||
} else if (minX > 0) {
|
||||
maxX -= minX
|
||||
}
|
||||
if (minY > 0 && maxY.toInt() == windowMetrics.bounds.height()) {
|
||||
maxY -= (minY * 2)
|
||||
} else if (minY > 0) {
|
||||
maxY -= minY
|
||||
}
|
||||
|
||||
return Pair(Point(minX, minY), Point(maxX.toInt(), maxY.toInt()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an InputOverlayDrawableButton, given by resId, with all of the
|
||||
* parameters set for it to be properly shown on the InputOverlay.
|
||||
@ -795,6 +827,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* for Android to call the onDraw method.
|
||||
*
|
||||
* @param context The current [Context].
|
||||
* @param windowSize The size of the window to draw the overlay on.
|
||||
* @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
|
||||
* @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
|
||||
* @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
|
||||
@ -802,6 +835,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
*/
|
||||
private fun initializeOverlayButton(
|
||||
context: Context,
|
||||
windowSize: Pair<Point, Point>,
|
||||
defaultResId: Int,
|
||||
pressedResId: Int,
|
||||
buttonId: Int,
|
||||
@ -836,12 +870,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
val overlayDrawable =
|
||||
InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId)
|
||||
|
||||
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val xKey = "$buttonId$orientation-X"
|
||||
val yKey = "$buttonId$orientation-Y"
|
||||
val drawableX = sPrefs.getFloat(xKey, 0f).toInt()
|
||||
val drawableY = sPrefs.getFloat(yKey, 0f).toInt()
|
||||
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
||||
val drawableYPercent = sPrefs.getFloat(yKey, 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val width = overlayDrawable.width
|
||||
val height = overlayDrawable.height
|
||||
|
||||
@ -859,6 +899,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
drawableX - (width / 2),
|
||||
drawableY - (height / 2)
|
||||
)
|
||||
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||
return overlayDrawable
|
||||
}
|
||||
|
||||
@ -866,6 +908,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* Initializes an [InputOverlayDrawableDpad]
|
||||
*
|
||||
* @param context The current [Context].
|
||||
* @param windowSize The size of the window to draw the overlay on.
|
||||
* @param defaultResId The [Bitmap] resource ID of the default state.
|
||||
* @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
|
||||
* @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
|
||||
@ -873,6 +916,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
*/
|
||||
private fun initializeOverlayDpad(
|
||||
context: Context,
|
||||
windowSize: Pair<Point, Point>,
|
||||
defaultResId: Int,
|
||||
pressedOneDirectionResId: Int,
|
||||
pressedTwoDirectionsResId: Int,
|
||||
@ -907,10 +951,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
ButtonType.DPAD_RIGHT
|
||||
)
|
||||
|
||||
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableX = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f).toInt()
|
||||
val drawableY = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f).toInt()
|
||||
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val width = overlayDrawable.width
|
||||
val height = overlayDrawable.height
|
||||
|
||||
@ -925,6 +975,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
// Need to set the image's position
|
||||
overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
|
||||
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||
return overlayDrawable
|
||||
}
|
||||
|
||||
@ -932,6 +984,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* Initializes an [InputOverlayDrawableJoystick]
|
||||
*
|
||||
* @param context The current [Context]
|
||||
* @param windowSize The size of the window to draw the overlay on.
|
||||
* @param resOuter Resource ID for the outer image of the joystick (the static image that shows the circular bounds).
|
||||
* @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
|
||||
* @param pressedResInner Resource ID for the pressed inner image of the joystick.
|
||||
@ -941,6 +994,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
*/
|
||||
private fun initializeOverlayJoystick(
|
||||
context: Context,
|
||||
windowSize: Pair<Point, Point>,
|
||||
resOuter: Int,
|
||||
defaultResInner: Int,
|
||||
pressedResInner: Int,
|
||||
@ -964,10 +1018,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
||||
val bitmapInnerPressed = getBitmap(context, pressedResInner, 1.0f)
|
||||
|
||||
// Get the minimum and maximum coordinates of the screen where the button can be placed.
|
||||
val min = windowSize.first
|
||||
val max = windowSize.second
|
||||
|
||||
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||
// These were set in the input overlay configuration menu.
|
||||
val drawableX = sPrefs.getFloat("$button$orientation-X", 0f).toInt()
|
||||
val drawableY = sPrefs.getFloat("$button$orientation-Y", 0f).toInt()
|
||||
val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
|
||||
val drawableYPercent = sPrefs.getFloat("$button$orientation-Y", 0f)
|
||||
val drawableX = (drawableXPercent * max.x + min.x).toInt()
|
||||
val drawableY = (drawableYPercent * max.y + min.y).toInt()
|
||||
val outerScale = 1.66f
|
||||
|
||||
// Now set the bounds for the InputOverlayDrawableJoystick.
|
||||
@ -996,6 +1056,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
// Need to set the image's position
|
||||
overlayDrawable.setPosition(drawableX, drawableY)
|
||||
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||
return overlayDrawable
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ class InputOverlayDrawableButton(
|
||||
controlPositionX = fingerPositionX - (width / 2)
|
||||
controlPositionY = fingerPositionY - (height / 2)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
@ -135,6 +136,11 @@ class InputOverlayDrawableButton(
|
||||
pressedStateBitmap.setBounds(left, top, right, bottom)
|
||||
}
|
||||
|
||||
fun setOpacity(value: Int) {
|
||||
defaultStateBitmap.alpha = value
|
||||
pressedStateBitmap.alpha = value
|
||||
}
|
||||
|
||||
val status: Int
|
||||
get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED
|
||||
val bounds: Rect
|
||||
|
@ -231,6 +231,7 @@ class InputOverlayDrawableDpad(
|
||||
previousTouchX = fingerPositionX
|
||||
previousTouchY = fingerPositionY
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
@ -258,6 +259,12 @@ class InputOverlayDrawableDpad(
|
||||
pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom)
|
||||
}
|
||||
|
||||
fun setOpacity(value: Int) {
|
||||
defaultStateBitmap.alpha = value
|
||||
pressedOneDirectionStateBitmap.alpha = value
|
||||
pressedTwoDirectionsStateBitmap.alpha = value
|
||||
}
|
||||
|
||||
val bounds: Rect
|
||||
get() = defaultStateBitmap.bounds
|
||||
|
||||
|
@ -48,6 +48,8 @@ class InputOverlayDrawableJoystick(
|
||||
val width: Int
|
||||
val height: Int
|
||||
|
||||
private var opacity: Int = 0
|
||||
|
||||
private var virtBounds: Rect
|
||||
private var origBounds: Rect
|
||||
|
||||
@ -121,7 +123,7 @@ class InputOverlayDrawableJoystick(
|
||||
}
|
||||
pressedState = true
|
||||
outerBitmap.alpha = 0
|
||||
boundsBoxBitmap.alpha = 255
|
||||
boundsBoxBitmap.alpha = opacity
|
||||
if (EmulationMenuSettings.joystickRelCenter) {
|
||||
virtBounds.offset(
|
||||
xPosition - virtBounds.centerX(),
|
||||
@ -139,7 +141,7 @@ class InputOverlayDrawableJoystick(
|
||||
pressedState = false
|
||||
xAxis = 0.0f
|
||||
yAxis = 0.0f
|
||||
outerBitmap.alpha = 255
|
||||
outerBitmap.alpha = opacity
|
||||
boundsBoxBitmap.alpha = 0
|
||||
virtBounds = Rect(
|
||||
origBounds.left,
|
||||
@ -203,6 +205,7 @@ class InputOverlayDrawableJoystick(
|
||||
controlPositionX = fingerPositionX - (width / 2)
|
||||
controlPositionY = fingerPositionY - (height / 2)
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
controlPositionX += fingerPositionX - previousTouchX
|
||||
controlPositionY += fingerPositionY - previousTouchY
|
||||
@ -261,4 +264,19 @@ class InputOverlayDrawableJoystick(
|
||||
controlPositionX = x
|
||||
controlPositionY = y
|
||||
}
|
||||
|
||||
fun setOpacity(value: Int) {
|
||||
opacity = value
|
||||
|
||||
defaultStateInnerBitmap.alpha = value
|
||||
pressedStateInnerBitmap.alpha = value
|
||||
|
||||
if (trackId == -1) {
|
||||
outerBitmap.alpha = value
|
||||
boundsBoxBitmap.alpha = 0
|
||||
} else {
|
||||
outerBitmap.alpha = 0
|
||||
boundsBoxBitmap.alpha = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
@ -251,11 +252,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (result == null)
|
||||
return@registerForActivityResult
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
// When a new directory is picked, we currently will reset the existing games
|
||||
@ -279,19 +278,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
return@registerForActivityResult
|
||||
|
||||
if (!FileUtil.hasExtension(result.toString(), "keys")) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.invalid_keys_file,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_keys_failure_extension_description
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
@ -310,11 +306,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
).show()
|
||||
gamesViewModel.reloadGames(true)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_keys_failure,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.invalid_keys_error,
|
||||
R.string.install_keys_failure_description,
|
||||
R.string.dumping_keys_quickstart_link
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -325,19 +321,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
return@registerForActivityResult
|
||||
|
||||
if (!FileUtil.hasExtension(result.toString(), "bin")) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.invalid_keys_file,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.reading_keys_failure,
|
||||
R.string.install_keys_failure_extension_description
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
takeFlags
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
)
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
@ -355,11 +348,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.install_amiibo_keys_failure,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
MessageDialogFragment.newInstance(
|
||||
R.string.invalid_keys_error,
|
||||
R.string.install_keys_failure_description,
|
||||
R.string.dumping_keys_quickstart_link
|
||||
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,11 @@ object GpuDriverHelper {
|
||||
)
|
||||
|
||||
// Unzip the driver.
|
||||
try {
|
||||
unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
|
||||
} catch (e: SecurityException) {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the driver parameters.
|
||||
initializeDriverParameters(context)
|
||||
|
@ -89,8 +89,16 @@ public:
|
||||
return m_native_window;
|
||||
}
|
||||
|
||||
void SetNativeWindow(ANativeWindow* m_native_window_) {
|
||||
m_native_window = m_native_window_;
|
||||
void SetNativeWindow(ANativeWindow* native_window) {
|
||||
m_native_window = native_window;
|
||||
}
|
||||
|
||||
u32 ScreenRotation() const {
|
||||
return m_screen_rotation;
|
||||
}
|
||||
|
||||
void SetScreenRotation(u32 screen_rotation) {
|
||||
m_screen_rotation = screen_rotation;
|
||||
}
|
||||
|
||||
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||
@ -140,6 +148,7 @@ public:
|
||||
return;
|
||||
}
|
||||
m_window->OnSurfaceChanged(m_native_window);
|
||||
m_system.Renderer().NotifySurfaceChanged();
|
||||
}
|
||||
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
||||
@ -379,6 +388,7 @@ private:
|
||||
// Window management
|
||||
std::unique_ptr<EmuWindow_Android> m_window;
|
||||
ANativeWindow* m_native_window{};
|
||||
u32 m_screen_rotation{};
|
||||
|
||||
// Core emulation
|
||||
Core::System m_system;
|
||||
@ -404,6 +414,10 @@ private:
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
u32 GetAndroidScreenRotation() {
|
||||
return EmulationSession::GetInstance().ScreenRotation();
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
@ -450,7 +464,9 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceDestroyed(JNIEnv* env,
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
jint layout_option,
|
||||
jint rotation) {}
|
||||
jint rotation) {
|
||||
return EmulationSession::GetInstance().SetScreenRotation(static_cast<u32>(rotation));
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz,
|
||||
|
@ -38,7 +38,8 @@
|
||||
<ImageView
|
||||
android:id="@+id/image_game_screen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
tools:src="@drawable/default_icon" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
67
src/android/app/src/main/res/layout/dialog_overlay_adjust.xml
Executable file
67
src/android/app/src/main/res/layout/dialog_overlay_adjust.xml
Executable file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/input_scale_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/emulation_control_scale"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="@+id/input_scale_slider"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/input_scale_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/input_scale_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/input_scale_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toTopOf="@+id/input_scale_slider"
|
||||
app:layout_constraintEnd_toEndOf="@+id/input_scale_slider"
|
||||
tools:text="100%" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/input_opacity_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/emulation_control_opacity"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="@+id/input_opacity_slider"
|
||||
app:layout_constraintTop_toBottomOf="@+id/input_scale_slider" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/input_opacity_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/input_opacity_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/input_opacity_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
app:layout_constraintBottom_toTopOf="@+id/input_opacity_slider"
|
||||
app:layout_constraintEnd_toEndOf="@+id/input_opacity_slider"
|
||||
tools:text="100%" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -10,6 +10,10 @@
|
||||
android:id="@+id/menu_edit_overlay"
|
||||
android:title="@string/emulation_touch_overlay_edit" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_adjust_overlay"
|
||||
android:title="@string/emulation_control_adjust" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_toggle_controls"
|
||||
android:title="@string/emulation_toggle_controls" />
|
||||
|
@ -64,8 +64,15 @@
|
||||
<string name="install_amiibo_keys_description">Required to use Amiibo in game</string>
|
||||
<string name="invalid_keys_file">Invalid keys file selected</string>
|
||||
<string name="install_keys_success">Keys successfully installed</string>
|
||||
<string name="install_keys_failure">Keys file (prod.keys) is invalid</string>
|
||||
<string name="install_amiibo_keys_failure">Keys file (key_retail.bin) is invalid</string>
|
||||
<string name="reading_keys_failure">Error reading encryption keys</string>
|
||||
<string name="install_keys_failure_extension_description">
|
||||
1. Verify your keys have the .keys extension.\n\n
|
||||
2. Keys must not be stored in the Downloads folder.\n\n
|
||||
Resolve the issue(s) and try again.
|
||||
</string>
|
||||
<string name="invalid_keys_error">Invalid encryption keys</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
|
||||
<string name="install_gpu_driver">Install GPU driver</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
@ -80,11 +87,13 @@
|
||||
<string name="no_file_manager">No file manager found</string>
|
||||
<string name="notification_no_directory_link">Could not open yuzu directory</string>
|
||||
<string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string>
|
||||
<string name="import_export_saves">Import/export saves</string>
|
||||
<string name="manage_save_data">Manage save data</string>
|
||||
<string name="manage_save_data_description">Save data found. Please select an option below.</string>
|
||||
<string name="import_export_saves_description">Import or export save files</string>
|
||||
<string name="import_export_saves_no_profile">No user profile found. Please launch a game first and retry.</string>
|
||||
<string name="save_file_imported_success">Save files were imported successfully</string>
|
||||
<string name="save_file_invalid_zip_structure">Invalid save directory structure: The first subfolder name must be the title ID of the game.</string>
|
||||
<string name="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
|
||||
<string name="save_file_imported_success">Imported successfully</string>
|
||||
<string name="save_file_invalid_zip_structure">Invalid save directory structure</string>
|
||||
<string name="save_file_invalid_zip_structure_description">The first subfolder name must be the title ID of the game.</string>
|
||||
<string name="import_saves">Import</string>
|
||||
<string name="export_saves">Export</string>
|
||||
|
||||
@ -164,6 +173,8 @@
|
||||
<string name="reset_all_settings">Reset all settings?</string>
|
||||
<string name="reset_all_settings_description">All Advanced Settings will be reset to their default configuration. This can not be undone.</string>
|
||||
<string name="settings_reset">Settings reset</string>
|
||||
<string name="close">Close</string>
|
||||
<string name="learn_more">Learn More</string>
|
||||
|
||||
<!-- GPU driver installation -->
|
||||
<string name="select_gpu_driver">Select GPU driver</string>
|
||||
@ -204,12 +215,14 @@
|
||||
<string name="emulation_haptics">Haptics</string>
|
||||
<string name="emulation_show_overlay">Show Overlay</string>
|
||||
<string name="emulation_toggle_all">Toggle All</string>
|
||||
<string name="emulation_control_scale">Adjust Scale</string>
|
||||
<string name="emulation_control_adjust">Adjust Overlay</string>
|
||||
<string name="emulation_control_scale">Scale</string>
|
||||
<string name="emulation_control_opacity">Opacity</string>
|
||||
<string name="emulation_touch_overlay_reset">Reset Overlay</string>
|
||||
<string name="emulation_touch_overlay_edit">Edit Overlay</string>
|
||||
<string name="emulation_pause">Pause Emulation</string>
|
||||
<string name="emulation_unpause">Unpause Emulation</string>
|
||||
<string name="emulation_input_overlay">Input Overlay</string>
|
||||
<string name="emulation_input_overlay">Overlay Options</string>
|
||||
<string name="emulation_game_loading">Game loading…</string>
|
||||
|
||||
<string name="load_settings">Loading Settings…</string>
|
||||
|
@ -89,6 +89,9 @@ public:
|
||||
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
||||
const Layout::FramebufferLayout& layout);
|
||||
|
||||
/// This is called to notify the rendering backend of a surface change
|
||||
virtual void NotifySurfaceChanged() {}
|
||||
|
||||
protected:
|
||||
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
||||
|
@ -54,6 +54,10 @@ public:
|
||||
return device.GetDriverName();
|
||||
}
|
||||
|
||||
void NotifySurfaceChanged() override {
|
||||
present_manager.NotifySurfaceChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
void Report() const;
|
||||
|
||||
|
@ -37,6 +37,10 @@
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
extern u32 GetAndroidScreenRotation();
|
||||
#endif
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
namespace {
|
||||
@ -74,23 +78,58 @@ struct ScreenRectVertex {
|
||||
}
|
||||
};
|
||||
|
||||
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
// clang-format off
|
||||
#ifdef ANDROID
|
||||
// Android renders in portrait, so rotate the matrix.
|
||||
return { 0.f, 2.f / width, 0.f, 0.f,
|
||||
-2.f / height, 0.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
1.f, -1.f, 0.f, 1.f};
|
||||
#else
|
||||
|
||||
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
constexpr u32 ROTATION_0 = 0;
|
||||
constexpr u32 ROTATION_90 = 1;
|
||||
constexpr u32 ROTATION_180 = 2;
|
||||
constexpr u32 ROTATION_270 = 3;
|
||||
|
||||
// clang-format off
|
||||
switch (GetAndroidScreenRotation()) {
|
||||
case ROTATION_0:
|
||||
// Desktop
|
||||
return { 2.f / width, 0.f, 0.f, 0.f,
|
||||
0.f, 2.f / height, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
-1.f, -1.f, 0.f, 1.f};
|
||||
#endif // ANDROID
|
||||
case ROTATION_180:
|
||||
// Reverse desktop
|
||||
return {-2.f / width, 0.f, 0.f, 0.f,
|
||||
0.f, -2.f / height, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
1.f, 1.f, 0.f, 1.f};
|
||||
case ROTATION_270:
|
||||
// Reverse landscape
|
||||
return { 0.f, -2.f / width, 0.f, 0.f,
|
||||
2.f / height, 0.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
-1.f, 1.f, 0.f, 1.f};
|
||||
case ROTATION_90:
|
||||
default:
|
||||
// Landscape
|
||||
return { 0.f, 2.f / width, 0.f, 0.f,
|
||||
-2.f / height, 0.f, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
1.f, -1.f, 0.f, 1.f};
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||
// clang-format off
|
||||
return { 2.f / width, 0.f, 0.f, 0.f,
|
||||
0.f, 2.f / height, 0.f, 0.f,
|
||||
0.f, 0.f, 1.f, 0.f,
|
||||
-1.f, -1.f, 0.f, 1.f};
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
|
||||
using namespace VideoCore::Surface;
|
||||
return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
|
||||
|
@ -291,6 +291,13 @@ void PresentManager::PresentThread(std::stop_token token) {
|
||||
}
|
||||
}
|
||||
|
||||
void PresentManager::NotifySurfaceChanged() {
|
||||
#ifdef ANDROID
|
||||
std::scoped_lock lock{recreate_surface_mutex};
|
||||
recreate_surface_cv.notify_one();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
||||
|
||||
@ -300,6 +307,21 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
};
|
||||
|
||||
#ifdef ANDROID
|
||||
std::unique_lock lock{recreate_surface_mutex};
|
||||
|
||||
const auto needs_recreation = [&] {
|
||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
||||
return true;
|
||||
}
|
||||
if (swapchain.NeedsRecreation(frame->is_srgb)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
|
||||
[&]() { return !needs_recreation(); });
|
||||
|
||||
// If the frontend recreated the surface, recreate the renderer surface and swapchain.
|
||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
||||
last_render_surface = render_window.GetWindowInfo().render_surface;
|
||||
@ -450,7 +472,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||
|
||||
// Submit the image copy/blit to the swapchain
|
||||
{
|
||||
std::scoped_lock lock{scheduler.submit_mutex};
|
||||
std::scoped_lock submit_lock{scheduler.submit_mutex};
|
||||
switch (const VkResult result =
|
||||
device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
|
||||
case VK_SUCCESS:
|
||||
|
@ -55,6 +55,9 @@ public:
|
||||
/// Waits for the present thread to finish presenting all queued frames.
|
||||
void WaitPresent();
|
||||
|
||||
/// This is called to notify the rendering backend of a surface change
|
||||
void NotifySurfaceChanged();
|
||||
|
||||
private:
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
@ -74,7 +77,9 @@ private:
|
||||
std::queue<Frame*> free_queue;
|
||||
std::condition_variable_any frame_cv;
|
||||
std::condition_variable free_cv;
|
||||
std::condition_variable recreate_surface_cv;
|
||||
std::mutex swapchain_mutex;
|
||||
std::mutex recreate_surface_mutex;
|
||||
std::mutex queue_mutex;
|
||||
std::mutex free_mutex;
|
||||
std::jthread present_thread;
|
||||
|
@ -17,7 +17,10 @@ namespace Vulkan {
|
||||
using namespace Common::Literals;
|
||||
|
||||
TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld)
|
||||
: m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device, false} {
|
||||
#ifndef ANDROID
|
||||
: m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device, false}
|
||||
#endif
|
||||
{
|
||||
{
|
||||
std::scoped_lock lk{m_submission_lock};
|
||||
m_submission_time = std::chrono::steady_clock::now();
|
||||
@ -34,6 +37,7 @@ void TurboMode::QueueSubmitted() {
|
||||
}
|
||||
|
||||
void TurboMode::Run(std::stop_token stop_token) {
|
||||
#ifndef ANDROID
|
||||
auto& dld = m_device.GetLogical();
|
||||
|
||||
// Allocate buffer. 2MiB should be sufficient.
|
||||
@ -146,10 +150,13 @@ void TurboMode::Run(std::stop_token stop_token) {
|
||||
// Create a single command buffer.
|
||||
auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
|
||||
auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()};
|
||||
#endif
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#ifdef ANDROID
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
adrenotools_set_turbo(true);
|
||||
#endif
|
||||
#else
|
||||
// Reset the fence.
|
||||
fence.Reset();
|
||||
|
@ -23,8 +23,10 @@ public:
|
||||
private:
|
||||
void Run(std::stop_token stop_token);
|
||||
|
||||
#ifndef ANDROID
|
||||
Device m_device;
|
||||
MemoryAllocator m_allocator;
|
||||
#endif
|
||||
std::mutex m_submission_lock;
|
||||
std::condition_variable_any m_submission_cv;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_submission_time{};
|
||||
|
Loading…
Reference in New Issue
Block a user