Merge pull request #11405 from t895/emulation-loading

android: Emulation loading UI and fixes
This commit is contained in:
Charles Lombardo 2023-08-30 16:24:46 -04:00 committed by GitHub
commit a2f0caefd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 392 additions and 236 deletions

View file

@ -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.isDirectory
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
import org.yuzu.yuzu_emu.utils.Log.error
import org.yuzu.yuzu_emu.utils.Log.verbose
import org.yuzu.yuzu_emu.utils.Log.warning
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
/**
@ -465,7 +463,7 @@ object NativeLibrary {
val emulationActivity = sEmulationActivity.get()
if (emulationActivity == null) {
warning("[NativeLibrary] EmulationActivity is null, can't exit.")
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit.")
return
}
@ -490,15 +488,27 @@ object NativeLibrary {
}
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
verbose("[NativeLibrary] Registering EmulationActivity.")
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity)
}
fun clearEmulationActivity() {
verbose("[NativeLibrary] Unregistering EmulationActivity.")
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
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.
*/

View file

@ -28,6 +28,7 @@ import android.view.Surface
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
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.IntSetting
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.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
@ -70,8 +72,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val actionMute = "ACTION_EMULATOR_MUTE"
private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
private val emulationViewModel: EmulationViewModel by viewModels()
override fun onDestroy() {
stopForegroundService(this)
emulationViewModel.clear()
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() {
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)

View file

@ -3,8 +3,6 @@
package org.yuzu.yuzu_emu.adapters
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.text.TextUtils
import android.view.LayoutInflater
@ -15,23 +13,20 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.GameIconUtils
class GameAdapter(private val activity: AppCompatActivity) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
@ -98,12 +93,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
this.game = game
binding.imageGameScreen.scaleType = ImageView.ScaleType.CENTER_CROP
activity.lifecycleScope.launch {
val bitmap = decodeGameIcon(game.path)
binding.imageGameScreen.load(bitmap) {
error(R.drawable.default_icon)
}
}
GameIconUtils.loadGameIcon(game, binding.imageGameScreen)
binding.textGameTitle.text = game.title.replace("[\\t\\n\\r]+".toRegex(), " ")
@ -126,14 +116,4 @@ class GameAdapter(private val activity: AppCompatActivity) :
return oldItem == newItem
}
}
private fun decodeGameIcon(uri: String): Bitmap? {
val data = NativeLibrary.getIcon(uri)
return BitmapFactory.decodeByteArray(
data,
0,
data.size,
BitmapFactory.Options()
)
}
}

View file

