diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index 5f5215876..2f1994e10 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -6,6 +6,7 @@ package org.citra.citra_emu.activities import android.Manifest.permission import android.annotation.SuppressLint +import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager @@ -116,24 +117,29 @@ class EmulationActivity : AppCompatActivity() { EmulationLifecycleUtil.addShutdownHook(onShutdown) - isEmulationRunning = true - instance = this + if (!intent.getBooleanExtra(NO_GAME_EDIT_MODE, false)) { + isEmulationRunning = true + instance = this + } applyOrientationSettings() // Check for orientation settings at startup - val game = try { - intent.extras?.let { extras -> - BundleCompat.getParcelable(extras, "game", Game::class.java) - } ?: run { - Log.error("[EmulationActivity] Missing game data in intent extras") + if (!intent.getBooleanExtra(NO_GAME_EDIT_MODE, false)) { + val game = try { + intent.extras?.let { extras -> + BundleCompat.getParcelable(extras, "game", Game::class.java) + } ?: run { + Log.error("[EmulationActivity] Missing game data in intent extras") + return + } + } catch (e: Exception) { + Log.error("[EmulationActivity] Failed to retrieve game data: ${e.message}") return } - } catch (e: Exception) { - Log.error("[EmulationActivity] Failed to retrieve game data: ${e.message}") - return - } - NativeLibrary.playTimeManagerStart(game.titleId) + + NativeLibrary.playTimeManagerStart(game.titleId) + } } // On some devices, the system bars will not disappear on first boot or after some @@ -174,8 +180,10 @@ class EmulationActivity : AppCompatActivity() { override fun onDestroy() { EmulationLifecycleUtil.removeHook(onShutdown) NativeLibrary.playTimeManagerStop() - isEmulationRunning = false - instance = null + if (!intent.getBooleanExtra(NO_GAME_EDIT_MODE, false)) { + isEmulationRunning = false + instance = null + } secondaryDisplay.releasePresentation() secondaryDisplay.releaseVD() @@ -544,6 +552,13 @@ class EmulationActivity : AppCompatActivity() { companion object { private var instance: EmulationActivity? = null + const val NO_GAME_EDIT_MODE = "noGameEditMode" + + fun launchForOverlayEdit(context: Context): Intent { + return Intent(context, EmulationActivity::class.java).apply { + putExtra(NO_GAME_EDIT_MODE, true) + } + } fun isRunning(): Boolean { return instance?.isEmulationRunning ?: false diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt index 547a53594..14bed3081 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/Settings.kt @@ -104,6 +104,7 @@ class Settings { const val SECTION_CAMERA = "Camera" const val SECTION_CONTROLS = "Controls" const val SECTION_RENDERER = "Renderer" + const val SECTION_INPUT_OVERLAY = "Input Overlay" const val SECTION_LAYOUT = "Layout" const val SECTION_UTILITY = "Utility" const val SECTION_AUDIO = "Audio" @@ -242,6 +243,7 @@ class Settings { SECTION_CAMERA, SECTION_CONTROLS, SECTION_RENDERER, + SECTION_INPUT_OVERLAY, SECTION_LAYOUT, SECTION_STORAGE, SECTION_UTILITY, diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 25e56517f..ac332c0f7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -5,6 +5,7 @@ package org.citra.citra_emu.features.settings.ui import android.content.Context +import android.content.Intent import android.content.SharedPreferences import android.content.res.Resources import android.hardware.camera2.CameraAccessException @@ -17,6 +18,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.serialization.builtins.IntArraySerializer import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.R +import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.display.StereoMode import org.citra.citra_emu.display.StereoWhichDisplay @@ -99,6 +101,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) Settings.SECTION_CAMERA -> addCameraSettings(sl) Settings.SECTION_CONTROLS -> addControlsSettings(sl) Settings.SECTION_RENDERER -> addGraphicsSettings(sl) + Settings.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl) Settings.SECTION_LAYOUT -> addLayoutSettings(sl) Settings.SECTION_AUDIO -> addAudioSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl) @@ -179,6 +182,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) Settings.SECTION_RENDERER ) ) + add( + SubmenuSetting( + R.string.preferences_input_overlay, + 0, + R.drawable.dpad, + Settings.SECTION_INPUT_OVERLAY + ) + ) add( SubmenuSetting( R.string.preferences_layout, @@ -1127,6 +1138,58 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) } } + private fun addInputOverlaySettings(sl: ArrayList) { + settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_input_overlay)) + sl.apply { + val inputOverlayOpacitySetting = object : AbstractBooleanSetting { + private val preferences = + PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) + + override var boolean: Boolean + get() = preferences.getBoolean("EmulationMenuSettings_ShowOverlay", true) + set(value) { + preferences.edit() + .putBoolean("EmulationMenuSettings_ShowOverlay", value) + .apply() + } + + override val key: String? = null + override val section: String? = null + override val isRuntimeEditable: Boolean = true + override val valueAsString: String + get() = preferences.getBoolean("EmulationMenuSettings_ShowOverlay", true) + .toString() + override val defaultValue = true + } + + add( + SwitchSetting( + inputOverlayOpacitySetting, + R.string.enable_input_overlay, + 0 + ) + ) + add( + HeaderSetting( + R.string.realtime_edit, + ) + ) + add( + RunnableSetting( + R.string.edit_overlay_layout, + R.string.edit_overlay_layout_description, + false, + R.drawable.dpad, + runnable = { + val intent = EmulationActivity.launchForOverlayEdit(CitraApplication.appContext) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + CitraApplication.appContext.startActivity(intent) + }, + ) + ) + } + } + private fun addLayoutSettings(sl: ArrayList) { settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_layout)) sl.apply { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 785438526..1f4b44822 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -129,6 +129,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (args.noGameEditMode) { + return + } + val intent = requireActivity().intent var intentUri: Uri? = intent.data val oldIntentInfo = Pair( @@ -212,6 +216,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram return } + if (args.noGameEditMode) { + setupNoGameEditMode() + return + } + binding.surfaceEmulation.holder.addCallback(this) binding.doneControlConfig.setOnClickListener { binding.doneControlConfig.visibility = View.GONE @@ -503,6 +512,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram override fun onResume() { super.onResume() + + if (args.noGameEditMode) { + return + } + Choreographer.getInstance().postFrameCallback(this) if (NativeLibrary.isRunning()) { emulationState.unpause() @@ -530,7 +544,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } override fun onPause() { - if (NativeLibrary.isRunning()) { + if (!args.noGameEditMode && NativeLibrary.isRunning()) { emulationState.pause() } Choreographer.getInstance().removeFrameCallback(this) @@ -574,6 +588,37 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } } + private fun setupNoGameEditMode() { + binding.surfaceInputOverlay.post { + binding.surfaceInputOverlay.refreshControls(true) + } + + binding.doneControlConfig.setOnClickListener { + finishNoGameEditMode() + } + + binding.doneControlConfig.visibility = View.VISIBLE + binding.surfaceInputOverlay.setIsInEditMode(true) + binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + binding.surfaceInputOverlay.visibility = View.VISIBLE + binding.loadingIndicator.visibility = View.GONE + + // in no game edit mode, back = done + requireActivity().onBackPressedDispatcher.addCallback( + viewLifecycleOwner, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + finishNoGameEditMode() + } + } + ) + } + + private fun finishNoGameEditMode() { + binding.surfaceInputOverlay.setIsInEditMode(false) + emulationActivity.finish() + } + private fun showSavestateMenu() { val popupMenu = PopupMenu( requireContext(), @@ -1236,7 +1281,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram .show() } - private fun resetInputOverlay() { + fun resetInputOverlay() { resetAllScales() preferences.edit() .putInt("controlOpacity", 50) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt index f7519bb81..0fca53a5d 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt @@ -570,7 +570,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex } } - fun refreshControls() { + fun refreshControls(noGameEdit: Boolean = false) { // Remove all the overlay buttons from the HashSet. overlayButtons.clear() overlayDpads.clear() @@ -583,7 +583,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex } // Add all the enabled overlay items back to the HashSet. - if (EmulationMenuSettings.showOverlay) { + if (noGameEdit || EmulationMenuSettings.showOverlay) { addOverlayControls(orientation) } invalidate() diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml index c8e628cda..f0a94ad16 100644 --- a/src/android/app/src/main/res/navigation/emulation_navigation.xml +++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml @@ -15,6 +15,10 @@ app:argType="org.citra.citra_emu.model.Game" app:nullable="true" android:defaultValue="@null" /> + Decompression failed. Already installed applications cannot be compressed or decompressed. + + Input Overlay + Enable Touch Input Overlay + Edit Touch Input Overlay + Edit in realtime + Edit the touch overlay without having to open a game. +