aboutsummaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authornyx-ynx <contact@nyxynx.dev>2025-09-29 22:40:03 +0200
committercrueter <crueter@eden-emu.dev>2025-09-29 22:40:03 +0200
commit03c7d6ce4a7919aacd9130bb7e44ab30e9fe387b (patch)
tree89ca79785196a03454e44ec1ce94831af00eecdb /src/android
parent824dc6948e5dca6cfeb0a42eb98cae8ed57a58d3 (diff)
[android] input over(lay)haul 1: Auto-hide input overlay setting (#493)
This is step 1 of https://git.eden-emu.dev/eden-emu/eden/issues/47 which was the easiest to implement. How was this not implemented on yuzu already? Would prefer if more people tested this than the usual amount. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/493 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Co-authored-by: nyx-ynx <contact@nyxynx.dev> Co-committed-by: nyx-ynx <contact@nyxynx.dev>
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt82
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt76
-rw-r--r--src/android/app/src/main/jni/android_settings.h9
-rw-r--r--src/android/app/src/main/res/layout/dialog_spinbox.xml55
-rw-r--r--src/android/app/src/main/res/values/strings.xml18
14 files changed, 409 insertions, 6 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 2046af42c8..da40453497 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
var isActivityRecreated = false
private lateinit var nfcReader: NfcReader
+ private var touchDownTime: Long = 0
+ private val maxTapDuration = 500L
+
private val gyro = FloatArray(3)
private val accel = FloatArray(3)
private var motionTimestamp: Long = 0
@@ -489,6 +492,38 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
}
}
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
+ val emulationFragment = navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
+
+ emulationFragment?.let { fragment ->
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ touchDownTime = System.currentTimeMillis()
+ // show overlay immediately on touch and cancel timer
+ if (!emulationViewModel.drawerOpen.value) {
+ fragment.handler.removeCallbacksAndMessages(null)
+ fragment.showOverlay()
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (!emulationViewModel.drawerOpen.value) {
+ val touchDuration = System.currentTimeMillis() - touchDownTime
+
+ if (touchDuration <= maxTapDuration) {
+ fragment.handleScreenTap(false)
+ } else {
+ // just start the auto-hide timer without toggling visibility
+ fragment.handleScreenTap(true)
+ }
+ }
+ }
+ }
+ }
+
+ return super.dispatchTouchEvent(event)
+ }
+
fun onEmulationStarted() {
emulationViewModel.setEmulationStarted(true)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 3c5b9003de..638e1101db 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
FRAME_INTERPOLATION("frame_interpolation"),
// FRAME_SKIPPING("frame_skipping"),
+ ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
+
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index 21aad8b5d1..d5556a337b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
LOGIN_SHARE_APPLET("login_share_applet_mode"),
WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"),
- MY_PAGE_APPLET("my_page_applet_mode")
+ MY_PAGE_APPLET("my_page_applet_mode"),
+ INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide")
;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index a52f582031..454d0e4807 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -12,6 +12,7 @@ object Settings {
SECTION_SYSTEM(R.string.preferences_system),
SECTION_RENDERER(R.string.preferences_graphics),
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
+ SECTION_INPUT_OVERLAY(R.string.input_overlay_options),
SECTION_SOC_OVERLAY(R.string.soc_overlay_options),
SECTION_AUDIO(R.string.preferences_audio),
SECTION_INPUT(R.string.preferences_controls),
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 1f2ba81a73..5f7f7a43f9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -96,6 +96,7 @@ abstract class SettingsItem(
const val TYPE_INT_SINGLE_CHOICE = 9
const val TYPE_INPUT_PROFILE = 10
const val TYPE_STRING_INPUT = 11
+ const val TYPE_SPINBOX = 12
const val FASTMEM_COMBINED = "fastmem_combined"
@@ -385,6 +386,22 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution
)
)
+ put(
+ SwitchSetting(
+ BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE,
+ titleId = R.string.enable_input_overlay_auto_hide,
+ )
+ )
+ put(
+ SpinBoxSetting(
+ IntSetting.INPUT_OVERLAY_AUTO_HIDE,
+ titleId = R.string.overlay_auto_hide,
+ descriptionId = R.string.overlay_auto_hide_description,
+ min = 1,
+ max = 999,
+ valueHint = R.string.seconds
+ )
+ )
put(
SwitchSetting(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt
new file mode 100644
index 0000000000..0b0d01dfe0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model.view
+
+import androidx.annotation.StringRes
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
+
+class SpinBoxSetting(
+ setting: AbstractSetting,
+ @StringRes titleId: Int = 0,
+ titleString: String = "",
+ @StringRes descriptionId: Int = 0,
+ descriptionString: String = "",
+ val valueHint: Int,
+ val min: Int,
+ val max: Int
+) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) {
+ override val type = TYPE_SPINBOX
+
+ fun getSelectedValue(needsGlobal: Boolean = false) =
+ when (setting) {
+ is AbstractByteSetting -> setting.getByte(needsGlobal).toInt()
+ is AbstractShortSetting -> setting.getShort(needsGlobal).toInt()
+ is AbstractIntSetting -> setting.getInt(needsGlobal)
+ is AbstractFloatSetting -> setting.getFloat(needsGlobal).toInt()
+ else -> 0
+ }
+
+ fun setSelectedValue(value: Int) =
+ when (setting) {
+ is AbstractByteSetting -> setting.setByte(value.toByte())
+ is AbstractShortSetting -> setting.setShort(value.toShort())
+ is AbstractFloatSetting -> setting.setFloat(value.toFloat())
+ else -> (setting as AbstractIntSetting).setInt(value)
+ }
+} \ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 500ac6e66e..bdc51b7070 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui
@@ -61,6 +61,10 @@ class SettingsAdapter(
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
+ SettingsItem.TYPE_SPINBOX -> {
+ SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this)
+ }
+
SettingsItem.TYPE_SUBMENU -> {
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
}
@@ -191,6 +195,14 @@ class SettingsAdapter(
position
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
}
+ fun onSpinBoxClick(item: SpinBoxSetting, position: Int) {
+ SettingsDialogFragment.newInstance(
+ settingsViewModel,
+ item,
+ SettingsItem.TYPE_SPINBOX,
+ position
+ ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
+ }
fun onSubmenuClick(item: SubmenuSetting) {
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
index dc9f561eca..2a97f15892 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt
@@ -14,6 +14,7 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
@@ -22,6 +23,7 @@ import com.google.android.material.slider.Slider
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.databinding.DialogSpinboxBinding
import org.yuzu.yuzu_emu.features.input.NativeInput
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
@@ -30,6 +32,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.utils.ParamPackage
@@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
private lateinit var sliderBinding: DialogSliderBinding
private lateinit var stringInputBinding: DialogEditTextBinding
+ private lateinit var spinboxBinding: DialogSpinboxBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.create()
}
+ SettingsItem.TYPE_SPINBOX -> {
+ spinboxBinding = DialogSpinboxBinding.inflate(layoutInflater)
+ val item = settingsViewModel.clickedItem as SpinBoxSetting
+
+ val currentValue = item.getSelectedValue()
+ spinboxBinding.editValue.setText(currentValue.toString())
+ spinboxBinding.textInputLayout.hint = getString(item.valueHint)
+
+ val dialog = MaterialAlertDialogBuilder(requireContext())
+ .setTitle(item.title)
+ .setView(spinboxBinding.root)
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+ .create()
+
+ val updateButtonState = { enabled: Boolean ->
+ dialog.setOnShowListener { dialogInterface ->
+ (dialogInterface as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
+ }
+ if (dialog.isShowing) {
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled
+ }
+ }
+
+ val updateValidity = { value: Int ->
+ val isValid = value in item.min..item.max
+ if (isValid) {
+ spinboxBinding.textInputLayout.error = null
+ } else {
+ spinboxBinding.textInputLayout.error = getString(
+ if (value < item.min) R.string.value_too_low else R.string.value_too_high,
+ if (value < item.min) item.min else item.max
+ )
+ }
+ updateButtonState(isValid)
+ }
+
+ spinboxBinding.buttonDecrement.setOnClickListener {
+ val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
+ val newValue = current - 1
+ spinboxBinding.editValue.setText(newValue.toString())
+ updateValidity(newValue)
+ }
+
+ spinboxBinding.buttonIncrement.setOnClickListener {
+ val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue
+ val newValue = current + 1
+ spinboxBinding.editValue.setText(newValue.toString())
+ updateValidity(newValue)
+ }
+
+ spinboxBinding.editValue.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
+ override fun afterTextChanged(s: Editable?) {
+ val value = s.toString().toIntOrNull()
+ if (value != null) {
+ updateValidity(value)
+ } else {
+ spinboxBinding.textInputLayout.error = getString(R.string.invalid_value)
+ updateButtonState(false)
+ }
+ }
+ })
+
+ updateValidity(currentValue)
+
+ dialog
+ }
+
SettingsItem.TYPE_STRING_INPUT -> {
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
val item = settingsViewModel.clickedItem as StringInputSetting
@@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
}
+ is SpinBoxSetting -> {
+ val spinBoxSetting = settingsViewModel.clickedItem as SpinBoxSetting
+ val value = spinboxBinding.editValue.text.toString().toIntOrNull()
+ if (value != null && value in spinBoxSetting.min..spinBoxSetting.max) {
+ spinBoxSetting.setSelectedValue(value)
+ }
+ }
+
is StringInputSetting -> {
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
stringInputSetting.setSelectedValue(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 14d62ceec3..715baec72f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -97,6 +97,7 @@ class SettingsFragmentPresenter(
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
+ MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
MenuTag.SECTION_INPUT -> addInputSettings(sl)
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
@@ -156,6 +157,14 @@ class SettingsFragmentPresenter(
menuKey = MenuTag.SECTION_SOC_OVERLAY
)
)
+ add(
+ SubmenuSetting(
+ titleId = R.string.input_overlay_options,
+ iconId = R.drawable.ic_controller,
+ descriptionId = R.string.input_overlay_options_description,
+ menuKey = MenuTag.SECTION_INPUT_OVERLAY
+ )
+ )
}
add(
SubmenuSetting(
@@ -264,6 +273,13 @@ class SettingsFragmentPresenter(
}
}
+ private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
+ sl.apply {
+ add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
+ add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
+ }
+ }
+
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(HeaderSetting(R.string.stats_overlay_customization))
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt
new file mode 100644
index 0000000000..1f9a16b798
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.ui.viewholder
+
+import android.view.View
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
+
+class SpinBoxViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
+ SettingViewHolder(binding.root, adapter) {
+ private lateinit var setting: SpinBoxSetting
+
+ override fun bind(item: SettingsItem) {
+ setting = item as SpinBoxSetting
+ binding.textSettingName.text = setting.title
+ binding.textSettingDescription.setVisible(item.description.isNotEmpty())
+ binding.textSettingDescription.text = setting.description
+ binding.textSettingValue.setVisible(true)
+ binding.textSettingValue.text = setting.getSelectedValue().toString()
+
+ binding.buttonClear.setVisible(setting.clearable)
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
+ }
+
+ setStyle(setting.isEditable, binding)
+ }
+ override fun onClick(clicked: View) {
+ if (setting.isEditable) {
+ adapter.onSpinBoxClick(setting, bindingAdapterPosition)
+ }
+ }
+ override fun onLongClick(clicked: View): Boolean {
+ if (setting.isEditable) {
+ return adapter.onLongClick(setting, bindingAdapterPosition)
+ }
+ return false
+ }
+} \ No newline at end of file
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 4487812ad1..b2d6135372 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private var perfStatsUpdater: (() -> Unit)? = null
private var socUpdater: (() -> Unit)? = null
+ val handler = Handler(Looper.getMainLooper())
+ private var isOverlayVisible = true
+
private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!!
@@ -452,7 +455,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
/**
* Ask user if they want to launch with default settings when custom settings fail
*/
- private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean {
+ private suspend fun askUserToLaunchWithDefaultSettings(
+ gameTitle: String,
+ errorMessage: String
+ ): Boolean {
return suspendCoroutine { continuation ->
requireActivity().runOnUiThread {
MaterialAlertDialogBuilder(requireContext())
@@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updateShowStatsOverlay()
updateSocOverlay()
+ initializeOverlayAutoHide()
+
// Re update binding when the specs values get initialized properly
binding.inGameMenu.getHeaderView(0).apply {
val titleView = findViewById<TextView>(R.id.text_game_title)
@@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
updatePauseMenuEntry(emulationState.isPaused)
}
}
+
+ // if the overlay auto-hide setting is changed while paused,
+ // we need to reinitialize the auto-hide timer
+ initializeOverlayAutoHide()
+
}
private fun resetInputOverlay() {
@@ -1035,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
- status == BatteryManager.BATTERY_STATUS_FULL
+ status == BatteryManager.BATTERY_STATUS_FULL
if (isCharging) {
sb.append(" ${getString(R.string.charging)}")
@@ -1728,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val socUpdateHandler = Handler(Looper.myLooper()!!)
}
-} \ No newline at end of file
+
+ private fun startOverlayAutoHideTimer(seconds: Int) {
+ handler.removeCallbacksAndMessages(null)
+
+ handler.postDelayed({
+ if (isOverlayVisible) {
+ hideOverlay()
+ }
+ }, seconds * 1000L)
+ }
+
+ fun handleScreenTap(isLongTap: Boolean) {
+ val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
+ val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
+
+ if (!shouldProceed) {
+ return
+ }
+
+ // failsafe
+ if (autoHideSeconds == 0) {
+ showOverlay()
+ return
+ }
+
+ if (!isOverlayVisible && !isLongTap) {
+ showOverlay()
+ }
+
+ startOverlayAutoHideTimer(autoHideSeconds)
+ }
+
+ private fun initializeOverlayAutoHide() {
+ val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
+ val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean()
+ val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
+
+ if (autoHideEnabled && showOverlay) {
+ showOverlay()
+ startOverlayAutoHideTimer(autoHideSeconds)
+ }
+ }
+
+
+ fun showOverlay() {
+ if (!isOverlayVisible) {
+ isOverlayVisible = true
+ ViewUtils.showView(binding.surfaceInputOverlay, 500)
+ }
+ }
+
+ private fun hideOverlay() {
+ if (isOverlayVisible) {
+ isOverlayVisible = false
+ ViewUtils.hideView(binding.surfaceInputOverlay, 500)
+ }
+ }
+}
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 16735531ee..c9e59ce105 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -80,6 +80,15 @@ namespace AndroidSettings {
Settings::Category::Overlay,
Settings::Specialization::Paired, true,
true};
+ Settings::Setting<bool> enable_input_overlay_auto_hide{linkage, false,
+ "enable_input_overlay_auto_hide",
+ Settings::Category::Overlay,
+ Settings::Specialization::Default, true,
+ true,};
+
+ Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
+ Settings::Category::Overlay,
+ Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
Settings::Category::Overlay,
Settings::Specialization::Default, true,
diff --git a/src/android/app/src/main/res/layout/dialog_spinbox.xml b/src/android/app/src/main/res/layout/dialog_spinbox.xml
new file mode 100644
index 0000000000..2f18ab750d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_spinbox.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_marginTop="32dp"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/spacing_large"
+ android:paddingRight="@dimen/spacing_large">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_decrement"
+ style="@style/Widget.Material3.Button.IconButton.Filled"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/decrement"
+ android:text="-" />
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/text_input_layout"
+ style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="@dimen/spacing_medlarge"
+ android:layout_marginRight="@dimen/spacing_medlarge"
+ android:layout_weight="1"
+ android:hint="Value">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/edit_value"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:textAlignment="textStart"
+ tools:text="0" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_increment"
+ style="@style/Widget.Material3.Button.IconButton.Filled"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/increment"
+ android:text="+" />
+
+ </LinearLayout>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index d41246b184..fd9e1b3f77 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -12,6 +12,24 @@
<string name="app_notification_channel_id" translatable="false">Eden</string>
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
<string name="app_notification_running">Eden is Running</string>
+ <string name="seconds">Seconds</string>
+
+ <!-- Spinbox strings -->
+ <string name="increment">Increment</string>
+ <string name="decrement">Decrement</string>
+ <string name="value">Value</string>
+ <string name="value_too_low">Value must be at least %1$d</string>
+ <string name="value_too_high">Value must be at most %1$d</string>
+ <string name="invalid_value">Invalid value</string>
+
+
+ <!-- Input Overlay -->
+ <string name="overlay_auto_hide">Overlay Auto Hide</string>
+ <string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
+ <string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
+
+ <string name="input_overlay_options">Input Overlay</string>
+ <string name="input_overlay_options_description">Configure on-screen controls</string>
<!-- Stats Overlay settings -->