early-access version 3629
This commit is contained in:
parent
ab55aa0871
commit
5d6ba5745f
@ -1,7 +1,7 @@
|
|||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 3628.
|
This is the source code for early-access 3629.
|
||||||
|
|
||||||
## Legal Notice
|
## 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.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.hardware.Sensor
|
import android.hardware.Sensor
|
||||||
import android.hardware.SensorEvent
|
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.fragments.EmulationFragment
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
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.ForegroundService
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||||
@ -128,6 +130,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
nfcReader.startScanning()
|
nfcReader.startScanning()
|
||||||
startMotionSensorListener()
|
startMotionSensorListener()
|
||||||
|
|
||||||
|
NativeLibrary.notifyOrientationChange(
|
||||||
|
EmulationMenuSettings.landscapeScreenLayout,
|
||||||
|
getAdjustedRotation()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@ -233,6 +240,23 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
|
|
||||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
|
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) {
|
private fun restoreState(savedInstanceState: Bundle) {
|
||||||
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
|
game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
|
||||||
}
|
}
|
||||||
@ -252,39 +276,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
View.SYSTEM_UI_FLAG_IMMERSIVE
|
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() {
|
private fun startMotionSensorListener() {
|
||||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||||
@ -302,22 +293,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
sensorManager.unregisterListener(this, accelSensor)
|
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 {
|
companion object {
|
||||||
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
const val EXTRA_SELECTED_GAME = "SelectedGame"
|
||||||
|
|
||||||
|
@ -111,6 +111,7 @@ class Settings {
|
|||||||
|
|
||||||
const val PREF_OVERLAY_INIT = "OverlayInit"
|
const val PREF_OVERLAY_INIT = "OverlayInit"
|
||||||
const val PREF_CONTROL_SCALE = "controlScale"
|
const val PREF_CONTROL_SCALE = "controlScale"
|
||||||
|
const val PREF_CONTROL_OPACITY = "controlOpacity"
|
||||||
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
const val PREF_TOUCH_ENABLED = "isTouchEnabled"
|
||||||
const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0"
|
const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0"
|
||||||
const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1"
|
const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1"
|
||||||
|
@ -118,12 +118,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
|||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
presenter.onStop(isFinishing)
|
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) {
|
override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -21,10 +23,12 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
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.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
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.databinding.FragmentEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||||
@ -168,14 +172,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshInputOverlay() {
|
private fun refreshInputOverlay() {
|
||||||
binding.surfaceInputOverlay.refreshControls()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetInputOverlay() {
|
private fun resetInputOverlay() {
|
||||||
// Reset button scale
|
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putInt(Settings.PREF_CONTROL_SCALE, 50)
|
.remove(Settings.PREF_CONTROL_SCALE)
|
||||||
|
.remove(Settings.PREF_CONTROL_OPACITY)
|
||||||
.apply()
|
.apply()
|
||||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
|
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() }
|
||||||
}
|
}
|
||||||
@ -251,6 +255,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.menu_adjust_overlay -> {
|
||||||
|
adjustOverlay()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_toggle_controls -> {
|
R.id.menu_toggle_controls -> {
|
||||||
val preferences =
|
val preferences =
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
@ -278,9 +287,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
// Override normal behaviour so the dialog doesn't close
|
// Override normal behaviour so the dialog doesn't close
|
||||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val isChecked = !optionsArray[0];
|
val isChecked = !optionsArray[0]
|
||||||
for (i in 0..14) {
|
for (i in 0..14) {
|
||||||
optionsArray[i] = isChecked;
|
optionsArray[i] = isChecked
|
||||||
dialog.listView.setItemChecked(i, isChecked)
|
dialog.listView.setItemChecked(i, isChecked)
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putBoolean("buttonToggle$i", isChecked)
|
.putBoolean("buttonToggle$i", isChecked)
|
||||||
@ -328,18 +337,64 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startConfiguringControls() {
|
private fun startConfiguringControls() {
|
||||||
binding.doneControlConfig.visibility = View.VISIBLE
|
binding.doneControlConfig.visibility = View.VISIBLE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(true)
|
binding.surfaceInputOverlay.setIsInEditMode(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopConfiguringControls() {
|
private fun stopConfiguringControls() {
|
||||||
binding.doneControlConfig.visibility = View.GONE
|
binding.doneControlConfig.visibility = View.GONE
|
||||||
binding.surfaceInputOverlay.setIsInEditMode(false)
|
binding.surfaceInputOverlay.setIsInEditMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isConfiguringControls: Boolean
|
@SuppressLint("SetTextI18n")
|
||||||
get() = binding.surfaceInputOverlay.isInEditMode
|
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() {
|
private fun setInsets() {
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
|
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
|
||||||
@ -415,8 +470,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
if (state != State.PAUSED) {
|
if (state != State.PAUSED) {
|
||||||
Log.debug("[EmulationFragment] Pausing emulation.")
|
Log.debug("[EmulationFragment] Pausing emulation.")
|
||||||
|
|
||||||
// Release the surface before pausing, since emulation has to be running for that.
|
|
||||||
NativeLibrary.surfaceDestroyed()
|
|
||||||
NativeLibrary.pauseEmulation()
|
NativeLibrary.pauseEmulation()
|
||||||
|
|
||||||
state = State.PAUSED
|
state = State.PAUSED
|
||||||
@ -461,7 +514,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
Log.debug("[EmulationFragment] Surface destroyed.")
|
Log.debug("[EmulationFragment] Surface destroyed.")
|
||||||
when (state) {
|
when (state) {
|
||||||
State.RUNNING -> {
|
State.RUNNING -> {
|
||||||
NativeLibrary.surfaceDestroyed()
|
|
||||||
state = State.PAUSED
|
state = State.PAUSED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
R.drawable.ic_add
|
R.drawable.ic_add
|
||||||
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.import_export_saves,
|
R.string.manage_save_data,
|
||||||
R.string.import_export_saves_description,
|
R.string.import_export_saves_description,
|
||||||
R.drawable.ic_save
|
R.drawable.ic_save
|
||||||
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
|
) { ImportExportSavesFragment().show(parentFragmentManager, ImportExportSavesFragment.TAG) },
|
||||||
|
@ -68,19 +68,21 @@ class ImportExportSavesFragment : DialogFragment() {
|
|||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return if (savesFolderRoot == "") {
|
return if (savesFolderRoot == "") {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.import_export_saves)
|
.setTitle(R.string.manage_save_data)
|
||||||
.setMessage(R.string.import_export_saves_no_profile)
|
.setMessage(R.string.import_export_saves_no_profile)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(R.string.import_export_saves)
|
.setTitle(R.string.manage_save_data)
|
||||||
.setPositiveButton(R.string.export_saves) { _, _ ->
|
.setMessage(R.string.manage_save_data_description)
|
||||||
|
.setNegativeButton(R.string.export_saves) { _, _ ->
|
||||||
exportSave()
|
exportSave()
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.import_saves) { _, _ ->
|
.setPositiveButton(R.string.import_saves) { _, _ ->
|
||||||
documentPicker.launch(arrayOf("application/zip"))
|
documentPicker.launch(arrayOf("application/zip"))
|
||||||
}
|
}
|
||||||
|
.setNeutralButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +97,10 @@ class ImportExportSavesFragment : DialogFragment() {
|
|||||||
tempFolder.mkdirs()
|
tempFolder.mkdirs()
|
||||||
val saveFolder = File(savesFolderRoot)
|
val saveFolder = File(savesFolderRoot)
|
||||||
val outputZipFile = File(
|
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()
|
outputZipFile.createNewFile()
|
||||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
|
||||||
@ -206,11 +211,10 @@ class ImportExportSavesFragment : DialogFragment() {
|
|||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (!validZip) {
|
if (!validZip) {
|
||||||
Toast.makeText(
|
MessageDialogFragment.newInstance(
|
||||||
context,
|
R.string.save_file_invalid_zip_structure,
|
||||||
context.getString(R.string.save_file_invalid_zip_structure),
|
R.string.save_file_invalid_zip_structure_description
|
||||||
Toast.LENGTH_LONG
|
).show(childFragmentManager, MessageDialogFragment.TAG)
|
||||||
).show()
|
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
Toast.makeText(
|
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.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Point
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.VectorDrawable
|
import android.graphics.drawable.VectorDrawable
|
||||||
@ -48,9 +49,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
|
||||||
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
|
||||||
|
|
||||||
private val preferences: SharedPreferences =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
|
||||||
|
|
||||||
private lateinit var windowInsets: WindowInsets
|
private lateinit var windowInsets: WindowInsets
|
||||||
|
|
||||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
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) {
|
private fun addOverlayControls(orientation: String) {
|
||||||
|
val windowSize = getSafeScreenSize(context)
|
||||||
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
|
if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) {
|
||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_a,
|
R.drawable.facebutton_a,
|
||||||
R.drawable.facebutton_a_depressed,
|
R.drawable.facebutton_a_depressed,
|
||||||
ButtonType.BUTTON_A,
|
ButtonType.BUTTON_A,
|
||||||
@ -358,6 +358,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_b,
|
R.drawable.facebutton_b,
|
||||||
R.drawable.facebutton_b_depressed,
|
R.drawable.facebutton_b_depressed,
|
||||||
ButtonType.BUTTON_B,
|
ButtonType.BUTTON_B,
|
||||||
@ -369,6 +370,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_x,
|
R.drawable.facebutton_x,
|
||||||
R.drawable.facebutton_x_depressed,
|
R.drawable.facebutton_x_depressed,
|
||||||
ButtonType.BUTTON_X,
|
ButtonType.BUTTON_X,
|
||||||
@ -380,6 +382,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_y,
|
R.drawable.facebutton_y,
|
||||||
R.drawable.facebutton_y_depressed,
|
R.drawable.facebutton_y_depressed,
|
||||||
ButtonType.BUTTON_Y,
|
ButtonType.BUTTON_Y,
|
||||||
@ -391,6 +394,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.l_shoulder,
|
R.drawable.l_shoulder,
|
||||||
R.drawable.l_shoulder_depressed,
|
R.drawable.l_shoulder_depressed,
|
||||||
ButtonType.TRIGGER_L,
|
ButtonType.TRIGGER_L,
|
||||||
@ -402,6 +406,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.r_shoulder,
|
R.drawable.r_shoulder,
|
||||||
R.drawable.r_shoulder_depressed,
|
R.drawable.r_shoulder_depressed,
|
||||||
ButtonType.TRIGGER_R,
|
ButtonType.TRIGGER_R,
|
||||||
@ -413,6 +418,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.zl_trigger,
|
R.drawable.zl_trigger,
|
||||||
R.drawable.zl_trigger_depressed,
|
R.drawable.zl_trigger_depressed,
|
||||||
ButtonType.TRIGGER_ZL,
|
ButtonType.TRIGGER_ZL,
|
||||||
@ -424,6 +430,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.zr_trigger,
|
R.drawable.zr_trigger,
|
||||||
R.drawable.zr_trigger_depressed,
|
R.drawable.zr_trigger_depressed,
|
||||||
ButtonType.TRIGGER_ZR,
|
ButtonType.TRIGGER_ZR,
|
||||||
@ -435,6 +442,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_plus,
|
R.drawable.facebutton_plus,
|
||||||
R.drawable.facebutton_plus_depressed,
|
R.drawable.facebutton_plus_depressed,
|
||||||
ButtonType.BUTTON_PLUS,
|
ButtonType.BUTTON_PLUS,
|
||||||
@ -446,6 +454,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_minus,
|
R.drawable.facebutton_minus,
|
||||||
R.drawable.facebutton_minus_depressed,
|
R.drawable.facebutton_minus_depressed,
|
||||||
ButtonType.BUTTON_MINUS,
|
ButtonType.BUTTON_MINUS,
|
||||||
@ -457,6 +466,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayDpads.add(
|
overlayDpads.add(
|
||||||
initializeOverlayDpad(
|
initializeOverlayDpad(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.dpad_standard,
|
R.drawable.dpad_standard,
|
||||||
R.drawable.dpad_standard_cardinal_depressed,
|
R.drawable.dpad_standard_cardinal_depressed,
|
||||||
R.drawable.dpad_standard_diagonal_depressed,
|
R.drawable.dpad_standard_diagonal_depressed,
|
||||||
@ -468,6 +478,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayJoysticks.add(
|
overlayJoysticks.add(
|
||||||
initializeOverlayJoystick(
|
initializeOverlayJoystick(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.joystick_range,
|
R.drawable.joystick_range,
|
||||||
R.drawable.joystick,
|
R.drawable.joystick,
|
||||||
R.drawable.joystick_depressed,
|
R.drawable.joystick_depressed,
|
||||||
@ -481,6 +492,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayJoysticks.add(
|
overlayJoysticks.add(
|
||||||
initializeOverlayJoystick(
|
initializeOverlayJoystick(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.joystick_range,
|
R.drawable.joystick_range,
|
||||||
R.drawable.joystick,
|
R.drawable.joystick,
|
||||||
R.drawable.joystick_depressed,
|
R.drawable.joystick_depressed,
|
||||||
@ -494,6 +506,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_home,
|
R.drawable.facebutton_home,
|
||||||
R.drawable.facebutton_home_depressed,
|
R.drawable.facebutton_home_depressed,
|
||||||
ButtonType.BUTTON_HOME,
|
ButtonType.BUTTON_HOME,
|
||||||
@ -505,6 +518,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
overlayButtons.add(
|
overlayButtons.add(
|
||||||
initializeOverlayButton(
|
initializeOverlayButton(
|
||||||
context,
|
context,
|
||||||
|
windowSize,
|
||||||
R.drawable.facebutton_screenshot,
|
R.drawable.facebutton_screenshot,
|
||||||
R.drawable.facebutton_screenshot_depressed,
|
R.drawable.facebutton_screenshot_depressed,
|
||||||
ButtonType.BUTTON_CAPTURE,
|
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) {
|
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()
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||||
.putFloat("$sharedPrefsId$orientation-X", x.toFloat())
|
.putFloat("$sharedPrefsId$orientation-X", (x - min.x).toFloat() / max.x)
|
||||||
.putFloat("$sharedPrefsId$orientation-Y", y.toFloat())
|
.putFloat("$sharedPrefsId$orientation-Y", (y - min.y).toFloat() / max.y)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -557,170 +574,129 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun defaultOverlayLandscape() {
|
private fun defaultOverlayLandscape() {
|
||||||
// Get screen size
|
// Each value represents the position of the button in relation to the screen size without insets.
|
||||||
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.
|
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_A.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_A.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_B.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_B.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_X.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_X.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_Y.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_Y.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZL.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZL.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZR.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_ZR.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.DPAD_UP.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.DPAD_UP.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_L.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_L.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_R.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.TRIGGER_R.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_PLUS.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_PLUS.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_MINUS.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_MINUS.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_HOME.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_HOME.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.BUTTON_CAPTURE.toString() + "-X",
|
ButtonType.BUTTON_CAPTURE.toString() + "-X",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
|
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_X)
|
||||||
.toFloat() / 1000 * maxX + minX
|
.toFloat() / 1000
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
|
ButtonType.BUTTON_CAPTURE.toString() + "-Y",
|
||||||
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
|
resources.getInteger(R.integer.SWITCH_BUTTON_CAPTURE_Y)
|
||||||
.toFloat() / 1000 * maxY + minY
|
.toFloat() / 1000
|
||||||
)
|
)
|
||||||
.putFloat(
|
.putFloat(
|
||||||
ButtonType.STICK_R.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.STICK_R.toString() + "-Y",
|
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(
|
.putFloat(
|
||||||
ButtonType.STICK_L.toString() + "-X",
|
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(
|
.putFloat(
|
||||||
ButtonType.STICK_L.toString() + "-Y",
|
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()
|
.apply()
|
||||||
}
|
}
|
||||||
@ -730,6 +706,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val preferences: SharedPreferences =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes a [Bitmap] by a given scale factor
|
* Resizes a [Bitmap] by a given scale factor
|
||||||
*
|
*
|
||||||
@ -766,6 +745,59 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
return scaledBitmap
|
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
|
* Initializes an InputOverlayDrawableButton, given by resId, with all of the
|
||||||
* parameters set for it to be properly shown on the InputOverlay.
|
* 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.
|
* for Android to call the onDraw method.
|
||||||
*
|
*
|
||||||
* @param context The current [Context].
|
* @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 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 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.
|
* @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(
|
private fun initializeOverlayButton(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
windowSize: Pair<Point, Point>,
|
||||||
defaultResId: Int,
|
defaultResId: Int,
|
||||||
pressedResId: Int,
|
pressedResId: Int,
|
||||||
buttonId: Int,
|
buttonId: Int,
|
||||||
@ -836,12 +870,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
val overlayDrawable =
|
val overlayDrawable =
|
||||||
InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId)
|
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.
|
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val xKey = "$buttonId$orientation-X"
|
val xKey = "$buttonId$orientation-X"
|
||||||
val yKey = "$buttonId$orientation-Y"
|
val yKey = "$buttonId$orientation-Y"
|
||||||
val drawableX = sPrefs.getFloat(xKey, 0f).toInt()
|
val drawableXPercent = sPrefs.getFloat(xKey, 0f)
|
||||||
val drawableY = sPrefs.getFloat(yKey, 0f).toInt()
|
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 width = overlayDrawable.width
|
||||||
val height = overlayDrawable.height
|
val height = overlayDrawable.height
|
||||||
|
|
||||||
@ -859,6 +899,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
drawableX - (width / 2),
|
drawableX - (width / 2),
|
||||||
drawableY - (height / 2)
|
drawableY - (height / 2)
|
||||||
)
|
)
|
||||||
|
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||||
|
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||||
return overlayDrawable
|
return overlayDrawable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,6 +908,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
* Initializes an [InputOverlayDrawableDpad]
|
* Initializes an [InputOverlayDrawableDpad]
|
||||||
*
|
*
|
||||||
* @param context The current [Context].
|
* @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 defaultResId The [Bitmap] resource ID of the default state.
|
||||||
* @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
|
* @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.
|
* @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(
|
private fun initializeOverlayDpad(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
windowSize: Pair<Point, Point>,
|
||||||
defaultResId: Int,
|
defaultResId: Int,
|
||||||
pressedOneDirectionResId: Int,
|
pressedOneDirectionResId: Int,
|
||||||
pressedTwoDirectionsResId: Int,
|
pressedTwoDirectionsResId: Int,
|
||||||
@ -907,10 +951,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
ButtonType.DPAD_RIGHT
|
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.
|
// The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val drawableX = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f).toInt()
|
val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-X", 0f)
|
||||||
val drawableY = sPrefs.getFloat("${ButtonType.DPAD_UP}$orientation-Y", 0f).toInt()
|
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 width = overlayDrawable.width
|
||||||
val height = overlayDrawable.height
|
val height = overlayDrawable.height
|
||||||
|
|
||||||
@ -925,6 +975,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
|
|
||||||
// Need to set the image's position
|
// Need to set the image's position
|
||||||
overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
|
overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
|
||||||
|
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||||
|
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||||
return overlayDrawable
|
return overlayDrawable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -932,6 +984,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
* Initializes an [InputOverlayDrawableJoystick]
|
* Initializes an [InputOverlayDrawableJoystick]
|
||||||
*
|
*
|
||||||
* @param context The current [Context]
|
* @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 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 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.
|
* @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(
|
private fun initializeOverlayJoystick(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
windowSize: Pair<Point, Point>,
|
||||||
resOuter: Int,
|
resOuter: Int,
|
||||||
defaultResInner: Int,
|
defaultResInner: Int,
|
||||||
pressedResInner: Int,
|
pressedResInner: Int,
|
||||||
@ -964,10 +1018,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
|||||||
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f)
|
||||||
val bitmapInnerPressed = getBitmap(context, pressedResInner, 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.
|
// The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
|
||||||
// These were set in the input overlay configuration menu.
|
// These were set in the input overlay configuration menu.
|
||||||
val drawableX = sPrefs.getFloat("$button$orientation-X", 0f).toInt()
|
val drawableXPercent = sPrefs.getFloat("$button$orientation-X", 0f)
|
||||||
val drawableY = sPrefs.getFloat("$button$orientation-Y", 0f).toInt()
|
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
|
val outerScale = 1.66f
|
||||||
|
|
||||||
// Now set the bounds for the InputOverlayDrawableJoystick.
|
// 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
|
// Need to set the image's position
|
||||||
overlayDrawable.setPosition(drawableX, drawableY)
|
overlayDrawable.setPosition(drawableX, drawableY)
|
||||||
|
val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
|
||||||
|
overlayDrawable.setOpacity(savedOpacity * 255 / 100)
|
||||||
return overlayDrawable
|
return overlayDrawable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,7 @@ class InputOverlayDrawableButton(
|
|||||||
controlPositionX = fingerPositionX - (width / 2)
|
controlPositionX = fingerPositionX - (width / 2)
|
||||||
controlPositionY = fingerPositionY - (height / 2)
|
controlPositionY = fingerPositionY - (height / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
controlPositionX += fingerPositionX - previousTouchX
|
controlPositionX += fingerPositionX - previousTouchX
|
||||||
controlPositionY += fingerPositionY - previousTouchY
|
controlPositionY += fingerPositionY - previousTouchY
|
||||||
@ -135,6 +136,11 @@ class InputOverlayDrawableButton(
|
|||||||
pressedStateBitmap.setBounds(left, top, right, bottom)
|
pressedStateBitmap.setBounds(left, top, right, bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setOpacity(value: Int) {
|
||||||
|
defaultStateBitmap.alpha = value
|
||||||
|
pressedStateBitmap.alpha = value
|
||||||
|
}
|
||||||
|
|
||||||
val status: Int
|
val status: Int
|
||||||
get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED
|
get() = if (pressedState) ButtonState.PRESSED else ButtonState.RELEASED
|
||||||
val bounds: Rect
|
val bounds: Rect
|
||||||
|
@ -231,6 +231,7 @@ class InputOverlayDrawableDpad(
|
|||||||
previousTouchX = fingerPositionX
|
previousTouchX = fingerPositionX
|
||||||
previousTouchY = fingerPositionY
|
previousTouchY = fingerPositionY
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
controlPositionX += fingerPositionX - previousTouchX
|
controlPositionX += fingerPositionX - previousTouchX
|
||||||
controlPositionY += fingerPositionY - previousTouchY
|
controlPositionY += fingerPositionY - previousTouchY
|
||||||
@ -258,6 +259,12 @@ class InputOverlayDrawableDpad(
|
|||||||
pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom)
|
pressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setOpacity(value: Int) {
|
||||||
|
defaultStateBitmap.alpha = value
|
||||||
|
pressedOneDirectionStateBitmap.alpha = value
|
||||||
|
pressedTwoDirectionsStateBitmap.alpha = value
|
||||||
|
}
|
||||||
|
|
||||||
val bounds: Rect
|
val bounds: Rect
|
||||||
get() = defaultStateBitmap.bounds
|
get() = defaultStateBitmap.bounds
|
||||||
|
|
||||||
|
@ -48,6 +48,8 @@ class InputOverlayDrawableJoystick(
|
|||||||
val width: Int
|
val width: Int
|
||||||
val height: Int
|
val height: Int
|
||||||
|
|
||||||
|
private var opacity: Int = 0
|
||||||
|
|
||||||
private var virtBounds: Rect
|
private var virtBounds: Rect
|
||||||
private var origBounds: Rect
|
private var origBounds: Rect
|
||||||
|
|
||||||
@ -121,7 +123,7 @@ class InputOverlayDrawableJoystick(
|
|||||||
}
|
}
|
||||||
pressedState = true
|
pressedState = true
|
||||||
outerBitmap.alpha = 0
|
outerBitmap.alpha = 0
|
||||||
boundsBoxBitmap.alpha = 255
|
boundsBoxBitmap.alpha = opacity
|
||||||
if (EmulationMenuSettings.joystickRelCenter) {
|
if (EmulationMenuSettings.joystickRelCenter) {
|
||||||
virtBounds.offset(
|
virtBounds.offset(
|
||||||
xPosition - virtBounds.centerX(),
|
xPosition - virtBounds.centerX(),
|
||||||
@ -139,7 +141,7 @@ class InputOverlayDrawableJoystick(
|
|||||||
pressedState = false
|
pressedState = false
|
||||||
xAxis = 0.0f
|
xAxis = 0.0f
|
||||||
yAxis = 0.0f
|
yAxis = 0.0f
|
||||||
outerBitmap.alpha = 255
|
outerBitmap.alpha = opacity
|
||||||
boundsBoxBitmap.alpha = 0
|
boundsBoxBitmap.alpha = 0
|
||||||
virtBounds = Rect(
|
virtBounds = Rect(
|
||||||
origBounds.left,
|
origBounds.left,
|
||||||
@ -203,6 +205,7 @@ class InputOverlayDrawableJoystick(
|
|||||||
controlPositionX = fingerPositionX - (width / 2)
|
controlPositionX = fingerPositionX - (width / 2)
|
||||||
controlPositionY = fingerPositionY - (height / 2)
|
controlPositionY = fingerPositionY - (height / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
MotionEvent.ACTION_MOVE -> {
|
||||||
controlPositionX += fingerPositionX - previousTouchX
|
controlPositionX += fingerPositionX - previousTouchX
|
||||||
controlPositionY += fingerPositionY - previousTouchY
|
controlPositionY += fingerPositionY - previousTouchY
|
||||||
@ -261,4 +264,19 @@ class InputOverlayDrawableJoystick(
|
|||||||
controlPositionX = x
|
controlPositionX = x
|
||||||
controlPositionY = y
|
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.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
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.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
@ -251,11 +252,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
if (result == null)
|
if (result == null)
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
result,
|
result,
|
||||||
takeFlags
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
)
|
)
|
||||||
|
|
||||||
// When a new directory is picked, we currently will reset the existing games
|
// When a new directory is picked, we currently will reset the existing games
|
||||||
@ -279,19 +278,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
|
|
||||||
if (!FileUtil.hasExtension(result.toString(), "keys")) {
|
if (!FileUtil.hasExtension(result.toString(), "keys")) {
|
||||||
Toast.makeText(
|
MessageDialogFragment.newInstance(
|
||||||
applicationContext,
|
R.string.reading_keys_failure,
|
||||||
R.string.invalid_keys_file,
|
R.string.install_keys_failure_extension_description
|
||||||
Toast.LENGTH_SHORT
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
).show()
|
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
result,
|
result,
|
||||||
takeFlags
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
)
|
)
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
@ -310,11 +306,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
).show()
|
).show()
|
||||||
gamesViewModel.reloadGames(true)
|
gamesViewModel.reloadGames(true)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
MessageDialogFragment.newInstance(
|
||||||
applicationContext,
|
R.string.invalid_keys_error,
|
||||||
R.string.install_keys_failure,
|
R.string.install_keys_failure_description,
|
||||||
Toast.LENGTH_LONG
|
R.string.dumping_keys_quickstart_link
|
||||||
).show()
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,19 +321,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
|
|
||||||
if (!FileUtil.hasExtension(result.toString(), "bin")) {
|
if (!FileUtil.hasExtension(result.toString(), "bin")) {
|
||||||
Toast.makeText(
|
MessageDialogFragment.newInstance(
|
||||||
applicationContext,
|
R.string.reading_keys_failure,
|
||||||
R.string.invalid_keys_file,
|
R.string.install_keys_failure_extension_description
|
||||||
Toast.LENGTH_SHORT
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
).show()
|
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
result,
|
result,
|
||||||
takeFlags
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
)
|
)
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
@ -355,11 +348,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
MessageDialogFragment.newInstance(
|
||||||
applicationContext,
|
R.string.invalid_keys_error,
|
||||||
R.string.install_amiibo_keys_failure,
|
R.string.install_keys_failure_description,
|
||||||
Toast.LENGTH_LONG
|
R.string.dumping_keys_quickstart_link
|
||||||
).show()
|
).show(supportFragmentManager, MessageDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,11 @@ object GpuDriverHelper {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Unzip the driver.
|
// Unzip the driver.
|
||||||
unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
|
try {
|
||||||
|
unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the driver parameters.
|
// Initialize the driver parameters.
|
||||||
initializeDriverParameters(context)
|
initializeDriverParameters(context)
|
||||||
|
@ -89,8 +89,16 @@ public:
|
|||||||
return m_native_window;
|
return m_native_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNativeWindow(ANativeWindow* m_native_window_) {
|
void SetNativeWindow(ANativeWindow* native_window) {
|
||||||
m_native_window = m_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,
|
void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
|
||||||
@ -140,6 +148,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_window->OnSurfaceChanged(m_native_window);
|
m_window->OnSurfaceChanged(m_native_window);
|
||||||
|
m_system.Renderer().NotifySurfaceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
||||||
@ -379,6 +388,7 @@ private:
|
|||||||
// Window management
|
// Window management
|
||||||
std::unique_ptr<EmuWindow_Android> m_window;
|
std::unique_ptr<EmuWindow_Android> m_window;
|
||||||
ANativeWindow* m_native_window{};
|
ANativeWindow* m_native_window{};
|
||||||
|
u32 m_screen_rotation{};
|
||||||
|
|
||||||
// Core emulation
|
// Core emulation
|
||||||
Core::System m_system;
|
Core::System m_system;
|
||||||
@ -404,6 +414,10 @@ private:
|
|||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
|
u32 GetAndroidScreenRotation() {
|
||||||
|
return EmulationSession::GetInstance().ScreenRotation();
|
||||||
|
}
|
||||||
|
|
||||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||||
Common::Log::Initialize();
|
Common::Log::Initialize();
|
||||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
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,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_notifyOrientationChange(JNIEnv* env,
|
||||||
[[maybe_unused]] jclass clazz,
|
[[maybe_unused]] jclass clazz,
|
||||||
jint layout_option,
|
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,
|
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setAppDirectory(JNIEnv* env,
|
||||||
[[maybe_unused]] jclass clazz,
|
[[maybe_unused]] jclass clazz,
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image_game_screen"
|
android:id="@+id/image_game_screen"
|
||||||
android:layout_width="match_parent"
|
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>
|
</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:id="@+id/menu_edit_overlay"
|
||||||
android:title="@string/emulation_touch_overlay_edit" />
|
android:title="@string/emulation_touch_overlay_edit" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_adjust_overlay"
|
||||||
|
android:title="@string/emulation_control_adjust" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_toggle_controls"
|
android:id="@+id/menu_toggle_controls"
|
||||||
android:title="@string/emulation_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="install_amiibo_keys_description">Required to use Amiibo in game</string>
|
||||||
<string name="invalid_keys_file">Invalid keys file selected</string>
|
<string name="invalid_keys_file">Invalid keys file selected</string>
|
||||||
<string name="install_keys_success">Keys successfully installed</string>
|
<string name="install_keys_success">Keys successfully installed</string>
|
||||||
<string name="install_keys_failure">Keys file (prod.keys) is invalid</string>
|
<string name="reading_keys_failure">Error reading encryption keys</string>
|
||||||
<string name="install_amiibo_keys_failure">Keys file (key_retail.bin) is invalid</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">Install GPU driver</string>
|
||||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||||
<string name="advanced_settings">Advanced settings</string>
|
<string name="advanced_settings">Advanced settings</string>
|
||||||
@ -80,11 +87,13 @@
|
|||||||
<string name="no_file_manager">No file manager found</string>
|
<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">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="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_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="import_export_saves_no_profile">No save data found. Please launch a game and retry.</string>
|
||||||
<string name="save_file_imported_success">Save files were imported successfully</string>
|
<string name="save_file_imported_success">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="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="import_saves">Import</string>
|
||||||
<string name="export_saves">Export</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">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="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="settings_reset">Settings reset</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
|
<string name="learn_more">Learn More</string>
|
||||||
|
|
||||||
<!-- GPU driver installation -->
|
<!-- GPU driver installation -->
|
||||||
<string name="select_gpu_driver">Select GPU driver</string>
|
<string name="select_gpu_driver">Select GPU driver</string>
|
||||||
@ -204,12 +215,14 @@
|
|||||||
<string name="emulation_haptics">Haptics</string>
|
<string name="emulation_haptics">Haptics</string>
|
||||||
<string name="emulation_show_overlay">Show Overlay</string>
|
<string name="emulation_show_overlay">Show Overlay</string>
|
||||||
<string name="emulation_toggle_all">Toggle All</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_reset">Reset Overlay</string>
|
||||||
<string name="emulation_touch_overlay_edit">Edit Overlay</string>
|
<string name="emulation_touch_overlay_edit">Edit Overlay</string>
|
||||||
<string name="emulation_pause">Pause Emulation</string>
|
<string name="emulation_pause">Pause Emulation</string>
|
||||||
<string name="emulation_unpause">Unpause 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="emulation_game_loading">Game loading…</string>
|
||||||
|
|
||||||
<string name="load_settings">Loading Settings…</string>
|
<string name="load_settings">Loading Settings…</string>
|
||||||
|
@ -89,6 +89,9 @@ public:
|
|||||||
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
void RequestScreenshot(void* data, std::function<void(bool)> callback,
|
||||||
const Layout::FramebufferLayout& layout);
|
const Layout::FramebufferLayout& layout);
|
||||||
|
|
||||||
|
/// This is called to notify the rendering backend of a surface change
|
||||||
|
virtual void NotifySurfaceChanged() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||||
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
std::unique_ptr<Core::Frontend::GraphicsContext> context;
|
||||||
|
@ -54,6 +54,10 @@ public:
|
|||||||
return device.GetDriverName();
|
return device.GetDriverName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotifySurfaceChanged() override {
|
||||||
|
present_manager.NotifySurfaceChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Report() const;
|
void Report() const;
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@
|
|||||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
extern u32 GetAndroidScreenRotation();
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -74,23 +78,58 @@ struct ScreenRectVertex {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
|
||||||
// clang-format off
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
// Android renders in portrait, so rotate the matrix.
|
|
||||||
return { 0.f, 2.f / width, 0.f, 0.f,
|
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||||
-2.f / height, 0.f, 0.f, 0.f,
|
constexpr u32 ROTATION_0 = 0;
|
||||||
0.f, 0.f, 1.f, 0.f,
|
constexpr u32 ROTATION_90 = 1;
|
||||||
1.f, -1.f, 0.f, 1.f};
|
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};
|
||||||
|
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
|
#else
|
||||||
|
|
||||||
|
std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
|
||||||
|
// clang-format off
|
||||||
return { 2.f / width, 0.f, 0.f, 0.f,
|
return { 2.f / width, 0.f, 0.f, 0.f,
|
||||||
0.f, 2.f / height, 0.f, 0.f,
|
0.f, 2.f / height, 0.f, 0.f,
|
||||||
0.f, 0.f, 1.f, 0.f,
|
0.f, 0.f, 1.f, 0.f,
|
||||||
-1.f, -1.f, 0.f, 1.f};
|
-1.f, -1.f, 0.f, 1.f};
|
||||||
#endif // ANDROID
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
|
u32 GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
|
||||||
using namespace VideoCore::Surface;
|
using namespace VideoCore::Surface;
|
||||||
return BytesPerBlock(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
|
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) {
|
void PresentManager::CopyToSwapchain(Frame* frame) {
|
||||||
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
|
||||||
|
|
||||||
@ -300,6 +307,21 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#ifdef ANDROID
|
#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 the frontend recreated the surface, recreate the renderer surface and swapchain.
|
||||||
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
if (last_render_surface != render_window.GetWindowInfo().render_surface) {
|
||||||
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
|
// 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 =
|
switch (const VkResult result =
|
||||||
device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
|
device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
|
||||||
case VK_SUCCESS:
|
case VK_SUCCESS:
|
||||||
|
@ -55,6 +55,9 @@ public:
|
|||||||
/// Waits for the present thread to finish presenting all queued frames.
|
/// Waits for the present thread to finish presenting all queued frames.
|
||||||
void WaitPresent();
|
void WaitPresent();
|
||||||
|
|
||||||
|
/// This is called to notify the rendering backend of a surface change
|
||||||
|
void NotifySurfaceChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PresentThread(std::stop_token token);
|
void PresentThread(std::stop_token token);
|
||||||
|
|
||||||
@ -74,7 +77,9 @@ private:
|
|||||||
std::queue<Frame*> free_queue;
|
std::queue<Frame*> free_queue;
|
||||||
std::condition_variable_any frame_cv;
|
std::condition_variable_any frame_cv;
|
||||||
std::condition_variable free_cv;
|
std::condition_variable free_cv;
|
||||||
|
std::condition_variable recreate_surface_cv;
|
||||||
std::mutex swapchain_mutex;
|
std::mutex swapchain_mutex;
|
||||||
|
std::mutex recreate_surface_mutex;
|
||||||
std::mutex queue_mutex;
|
std::mutex queue_mutex;
|
||||||
std::mutex free_mutex;
|
std::mutex free_mutex;
|
||||||
std::jthread present_thread;
|
std::jthread present_thread;
|
||||||
|
@ -17,7 +17,10 @@ namespace Vulkan {
|
|||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
|
|
||||||
TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld)
|
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};
|
std::scoped_lock lk{m_submission_lock};
|
||||||
m_submission_time = std::chrono::steady_clock::now();
|
m_submission_time = std::chrono::steady_clock::now();
|
||||||
@ -34,6 +37,7 @@ void TurboMode::QueueSubmitted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TurboMode::Run(std::stop_token stop_token) {
|
void TurboMode::Run(std::stop_token stop_token) {
|
||||||
|
#ifndef ANDROID
|
||||||
auto& dld = m_device.GetLogical();
|
auto& dld = m_device.GetLogical();
|
||||||
|
|
||||||
// Allocate buffer. 2MiB should be sufficient.
|
// Allocate buffer. 2MiB should be sufficient.
|
||||||
@ -146,10 +150,13 @@ void TurboMode::Run(std::stop_token stop_token) {
|
|||||||
// Create a single command buffer.
|
// Create a single command buffer.
|
||||||
auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
|
auto cmdbufs = command_pool.Allocate(1, VK_COMMAND_BUFFER_LEVEL_PRIMARY);
|
||||||
auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()};
|
auto cmdbuf = vk::CommandBuffer{cmdbufs[0], m_device.GetDispatchLoader()};
|
||||||
|
#endif
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
while (!stop_token.stop_requested()) {
|
||||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
#ifdef ANDROID
|
||||||
|
#ifdef ARCHITECTURE_arm64
|
||||||
adrenotools_set_turbo(true);
|
adrenotools_set_turbo(true);
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
// Reset the fence.
|
// Reset the fence.
|
||||||
fence.Reset();
|
fence.Reset();
|
||||||
|
@ -23,8 +23,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
void Run(std::stop_token stop_token);
|
void Run(std::stop_token stop_token);
|
||||||
|
|
||||||
|
#ifndef ANDROID
|
||||||
Device m_device;
|
Device m_device;
|
||||||
MemoryAllocator m_allocator;
|
MemoryAllocator m_allocator;
|
||||||
|
#endif
|
||||||
std::mutex m_submission_lock;
|
std::mutex m_submission_lock;
|
||||||
std::condition_variable_any m_submission_cv;
|
std::condition_variable_any m_submission_cv;
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_submission_time{};
|
std::chrono::time_point<std::chrono::steady_clock> m_submission_time{};
|
||||||
|
Loading…
Reference in New Issue
Block a user