[android] over(lay)haul 1: Auto-hide overlay setting
All checks were successful
eden-license / license-header (pull_request) Successful in 37s

This commit is contained in:
nyx 2025-09-15 13:57:08 +02:00
parent 4c5d03f5de
commit af7f98f5be
9 changed files with 193 additions and 3 deletions

View file

@ -66,6 +66,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
@ -476,6 +479,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)
}

View file

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

View file

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

View file

@ -392,6 +392,15 @@ abstract class SettingsItem(
warningMessage = R.string.warning_resolution
)
)
put(
SingleChoiceSetting(
IntSetting.INPUT_OVERLAY_AUTO_HIDE,
titleId = R.string.overlay_auto_hide,
descriptionId = R.string.overlay_auto_hide_description,
choicesId = R.array.overlayAutoHideEntries,
valuesId = R.array.overlayAutoHideValues,
)
)
put(
SwitchSetting(

View file

@ -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(
@ -265,6 +274,12 @@ class SettingsFragmentPresenter(
}
}
private fun addInputOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
}
}
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
sl.apply {
add(HeaderSetting(R.string.stats_overlay_customization))

View file

@ -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)
@ -910,6 +918,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val position = IntSetting.PERF_OVERLAY_POSITION.getInt()
updateStatsPosition(position)
// if the overlay auto-hide setting is changed while paused,
// we need to reinitialize the auto-hide timer
initializeOverlayAutoHide()
val socPosition = IntSetting.SOC_OVERLAY_POSITION.getInt()
updateSocPosition(socPosition)
@ -1033,7 +1045,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)}")
@ -1722,4 +1734,60 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
private val socUpdateHandler = Handler(Looper.myLooper()!!)
}
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()
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
return
}
if (autoHideSeconds == 0) {
showOverlay()
return
}
// Show overlay for quick taps when it's hidden
if (!isOverlayVisible && !isLongTap) {
showOverlay()
}
startOverlayAutoHideTimer(autoHideSeconds)
}
private fun initializeOverlayAutoHide() {
val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt()
if (autoHideSeconds > 0) {
handler.postDelayed({
// since the timer starts only after touch input, we need to always force hide it
hideOverlay()
}, autoHideSeconds * 1000L)
}
}
fun showOverlay() {
if (!isOverlayVisible) {
isOverlayVisible = true
ViewUtils.showView(binding.surfaceInputOverlay, 500)
}
}
private fun hideOverlay() {
if (isOverlayVisible) {
isOverlayVisible = false
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
}
}
}

View file

@ -79,6 +79,10 @@ namespace AndroidSettings {
Settings::Category::Overlay,
Settings::Specialization::Paired, true,
true};
Settings::Setting<u32> perf_overlay_border{linkage, 0, "input_overlay_auto_hide",
Settings::Category::Overlay,
Settings::Specialization::Default, true, true,};
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
Settings::Category::Overlay,
Settings::Specialization::Default, true,

View file

@ -497,6 +497,40 @@
<item>1</item>
</integer-array>
<string-array name="overlayAutoHideEntries">
<item>@string/overlay_auto_hide_never</item>
<item>@string/overlay_auto_hide_instant</item>
<item>@string/overlay_auto_hide_5s</item>
<item>@string/overlay_auto_hide_10s</item>
<item>@string/overlay_auto_hide_15s</item>
<item>@string/overlay_auto_hide_20s</item>
<item>@string/overlay_auto_hide_25s</item>
<item>@string/overlay_auto_hide_30s</item>
<item>@string/overlay_auto_hide_35s</item>
<item>@string/overlay_auto_hide_40s</item>
<item>@string/overlay_auto_hide_45s</item>
<item>@string/overlay_auto_hide_50s</item>
<item>@string/overlay_auto_hide_55s</item>
<item>@string/overlay_auto_hide_60s</item>
</string-array>
<integer-array name="overlayAutoHideValues">
<item>0</item>
<item>1</item>
<item>5</item>
<item>10</item>
<item>15</item>
<item>20</item>
<item>25</item>
<item>30</item>
<item>35</item>
<item>40</item>
<item>45</item>
<item>50</item>
<item>55</item>
<item>60</item>
</integer-array>
<string-array name="dynaStateEntries">
<item>@string/disabled</item>
<item>1</item>

View file

@ -9,6 +9,29 @@
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
<string name="notification_permission_not_granted">Notification permission not granted!</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. Select "Never" to keep the overlay always visible.</string>
<string name="overlay_auto_hide_never">Never</string>
<string name="overlay_auto_hide_instant">Instantly</string>
<string name="overlay_auto_hide_5s">5 seconds</string>
<string name="overlay_auto_hide_10s">10 seconds</string>
<string name="overlay_auto_hide_15s">15 seconds</string>
<string name="overlay_auto_hide_20s">20 seconds</string>
<string name="overlay_auto_hide_25s">25 seconds</string>
<string name="overlay_auto_hide_30s">30 seconds</string>
<string name="overlay_auto_hide_35s">35 seconds</string>
<string name="overlay_auto_hide_40s">40 seconds</string>
<string name="overlay_auto_hide_45s">45 seconds</string>
<string name="overlay_auto_hide_50s">50 seconds</string>
<string name="overlay_auto_hide_55s">55 seconds</string>
<string name="overlay_auto_hide_60s">1 minute</string>
<string name="input_overlay_options">Input Overlay</string>
<string name="input_overlay_options_description">Configure on-screen controls</string>
<!-- Stats Overlay settings -->
<string name="enhanced_fps_suffix">(Enhanced)</string>
<string name="process_ram">Process RAM: %1$d MB</string>