Merge pull request #11405 from t895/emulation-loading
android: Emulation loading UI and fixes
This commit is contained in:
commit
a2f0caefd4
28 changed files with 392 additions and 236 deletions
|
@ -22,9 +22,7 @@ import org.yuzu.yuzu_emu.utils.FileUtil.exists
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
|
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
||||||
import org.yuzu.yuzu_emu.utils.Log.error
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.Log.verbose
|
|
||||||
import org.yuzu.yuzu_emu.utils.Log.warning
|
|
||||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,7 +463,7 @@ object NativeLibrary {
|
||||||
|
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
warning("[NativeLibrary] EmulationActivity is null, can't exit.")
|
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,15 +488,27 @@ object NativeLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||||
verbose("[NativeLibrary] Registering EmulationActivity.")
|
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
||||||
sEmulationActivity = WeakReference(emulationActivity)
|
sEmulationActivity = WeakReference(emulationActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearEmulationActivity() {
|
fun clearEmulationActivity() {
|
||||||
verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
||||||
sEmulationActivity.clear()
|
sEmulationActivity.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun onEmulationStarted() {
|
||||||
|
sEmulationActivity.get()!!.onEmulationStarted()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@JvmStatic
|
||||||
|
fun onEmulationStopped(status: Int) {
|
||||||
|
sEmulationActivity.get()!!.onEmulationStopped(status)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the Yuzu version, Android version and, CPU.
|
* Logs the Yuzu version, Android version and, CPU.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,6 +28,7 @@ import android.view.Surface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
@ -41,6 +42,7 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
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.ForegroundService
|
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||||
|
@ -70,8 +72,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||||
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
|
||||||
|
|
||||||
|
private val emulationViewModel: EmulationViewModel by viewModels()
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
stopForegroundService(this)
|
stopForegroundService(this)
|
||||||
|
emulationViewModel.clear()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +421,16 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onEmulationStarted() {
|
||||||
|
emulationViewModel.setEmulationStarted(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onEmulationStopped(status: Int) {
|
||||||
|
if (status == 0) {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.adapters
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -15,23 +13,20 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.load
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
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.adapters.GameAdapter.GameViewHolder
|
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
|
||||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
|
import org.yuzu.yuzu_emu.utils.GameIconUtils
|
||||||
|
|
||||||
class GameAdapter(private val activity: AppCompatActivity) :
|
class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||||
|
@ -98,12 +93,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
this.game = game
|
this.game = game
|
||||||
|
|
||||||
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
|
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
activity.lifecycleScope.launch {
|
GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
|
||||||
val bitmap = decodeGameIcon(game.path)
|
|
||||||
binding.imageGameScreen.load(bitmap) {
|
|
||||||
error(R.drawable.default_icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
|
||||||
|
|
||||||
|
@ -126,14 +116,4 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decodeGameIcon(uri: String): Bitmap? {
|
|
||||||
val data = NativeLibrary.getIcon(uri)
|
|
||||||
return BitmapFactory.decodeByteArray(
|
|
||||||
data,
|
|
||||||
0,
|
|
||||||
data.size,
|
|
||||||
BitmapFactory.Options()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,43 +4,43 @@
|
||||||
package org.yuzu.yuzu_emu.disk_shader_cache
|
package org.yuzu.yuzu_emu.disk_shader_cache
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
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.disk_shader_cache.ui.ShaderProgressDialogFragment
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
object DiskShaderCacheProgress {
|
object DiskShaderCacheProgress {
|
||||||
val finishLock = Object()
|
private lateinit var emulationViewModel: EmulationViewModel
|
||||||
private lateinit var fragment: ShaderProgressDialogFragment
|
|
||||||
|
|
||||||
private fun prepareDialog() {
|
private fun prepareViewModel() {
|
||||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
|
emulationViewModel =
|
||||||
emulationActivity.runOnUiThread {
|
ViewModelProvider(
|
||||||
fragment = ShaderProgressDialogFragment.newInstance(
|
NativeLibrary.sEmulationActivity.get() as EmulationActivity
|
||||||
emulationActivity.getString(R.string.loading),
|
)[EmulationViewModel::class.java]
|
||||||
emulationActivity.getString(R.string.preparing_shaders)
|
|
||||||
)
|
|
||||||
fragment.show(
|
|
||||||
emulationActivity.supportFragmentManager,
|
|
||||||
ShaderProgressDialogFragment.TAG
|
|
||||||
)
|
|
||||||
}
|
|
||||||
synchronized(finishLock) { finishLock.wait() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun loadProgress(stage: Int, progress: Int, max: Int) {
|
fun loadProgress(stage: Int, progress: Int, max: Int) {
|
||||||
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
val emulationActivity = NativeLibrary.sEmulationActivity.get()
|
||||||
?: error("[DiskShaderCacheProgress] EmulationActivity not present")
|
if (emulationActivity == null) {
|
||||||
|
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
when (LoadCallbackStage.values()[stage]) {
|
emulationActivity.runOnUiThread {
|
||||||
LoadCallbackStage.Prepare -> prepareDialog()
|
when (LoadCallbackStage.values()[stage]) {
|
||||||
LoadCallbackStage.Build -> fragment.onUpdateProgress(
|
LoadCallbackStage.Prepare -> prepareViewModel()
|
||||||
emulationActivity.getString(R.string.building_shaders),
|
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
|
||||||
progress,
|
emulationActivity.getString(R.string.building_shaders),
|
||||||
max
|
progress,
|
||||||
)
|
max
|
||||||
LoadCallbackStage.Complete -> fragment.dismiss()
|
)
|
||||||
|
|
||||||
|
LoadCallbackStage.Complete -> {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.disk_shader_cache
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class ShaderProgressViewModel : ViewModel() {
|
|
||||||
private val _progress = MutableLiveData(0)
|
|
||||||
val progress: LiveData<Int> get() = _progress
|
|
||||||
|
|
||||||
private val _max = MutableLiveData(0)
|
|
||||||
val max: LiveData<Int> get() = _max
|
|
||||||
|
|
||||||
private val _message = MutableLiveData("")
|
|
||||||
val message: LiveData<String> get() = _message
|
|
||||||
|
|
||||||
fun setProgress(progress: Int) {
|
|
||||||
_progress.postValue(progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMax(max: Int) {
|
|
||||||
_max.postValue(max)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setMessage(msg: String) {
|
|
||||||
_message.postValue(msg)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.disk_shader_cache.ui
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.fragment.app.DialogFragment
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
|
||||||
import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress
|
|
||||||
import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel
|
|
||||||
|
|
||||||
class ShaderProgressDialogFragment : DialogFragment() {
|
|
||||||
private var _binding: DialogProgressBarBinding? = null
|
|
||||||
private val binding get() = _binding!!
|
|
||||||
|
|
||||||
private lateinit var alertDialog: AlertDialog
|
|
||||||
|
|
||||||
private lateinit var shaderProgressViewModel: ShaderProgressViewModel
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
||||||
_binding = DialogProgressBarBinding.inflate(layoutInflater)
|
|
||||||
shaderProgressViewModel =
|
|
||||||
ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java]
|
|
||||||
|
|
||||||
val title = requireArguments().getString(TITLE)
|
|
||||||
val message = requireArguments().getString(MESSAGE)
|
|
||||||
|
|
||||||
isCancelable = false
|
|
||||||
alertDialog = MaterialAlertDialogBuilder(requireActivity())
|
|
||||||
.setView(binding.root)
|
|
||||||
.setTitle(title)
|
|
||||||
.setMessage(message)
|
|
||||||
.create()
|
|
||||||
return alertDialog
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View {
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress ->
|
|
||||||
binding.progressBar.progress = progress
|
|
||||||
setUpdateText()
|
|
||||||
}
|
|
||||||
shaderProgressViewModel.max.observe(viewLifecycleOwner) { max ->
|
|
||||||
binding.progressBar.max = max
|
|
||||||
setUpdateText()
|
|
||||||
}
|
|
||||||
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
|
|
||||||
alertDialog.setMessage(msg)
|
|
||||||
}
|
|
||||||
synchronized(DiskShaderCacheProgress.finishLock) {
|
|
||||||
DiskShaderCacheProgress.finishLock.notifyAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
|
||||||
super.onDestroyView()
|
|
||||||
_binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onUpdateProgress(msg: String, progress: Int, max: Int) {
|
|
||||||
shaderProgressViewModel.setProgress(progress)
|
|
||||||
shaderProgressViewModel.setMax(max)
|
|
||||||
shaderProgressViewModel.setMessage(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setUpdateText() {
|
|
||||||
binding.progressText.text = String.format(
|
|
||||||
"%d/%d",
|
|
||||||
shaderProgressViewModel.progress.value,
|
|
||||||
shaderProgressViewModel.max.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "ProgressDialogFragment"
|
|
||||||
const val TITLE = "title"
|
|
||||||
const val MESSAGE = "message"
|
|
||||||
|
|
||||||
fun newInstance(title: String, message: String): ShaderProgressDialogFragment {
|
|
||||||
val frag = ShaderProgressDialogFragment()
|
|
||||||
val args = Bundle()
|
|
||||||
args.putString(TITLE, title)
|
|
||||||
args.putString(MESSAGE, message)
|
|
||||||
frag.arguments = args
|
|
||||||
return frag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,8 +24,9 @@ import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
|
|
||||||
|
@ -66,6 +68,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
private lateinit var game: Game
|
private lateinit var game: Game
|
||||||
|
|
||||||
|
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||||
|
|
||||||
private var isInFoldableLayout = false
|
private var isInFoldableLayout = false
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
|
@ -130,9 +134,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
binding.showFpsText.setTextColor(Color.YELLOW)
|
binding.showFpsText.setTextColor(Color.YELLOW)
|
||||||
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
|
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
|
||||||
|
|
||||||
// Setup overlay.
|
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
updateShowFpsOverlay()
|
|
||||||
|
|
||||||
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
|
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
|
||||||
game.title
|
game.title
|
||||||
binding.inGameMenu.setNavigationItemSelectedListener {
|
binding.inGameMenu.setNavigationItemSelectedListener {
|
||||||
|
@ -174,7 +176,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
R.id.menu_exit -> {
|
R.id.menu_exit -> {
|
||||||
emulationState.stop()
|
emulationState.stop()
|
||||||
requireActivity().finish()
|
emulationViewModel.setIsEmulationStopping(true)
|
||||||
|
binding.drawerLayout.close()
|
||||||
|
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +192,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
object : OnBackPressedCallback(true) {
|
object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
|
if (!NativeLibrary.isRunning()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (binding.drawerLayout.isOpen) {
|
if (binding.drawerLayout.isOpen) {
|
||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,6 +212,54 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameIconUtils.loadGameIcon(game, binding.loadingImage)
|
||||||
|
binding.loadingTitle.text = game.title
|
||||||
|
binding.loadingTitle.isSelected = true
|
||||||
|
binding.loadingText.isSelected = true
|
||||||
|
|
||||||
|
emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
|
||||||
|
if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
|
||||||
|
binding.loadingProgressIndicator.isIndeterminate = false
|
||||||
|
|
||||||
|
if (it < binding.loadingProgressIndicator.max) {
|
||||||
|
binding.loadingProgressIndicator.progress = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == emulationViewModel.totalShaders.value!!) {
|
||||||
|
binding.loadingText.setText(R.string.loading)
|
||||||
|
binding.loadingProgressIndicator.isIndeterminate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
|
||||||
|
binding.loadingProgressIndicator.max = it
|
||||||
|
}
|
||||||
|
emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
|
||||||
|
if (it.isNotEmpty()) {
|
||||||
|
binding.loadingText.text = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
|
||||||
|
if (started) {
|
||||||
|
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||||
|
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||||
|
ViewUtils.hideView(binding.loadingIndicator)
|
||||||
|
|
||||||
|
// Setup overlay
|
||||||
|
updateShowFpsOverlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
|
||||||
|
if (it) {
|
||||||
|
binding.loadingText.setText(R.string.shutting_down)
|
||||||
|
ViewUtils.showView(binding.loadingIndicator)
|
||||||
|
ViewUtils.hideView(binding.inputContainer)
|
||||||
|
ViewUtils.hideView(binding.showFpsText)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
@ -213,11 +269,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
}
|
}
|
||||||
if (EmulationMenuSettings.showOverlay) {
|
if (EmulationMenuSettings.showOverlay) {
|
||||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
|
binding.surfaceInputOverlay.post {
|
||||||
|
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (EmulationMenuSettings.showOverlay) {
|
if (EmulationMenuSettings.showOverlay &&
|
||||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
|
emulationViewModel.emulationStarted.value == true
|
||||||
|
) {
|
||||||
|
binding.surfaceInputOverlay.post {
|
||||||
|
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.surfaceInputOverlay.post {
|
||||||
|
binding.surfaceInputOverlay.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!isInFoldableLayout) {
|
if (!isInFoldableLayout) {
|
||||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
@ -226,9 +292,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
|
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!binding.surfaceInputOverlay.isInEditMode) {
|
|
||||||
refreshInputOverlay()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,10 +323,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
super.onDetach()
|
super.onDetach()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshInputOverlay() {
|
|
||||||
binding.surfaceInputOverlay.refreshControls()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetInputOverlay() {
|
private fun resetInputOverlay() {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.remove(Settings.PREF_CONTROL_SCALE)
|
.remove(Settings.PREF_CONTROL_SCALE)
|
||||||
|
@ -281,17 +340,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
val FRAMETIME = 2
|
val FRAMETIME = 2
|
||||||
val SPEED = 3
|
val SPEED = 3
|
||||||
perfStatsUpdater = {
|
perfStatsUpdater = {
|
||||||
val perfStats = NativeLibrary.getPerfStats()
|
if (emulationViewModel.emulationStarted.value == true) {
|
||||||
if (perfStats[FPS] > 0 && _binding != null) {
|
val perfStats = NativeLibrary.getPerfStats()
|
||||||
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
|
if (perfStats[FPS] > 0 && _binding != null) {
|
||||||
}
|
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
|
||||||
|
}
|
||||||
if (!emulationState.isStopped) {
|
|
||||||
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
|
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
perfStatsUpdateHandler.post(perfStatsUpdater!!)
|
||||||
binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
|
|
||||||
binding.showFpsText.visibility = View.VISIBLE
|
binding.showFpsText.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
if (perfStatsUpdater != null) {
|
if (perfStatsUpdater != null) {
|
||||||
|
@ -349,7 +406,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
isInFoldableLayout = true
|
isInFoldableLayout = true
|
||||||
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
|
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
|
||||||
refreshInputOverlay()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.isSeparating
|
it.isSeparating
|
||||||
|
@ -437,7 +493,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
refreshInputOverlay()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
|
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
|
||||||
|
@ -461,7 +517,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
R.id.menu_show_overlay -> {
|
R.id.menu_show_overlay -> {
|
||||||
it.isChecked = !it.isChecked
|
it.isChecked = !it.isChecked
|
||||||
EmulationMenuSettings.showOverlay = it.isChecked
|
EmulationMenuSettings.showOverlay = it.isChecked
|
||||||
refreshInputOverlay()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,14 +623,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putInt(Settings.PREF_CONTROL_SCALE, scale)
|
.putInt(Settings.PREF_CONTROL_SCALE, scale)
|
||||||
.apply()
|
.apply()
|
||||||
refreshInputOverlay()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setControlOpacity(opacity: Int) {
|
private fun setControlOpacity(opacity: Int) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
|
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
|
||||||
.apply()
|
.apply()
|
||||||
refreshInputOverlay()
|
binding.surfaceInputOverlay.refreshControls()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setInsets() {
|
private fun setInsets() {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class EmulationViewModel : ViewModel() {
|
||||||
|
private val _emulationStarted = MutableLiveData(false)
|
||||||
|
val emulationStarted: LiveData<Boolean> get() = _emulationStarted
|
||||||
|
|
||||||
|
private val _isEmulationStopping = MutableLiveData(false)
|
||||||
|
val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
|
||||||
|
|
||||||
|
private val _shaderProgress = MutableLiveData(0)
|
||||||
|
val shaderProgress: LiveData<Int> get() = _shaderProgress
|
||||||
|
|
||||||
|
private val _totalShaders = MutableLiveData(0)
|
||||||
|
val totalShaders: LiveData<Int> get() = _totalShaders
|
||||||
|
|
||||||
|
private val _shaderMessage = MutableLiveData("")
|
||||||
|
val shaderMessage: LiveData<String> get() = _shaderMessage
|
||||||
|
|
||||||
|
fun setEmulationStarted(started: Boolean) {
|
||||||
|
_emulationStarted.postValue(started)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsEmulationStopping(value: Boolean) {
|
||||||
|
_isEmulationStopping.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShaderProgress(progress: Int) {
|
||||||
|
_shaderProgress.value = progress
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTotalShaders(max: Int) {
|
||||||
|
_totalShaders.value = max
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShaderMessage(msg: String) {
|
||||||
|
_shaderMessage.value = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateProgress(msg: String, progress: Int, max: Int) {
|
||||||
|
setShaderMessage(msg)
|
||||||
|
setShaderProgress(progress)
|
||||||
|
setTotalShaders(max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
_emulationStarted.value = false
|
||||||
|
_isEmulationStopping.value = false
|
||||||
|
_shaderProgress.value = 0
|
||||||
|
_totalShaders.value = 0
|
||||||
|
_shaderMessage.value = ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.fetch.DrawableResult
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.key.Keyer
|
||||||
|
import coil.memory.MemoryCache
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.request.Options
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
|
|
||||||
|
class GameIconFetcher(
|
||||||
|
private val game: Game,
|
||||||
|
private val options: Options
|
||||||
|
) : Fetcher {
|
||||||
|
override suspend fun fetch(): FetchResult {
|
||||||
|
return DrawableResult(
|
||||||
|
drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources),
|
||||||
|
isSampled = false,
|
||||||
|
dataSource = DataSource.DISK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeGameIcon(uri: String): Bitmap? {
|
||||||
|
val data = NativeLibrary.getIcon(uri)
|
||||||
|
return BitmapFactory.decodeByteArray(
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
data.size,
|
||||||
|
BitmapFactory.Options()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory : Fetcher.Factory<Game> {
|
||||||
|
override fun create(data: Game, options: Options, imageLoader: ImageLoader): Fetcher =
|
||||||
|
GameIconFetcher(data, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GameIconKeyer : Keyer<Game> {
|
||||||
|
override fun key(data: Game, options: Options): String = data.path
|
||||||
|
}
|
||||||
|
|
||||||
|
object GameIconUtils {
|
||||||
|
private val imageLoader = ImageLoader.Builder(YuzuApplication.appContext)
|
||||||
|
.components {
|
||||||
|
add(GameIconKeyer())
|
||||||
|
add(GameIconFetcher.Factory())
|
||||||
|
}
|
||||||
|
.memoryCache {
|
||||||
|
MemoryCache.Builder(YuzuApplication.appContext)
|
||||||
|
.maxSizePercent(0.25)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun loadGameIcon(game: Game, imageView: ImageView) {
|
||||||
|
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||||
|
.data(game)
|
||||||
|
.target(imageView)
|
||||||
|
.error(R.drawable.default_icon)
|
||||||
|
.build()
|
||||||
|
imageLoader.enqueue(request)
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class;
|
||||||
static jclass s_load_callback_stage_class;
|
static jclass s_load_callback_stage_class;
|
||||||
static jmethodID s_exit_emulation_activity;
|
static jmethodID s_exit_emulation_activity;
|
||||||
static jmethodID s_disk_cache_load_progress;
|
static jmethodID s_disk_cache_load_progress;
|
||||||
|
static jmethodID s_on_emulation_started;
|
||||||
|
static jmethodID s_on_emulation_stopped;
|
||||||
|
|
||||||
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
||||||
|
|
||||||
|
@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() {
|
||||||
return s_disk_cache_load_progress;
|
return s_disk_cache_load_progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jmethodID GetOnEmulationStarted() {
|
||||||
|
return s_on_emulation_started;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID GetOnEmulationStopped() {
|
||||||
|
return s_on_emulation_stopped;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||||
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
||||||
s_disk_cache_load_progress =
|
s_disk_cache_load_progress =
|
||||||
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
|
env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V");
|
||||||
|
s_on_emulation_started =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
|
||||||
|
s_on_emulation_stopped =
|
||||||
|
env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
|
||||||
|
|
||||||
// Initialize Android Storage
|
// Initialize Android Storage
|
||||||
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
|
||||||
|
|
|
@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass();
|
||||||
jclass GetDiskCacheLoadCallbackStageClass();
|
jclass GetDiskCacheLoadCallbackStageClass();
|
||||||
jmethodID GetExitEmulationActivity();
|
jmethodID GetExitEmulationActivity();
|
||||||
jmethodID GetDiskCacheLoadProgress();
|
jmethodID GetDiskCacheLoadProgress();
|
||||||
|
jmethodID GetOnEmulationStarted();
|
||||||
|
jmethodID GetOnEmulationStopped();
|
||||||
|
|
||||||
} // namespace IDCache
|
} // namespace IDCache
|
||||||
|
|
|
@ -203,12 +203,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsRunning() const {
|
bool IsRunning() const {
|
||||||
std::scoped_lock lock(m_mutex);
|
|
||||||
return m_is_running;
|
return m_is_running;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsPaused() const {
|
bool IsPaused() const {
|
||||||
std::scoped_lock lock(m_mutex);
|
|
||||||
return m_is_running && m_is_paused;
|
return m_is_running && m_is_paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,6 +333,8 @@ public:
|
||||||
|
|
||||||
// Tear down the render window.
|
// Tear down the render window.
|
||||||
m_window.reset();
|
m_window.reset();
|
||||||
|
|
||||||
|
OnEmulationStopped(m_load_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PauseEmulation() {
|
void PauseEmulation() {
|
||||||
|
@ -376,6 +376,8 @@ public:
|
||||||
m_system.InitializeDebugger();
|
m_system.InitializeDebugger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnEmulationStarted();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
{
|
{
|
||||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||||
|
@ -511,6 +513,18 @@ private:
|
||||||
static_cast<jint>(progress), static_cast<jint>(max));
|
static_cast<jint>(progress), static_cast<jint>(max));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void OnEmulationStarted() {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||||
|
IDCache::GetOnEmulationStarted());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnEmulationStopped(Core::SystemResultStatus result) {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||||
|
IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static EmulationSession s_instance;
|
static EmulationSession s_instance;
|
||||||
|
|
||||||
|
@ -528,8 +542,8 @@ private:
|
||||||
Core::PerfStatsResults m_perf_stats{};
|
Core::PerfStatsResults m_perf_stats{};
|
||||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||||
bool m_is_running{};
|
std::atomic<bool> m_is_running = false;
|
||||||
bool m_is_paused{};
|
std::atomic<bool> m_is_paused = false;
|
||||||
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
|
||||||
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
|
||||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||||
|
|
|
@ -26,6 +26,81 @@
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:focusableInTouchMode="false" />
|
android:focusableInTouchMode="false" />
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/loading_indicator"
|
||||||
|
style="?attr/materialCardViewOutlinedStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:focusable="false">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/loading_layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/loading_image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/linearLayout"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/linearLayout"
|
||||||
|
tools:src="@drawable/default_icon" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:paddingVertical="36dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/loading_image"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/loading_title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
tools:text="@string/games" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/loading_text"
|
||||||
|
style="@style/TextAppearance.Material3.TitleSmall"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@string/loading"
|
||||||
|
android:textAlignment="viewStart" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/loading_progress_indicator"
|
||||||
|
android:layout_width="192dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:trackCornerRadius="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
|
@ -41,11 +116,12 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:focusableInTouchMode="true" />
|
android:focusableInTouchMode="true"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
|
||||||
android:id="@+id/done_control_config"
|
android:id="@+id/done_control_config"
|
||||||
|
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
@ -81,6 +157,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start|bottom"
|
android:layout_gravity="start|bottom"
|
||||||
app:headerLayout="@layout/header_in_game"
|
app:headerLayout="@layout/header_in_game"
|
||||||
app:menu="@menu/menu_in_game" />
|
app:menu="@menu/menu_in_game"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
|
|
|
@ -209,7 +209,6 @@
|
||||||
<string name="emulation_pause">Emulation pausieren</string>
|
<string name="emulation_pause">Emulation pausieren</string>
|
||||||
<string name="emulation_unpause">Emulation fortsetzen</string>
|
<string name="emulation_unpause">Emulation fortsetzen</string>
|
||||||
<string name="emulation_input_overlay">Overlay-Optionen</string>
|
<string name="emulation_input_overlay">Overlay-Optionen</string>
|
||||||
<string name="emulation_game_loading">Spiel lädt…</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Lädt Einstellungen...</string>
|
<string name="load_settings">Lädt Einstellungen...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Pausar Emulación</string>
|
<string name="emulation_pause">Pausar Emulación</string>
|
||||||
<string name="emulation_unpause">Reanudar Emulación</string>
|
<string name="emulation_unpause">Reanudar Emulación</string>
|
||||||
<string name="emulation_input_overlay">Opciones de pantalla </string>
|
<string name="emulation_input_overlay">Opciones de pantalla </string>
|
||||||
<string name="emulation_game_loading">Cargando juego...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Cargando configuración...</string>
|
<string name="load_settings">Cargando configuración...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Mettre en pause l\'émulation</string>
|
<string name="emulation_pause">Mettre en pause l\'émulation</string>
|
||||||
<string name="emulation_unpause">Reprendre l\'émulation</string>
|
<string name="emulation_unpause">Reprendre l\'émulation</string>
|
||||||
<string name="emulation_input_overlay">Options de l\'overlay</string>
|
<string name="emulation_input_overlay">Options de l\'overlay</string>
|
||||||
<string name="emulation_game_loading">Chargement du jeu...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Chargement des paramètres…</string>
|
<string name="load_settings">Chargement des paramètres…</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Metti in pausa l\'emulazione</string>
|
<string name="emulation_pause">Metti in pausa l\'emulazione</string>
|
||||||
<string name="emulation_unpause">Riprendi Emulazione</string>
|
<string name="emulation_unpause">Riprendi Emulazione</string>
|
||||||
<string name="emulation_input_overlay">Impostazioni Overlay</string>
|
<string name="emulation_input_overlay">Impostazioni Overlay</string>
|
||||||
<string name="emulation_game_loading">Caricamento del gioco...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Caricamento delle impostazioni...</string>
|
<string name="load_settings">Caricamento delle impostazioni...</string>
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,6 @@
|
||||||
<string name="emulation_pause">エミュレーションを一時停止</string>
|
<string name="emulation_pause">エミュレーションを一時停止</string>
|
||||||
<string name="emulation_unpause">エミュレーションを再開</string>
|
<string name="emulation_unpause">エミュレーションを再開</string>
|
||||||
<string name="emulation_input_overlay">オーバーレイオプション</string>
|
<string name="emulation_input_overlay">オーバーレイオプション</string>
|
||||||
<string name="emulation_game_loading">ロード中…</string>
|
|
||||||
|
|
||||||
<string name="load_settings">設定をロード中…</string>
|
<string name="load_settings">設定をロード中…</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">에뮬레이션 일시 중지</string>
|
<string name="emulation_pause">에뮬레이션 일시 중지</string>
|
||||||
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
|
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
|
||||||
<string name="emulation_input_overlay">오버레이 옵션</string>
|
<string name="emulation_input_overlay">오버레이 옵션</string>
|
||||||
<string name="emulation_game_loading">게임 불러오기 중...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">설정 불러오기 중...</string>
|
<string name="load_settings">설정 불러오기 중...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Pause Emulering</string>
|
<string name="emulation_pause">Pause Emulering</string>
|
||||||
<string name="emulation_unpause">Opphev pausing av emulering</string>
|
<string name="emulation_unpause">Opphev pausing av emulering</string>
|
||||||
<string name="emulation_input_overlay">Alternativer for overlegg</string>
|
<string name="emulation_input_overlay">Alternativer for overlegg</string>
|
||||||
<string name="emulation_game_loading">Spillet lastes inn...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Laster inn innstillinger...</string>
|
<string name="load_settings">Laster inn innstillinger...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Wstrzymaj emulację</string>
|
<string name="emulation_pause">Wstrzymaj emulację</string>
|
||||||
<string name="emulation_unpause">Wznów emulację</string>
|
<string name="emulation_unpause">Wznów emulację</string>
|
||||||
<string name="emulation_input_overlay">Opcje nakładki</string>
|
<string name="emulation_input_overlay">Opcje nakładki</string>
|
||||||
<string name="emulation_game_loading">Wczytywanie gry...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Wczytywanie ustawień...</string>
|
<string name="load_settings">Wczytywanie ustawień...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Pausa emulação</string>
|
<string name="emulation_pause">Pausa emulação</string>
|
||||||
<string name="emulation_unpause">Retomar emulação</string>
|
<string name="emulation_unpause">Retomar emulação</string>
|
||||||
<string name="emulation_input_overlay">Opções de sobreposição </string>
|
<string name="emulation_input_overlay">Opções de sobreposição </string>
|
||||||
<string name="emulation_game_loading">Jogo a carregar...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Configurações a carregar...</string>
|
<string name="load_settings">Configurações a carregar...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Pausa emulação</string>
|
<string name="emulation_pause">Pausa emulação</string>
|
||||||
<string name="emulation_unpause">Retomar emulação</string>
|
<string name="emulation_unpause">Retomar emulação</string>
|
||||||
<string name="emulation_input_overlay">Opções de sobreposição </string>
|
<string name="emulation_input_overlay">Opções de sobreposição </string>
|
||||||
<string name="emulation_game_loading">Jogo a carregar...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Configurações a carregar...</string>
|
<string name="load_settings">Configurações a carregar...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Пауза эмуляции</string>
|
<string name="emulation_pause">Пауза эмуляции</string>
|
||||||
<string name="emulation_unpause">Возобновление эмуляции</string>
|
<string name="emulation_unpause">Возобновление эмуляции</string>
|
||||||
<string name="emulation_input_overlay">Настройки оверлея</string>
|
<string name="emulation_input_overlay">Настройки оверлея</string>
|
||||||
<string name="emulation_game_loading">Загрузка игры...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Загрузка настроек...</string>
|
<string name="load_settings">Загрузка настроек...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">Пауза емуляції</string>
|
<string name="emulation_pause">Пауза емуляції</string>
|
||||||
<string name="emulation_unpause">Відновлення емуляції</string>
|
<string name="emulation_unpause">Відновлення емуляції</string>
|
||||||
<string name="emulation_input_overlay">Налаштування оверлея</string>
|
<string name="emulation_input_overlay">Налаштування оверлея</string>
|
||||||
<string name="emulation_game_loading">Завантаження гри...</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Завантаження налаштувань...</string>
|
<string name="load_settings">Завантаження налаштувань...</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">暂停模拟</string>
|
<string name="emulation_pause">暂停模拟</string>
|
||||||
<string name="emulation_unpause">继续模拟</string>
|
<string name="emulation_unpause">继续模拟</string>
|
||||||
<string name="emulation_input_overlay">虚拟按键选项</string>
|
<string name="emulation_input_overlay">虚拟按键选项</string>
|
||||||
<string name="emulation_game_loading">载入游戏中…</string>
|
|
||||||
|
|
||||||
<string name="load_settings">正在载入设定…</string>
|
<string name="load_settings">正在载入设定…</string>
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,6 @@
|
||||||
<string name="emulation_pause">暫停模擬</string>
|
<string name="emulation_pause">暫停模擬</string>
|
||||||
<string name="emulation_unpause">取消暫停模擬</string>
|
<string name="emulation_unpause">取消暫停模擬</string>
|
||||||
<string name="emulation_input_overlay">覆疊選項</string>
|
<string name="emulation_input_overlay">覆疊選項</string>
|
||||||
<string name="emulation_game_loading">遊戲正在載入…</string>
|
|
||||||
|
|
||||||
<string name="load_settings">正在載入設定…</string>
|
<string name="load_settings">正在載入設定…</string>
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,7 @@
|
||||||
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
|
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
|
||||||
<string name="unimplemented_menu">Unimplemented Menu</string>
|
<string name="unimplemented_menu">Unimplemented Menu</string>
|
||||||
<string name="loading">Loading…</string>
|
<string name="loading">Loading…</string>
|
||||||
|
<string name="shutting_down">Shutting down…</string>
|
||||||
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
|
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
|
||||||
<string name="reset_to_default">Reset to default</string>
|
<string name="reset_to_default">Reset to default</string>
|
||||||
<string name="reset_all_settings">Reset all settings?</string>
|
<string name="reset_all_settings">Reset all settings?</string>
|
||||||
|
@ -262,7 +263,6 @@
|
||||||
<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">Overlay options</string>
|
<string name="emulation_input_overlay">Overlay options</string>
|
||||||
<string name="emulation_game_loading">Game loading…</string>
|
|
||||||
|
|
||||||
<string name="load_settings">Loading settings…</string>
|
<string name="load_settings">Loading settings…</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue