android: Game loading/shutting down indicators

master
Charles Lombardo 2023-08-12 20:11:59 +07:00
parent 270f430f70
commit b0a96d5216
26 changed files with 311 additions and 210 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)

@ -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 = ""
}
}

@ -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

@ -335,6 +335,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 +378,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 +515,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;

@ -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>