@ -4,43 +4,43 @@
package org.yuzu.yuzu_emu.disk_shader_cache
import androidx.annotation.Keep
import androidx.lifecycle.ViewModelProvider
import org.yuzu.yuzu_emu.NativeLibrary
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
object DiskShaderCacheProgress {
val finishLock = Object()
private lateinit var fragment: ShaderProgressDialogFragment
private lateinit var emulationViewModel: EmulationViewModel
private fun prepareDialog() {
val emulationActivity = NativeLibrary.sEmulationActivity.get()!!
emulationActivity.runOnUiThread {
fragment = ShaderProgressDialogFragment.newInstance(
emulationActivity.getString(R.string.loading),
emulationActivity.getString(R.string.preparing_shaders)
)
fragment.show(
emulationActivity.supportFragmentManager,
ShaderProgressDialogFragment.TAG
)
}
synchronized(finishLock) { finishLock.wait() }
private fun prepareViewModel() {
emulationViewModel =
ViewModelProvider(
NativeLibrary.sEmulationActivity.get() as EmulationActivity
)[EmulationViewModel::class.java]
}
@JvmStatic
fun loadProgress(stage: Int, progress: Int, max: Int) {
val emulationActivity = NativeLibrary.sEmulationActivity.get()
?: error("[DiskShaderCacheProgress] EmulationActivity not present")
if (emulationActivity == null) {
Log.error("[DiskShaderCacheProgress] EmulationActivity not present")
return
}
emulationActivity.runOnUiThread {
when (LoadCallbackStage.values()[stage]) {
LoadCallbackStage.Prepare -> prepareDialog()
LoadCallbackStage.Build -> fragment.onUpdateProgress(
LoadCallbackStage.Prepare -> prepareViewModel()
LoadCallbackStage.Build -> emulationViewModel.updateProgress(
emulationActivity.getString(R.string.building_shaders),
progress,
max
)
LoadCallbackStage.Complete -> fragment.dismiss()
LoadCallbackStage.Complete -> {}
}
}
}

View file

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

View file

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

View file

@ -24,8 +24,9 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
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.utils.SettingsFile
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.utils.*
@ -66,6 +68,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var game: Game
private val emulationViewModel: EmulationViewModel by activityViewModels()
private var isInFoldableLayout = false
override fun onAttach(context: Context) {
@ -130,9 +134,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.showFpsText.setTextColor(Color.YELLOW)
binding.doneControlConfig.setOnClickListener { stopConfiguringControls() }
// Setup overlay.
updateShowFpsOverlay()
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
binding.inGameMenu.setNavigationItemSelectedListener {
@ -174,7 +176,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_exit -> {
emulationState.stop()
requireActivity().finish()
emulationViewModel.setIsEmulationStopping(true)
binding.drawerLayout.close()
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
true
}
@ -188,6 +192,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
requireActivity(),
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!NativeLibrary.isRunning()) {
return
}
if (binding.drawerLayout.isOpen) {
binding.drawerLayout.close()
} else {
@ -204,6 +212,54 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.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) {
@ -213,11 +269,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.drawerLayout.close()
}
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE
}
}
} else {
if (EmulationMenuSettings.showOverlay) {
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
if (EmulationMenuSettings.showOverlay &&
emulationViewModel.emulationStarted.value == true
) {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.VISIBLE
}
} else {
binding.surfaceInputOverlay.post {
binding.surfaceInputOverlay.visibility = View.INVISIBLE
}
}
if (!isInFoldableLayout) {
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
@ -226,9 +292,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
}
}
if (!binding.surfaceInputOverlay.isInEditMode) {
refreshInputOverlay()
}
}
}
@ -260,10 +323,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
super.onDetach()
}
private fun refreshInputOverlay() {
binding.surfaceInputOverlay.refreshControls()
}
private fun resetInputOverlay() {
preferences.edit()
.remove(Settings.PREF_CONTROL_SCALE)
@ -281,17 +340,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val FRAMETIME = 2
val SPEED = 3
perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value == true) {
val perfStats = NativeLibrary.getPerfStats()
if (perfStats[FPS] > 0 && _binding != null) {
binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
}
if (!emulationState.isStopped) {
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100)
}
}
perfStatsUpdateHandler.post(perfStatsUpdater!!)
binding.showFpsText.text = resources.getString(R.string.emulation_game_loading)
binding.showFpsText.visibility = View.VISIBLE
} else {
if (perfStatsUpdater != null) {
@ -349,7 +406,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
isInFoldableLayout = true
binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
refreshInputOverlay()
}
}
it.isSeparating
@ -437,7 +493,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
.apply()
}
.setPositiveButton(android.R.string.ok) { _, _ ->
refreshInputOverlay()
binding.surfaceInputOverlay.refreshControls()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.emulation_toggle_all) { _, _ -> }
@ -461,7 +517,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
R.id.menu_show_overlay -> {
it.isChecked = !it.isChecked
EmulationMenuSettings.showOverlay = it.isChecked
refreshInputOverlay()
binding.surfaceInputOverlay.refreshControls()
true
}
@ -567,14 +623,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
preferences.edit()
.putInt(Settings.PREF_CONTROL_SCALE, scale)
.apply()
refreshInputOverlay()
binding.surfaceInputOverlay.refreshControls()
}
private fun setControlOpacity(opacity: Int) {
preferences.edit()
.putInt(Settings.PREF_CONTROL_OPACITY, opacity)
.apply()
refreshInputOverlay()
binding.surfaceInputOverlay.refreshControls()
}
private fun setInsets() {

View file

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

View file

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

View file

@ -15,6 +15,8 @@ static jclass s_disk_cache_progress_class;
static jclass s_load_callback_stage_class;
static jmethodID s_exit_emulation_activity;
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;
@ -59,6 +61,14 @@ jmethodID GetDiskCacheLoadProgress() {
return s_disk_cache_load_progress;
}
jmethodID GetOnEmulationStarted() {
return s_on_emulation_started;
}
jmethodID GetOnEmulationStopped() {
return s_on_emulation_stopped;
}
} // namespace IDCache
#ifdef __cplusplus
@ -85,6 +95,10 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
s_disk_cache_load_progress =
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
Common::FS::Android::RegisterCallbacks(env, s_native_library_class);

View file

@ -15,5 +15,7 @@ jclass GetDiskCacheProgressClass();
jclass GetDiskCacheLoadCallbackStageClass();
jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress();
jmethodID GetOnEmulationStarted();
jmethodID GetOnEmulationStopped();
} // namespace IDCache

