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