View file

@ -203,12 +203,10 @@ public:
}
bool IsRunning() const {
std::scoped_lock lock(m_mutex);
return m_is_running;
}
bool IsPaused() const {
std::scoped_lock lock(m_mutex);
return m_is_running && m_is_paused;
}
@ -335,6 +333,8 @@ public:
// Tear down the render window.
m_window.reset();
OnEmulationStopped(m_load_result);
}
void PauseEmulation() {
@ -376,6 +376,8 @@ public:
m_system.InitializeDebugger();
}
OnEmulationStarted();
while (true) {
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
@ -511,6 +513,18 @@ private:
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:
static EmulationSession s_instance;
@ -528,8 +542,8 @@ private:
Core::PerfStatsResults m_perf_stats{};
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
bool m_is_running{};
bool m_is_paused{};
std::atomic<bool> m_is_running = false;
std::atomic<bool> m_is_paused = false;
SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;

View file

@ -26,6 +26,81 @@
android:focusable="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
@ -41,11 +116,12 @@
android:layout_height="match_parent"
android:layout_gravity="center"
android:focusable="true"
android:focusableInTouchMode="true" />
android:focusableInTouchMode="true"
android:visibility="invisible" />
<Button
style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
@ -81,6 +157,7 @@
android:layout_height="match_parent"
android:layout_gravity="start|bottom"
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>

View file

@ -209,7 +209,6 @@
<string name="emulation_pause">Emulation pausieren</string>
<string name="emulation_unpause">Emulation fortsetzen</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>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Pausar Emulación</string>
<string name="emulation_unpause">Reanudar Emulación</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>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Mettre en pause l\'émulation</string>
<string name="emulation_unpause">Reprendre l\'émulation</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>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Metti in pausa l\'emulazione</string>
<string name="emulation_unpause">Riprendi Emulazione</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>

View file

@ -211,7 +211,6 @@
<string name="emulation_pause">エミュレーションを一時停止</string>
<string name="emulation_unpause">エミュレーションを再開</string>
<string name="emulation_input_overlay">オーバーレイオプション</string>
<string name="emulation_game_loading">ロード中…</string>
<string name="load_settings">設定をロード中…</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">에뮬레이션 일시 중지</string>
<string name="emulation_unpause">에뮬레이션 일시 중지 해제</string>
<string name="emulation_input_overlay">오버레이 옵션</string>
<string name="emulation_game_loading">게임 불러오기 중...</string>
<string name="load_settings">설정 불러오기 중...</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Pause Emulering</string>
<string name="emulation_unpause">Opphev pausing av emulering</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>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Wstrzymaj emulację</string>
<string name="emulation_unpause">Wznów emulację</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>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Pausa 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_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Pausa 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_game_loading">Jogo a carregar...</string>
<string name="load_settings">Configurações a carregar...</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Пауза эмуляции</string>
<string name="emulation_unpause">Возобновление эмуляции</string>
<string name="emulation_input_overlay">Настройки оверлея</string>
<string name="emulation_game_loading">Загрузка игры...</string>
<string name="load_settings">Загрузка настроек...</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">Пауза емуляції</string>
<string name="emulation_unpause">Відновлення емуляції</string>
<string name="emulation_input_overlay">Налаштування оверлея</string>
<string name="emulation_game_loading">Завантаження гри...</string>
<string name="load_settings">Завантаження налаштувань...</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">暂停模拟</string>
<string name="emulation_unpause">继续模拟</string>
<string name="emulation_input_overlay">虚拟按键选项</string>
<string name="emulation_game_loading">载入游戏中…</string>
<string name="load_settings">正在载入设定…</string>

View file

@ -213,7 +213,6 @@
<string name="emulation_pause">暫停模擬</string>
<string name="emulation_unpause">取消暫停模擬</string>
<string name="emulation_input_overlay">覆疊選項</string>
<string name="emulation_game_loading">遊戲正在載入…</string>
<string name="load_settings">正在載入設定…</string>

View file

@ -204,6 +204,7 @@
<string name="error_saving">Error saving %1$s.ini: %2$s</string>
<string name="unimplemented_menu">Unimplemented Menu</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_to_default">Reset to default</string>
<string name="reset_all_settings">Reset all settings?</string>
@ -262,7 +263,6 @@
<string name="emulation_pause">Pause emulation</string>
<string name="emulation_unpause">Unpause emulation</string>
<string name="emulation_input_overlay">Overlay options</string>
<string name="emulation_game_loading">Game loading…</string>
<string name="load_settings">Loading settings…</string>