mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2025-10-06 00:02:44 +02:00
[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>
This commit is contained in:
@@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||||||
var isActivityRecreated = false
|
var isActivityRecreated = false
|
||||||
private lateinit var nfcReader: NfcReader
|
private lateinit var nfcReader: NfcReader
|
||||||
|
|
||||||
|
private var touchDownTime: Long = 0
|
||||||
|
private val maxTapDuration = 500L
|
||||||
|
|
||||||
private val gyro = FloatArray(3)
|
private val gyro = FloatArray(3)
|
||||||
private val accel = FloatArray(3)
|
private val accel = FloatArray(3)
|
||||||
private var motionTimestamp: Long = 0
|
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() {
|
fun onEmulationStarted() {
|
||||||
emulationViewModel.setEmulationStarted(true)
|
emulationViewModel.setEmulationStarted(true)
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
FRAME_INTERPOLATION("frame_interpolation"),
|
FRAME_INTERPOLATION("frame_interpolation"),
|
||||||
// FRAME_SKIPPING("frame_skipping"),
|
// FRAME_SKIPPING("frame_skipping"),
|
||||||
|
|
||||||
|
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
|
||||||
|
|
||||||
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
|
||||||
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
|
||||||
|
|
||||||
|
@@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
|||||||
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
|
OFFLINE_WEB_APPLET("offline_web_applet_mode"),
|
||||||
LOGIN_SHARE_APPLET("login_share_applet_mode"),
|
LOGIN_SHARE_APPLET("login_share_applet_mode"),
|
||||||
WIFI_WEB_AUTH_APPLET("wifi_web_auth_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)
|
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||||
|
@@ -12,6 +12,7 @@ object Settings {
|
|||||||
SECTION_SYSTEM(R.string.preferences_system),
|
SECTION_SYSTEM(R.string.preferences_system),
|
||||||
SECTION_RENDERER(R.string.preferences_graphics),
|
SECTION_RENDERER(R.string.preferences_graphics),
|
||||||
SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options),
|
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_SOC_OVERLAY(R.string.soc_overlay_options),
|
||||||
SECTION_AUDIO(R.string.preferences_audio),
|
SECTION_AUDIO(R.string.preferences_audio),
|
||||||
SECTION_INPUT(R.string.preferences_controls),
|
SECTION_INPUT(R.string.preferences_controls),
|
||||||
|
@@ -96,6 +96,7 @@ abstract class SettingsItem(
|
|||||||
const val TYPE_INT_SINGLE_CHOICE = 9
|
const val TYPE_INT_SINGLE_CHOICE = 9
|
||||||
const val TYPE_INPUT_PROFILE = 10
|
const val TYPE_INPUT_PROFILE = 10
|
||||||
const val TYPE_STRING_INPUT = 11
|
const val TYPE_STRING_INPUT = 11
|
||||||
|
const val TYPE_SPINBOX = 12
|
||||||
|
|
||||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||||
|
|
||||||
@@ -385,6 +386,22 @@ abstract class SettingsItem(
|
|||||||
warningMessage = R.string.warning_resolution
|
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(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.ui
|
package org.yuzu.yuzu_emu.features.settings.ui
|
||||||
|
|
||||||
@@ -61,6 +61,10 @@ class SettingsAdapter(
|
|||||||
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SliderViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsItem.TYPE_SPINBOX -> {
|
||||||
|
SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
|
}
|
||||||
|
|
||||||
SettingsItem.TYPE_SUBMENU -> {
|
SettingsItem.TYPE_SUBMENU -> {
|
||||||
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||||
}
|
}
|
||||||
@@ -191,6 +195,14 @@ class SettingsAdapter(
|
|||||||
position
|
position
|
||||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
).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) {
|
fun onSubmenuClick(item: SubmenuSetting) {
|
||||||
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
|
||||||
|
@@ -14,6 +14,7 @@ import android.text.TextWatcher
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
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.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
|
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.NativeInput
|
||||||
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
import org.yuzu.yuzu_emu.features.input.model.AnalogDirection
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting
|
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.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
|
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.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.StringInputSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.utils.ParamPackage
|
import org.yuzu.yuzu_emu.utils.ParamPackage
|
||||||
@@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
|
|
||||||
private lateinit var sliderBinding: DialogSliderBinding
|
private lateinit var sliderBinding: DialogSliderBinding
|
||||||
private lateinit var stringInputBinding: DialogEditTextBinding
|
private lateinit var stringInputBinding: DialogEditTextBinding
|
||||||
|
private lateinit var spinboxBinding: DialogSpinboxBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
.create()
|
.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 -> {
|
SettingsItem.TYPE_STRING_INPUT -> {
|
||||||
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
||||||
val item = settingsViewModel.clickedItem as StringInputSetting
|
val item = settingsViewModel.clickedItem as StringInputSetting
|
||||||
@@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
|||||||
sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value)
|
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 -> {
|
is StringInputSetting -> {
|
||||||
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting
|
||||||
stringInputSetting.setSelectedValue(
|
stringInputSetting.setSelectedValue(
|
||||||
|
@@ -97,6 +97,7 @@ class SettingsFragmentPresenter(
|
|||||||
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||||
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl)
|
||||||
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl)
|
||||||
|
MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl)
|
||||||
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||||
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
MenuTag.SECTION_INPUT -> addInputSettings(sl)
|
||||||
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0)
|
||||||
@@ -156,6 +157,14 @@ class SettingsFragmentPresenter(
|
|||||||
menuKey = MenuTag.SECTION_SOC_OVERLAY
|
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(
|
add(
|
||||||
SubmenuSetting(
|
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>) {
|
private fun addSocOverlaySettings(sl: ArrayList<SettingsItem>) {
|
||||||
sl.apply {
|
sl.apply {
|
||||||
add(HeaderSetting(R.string.stats_overlay_customization))
|
add(HeaderSetting(R.string.stats_overlay_customization))
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
private var perfStatsUpdater: (() -> Unit)? = null
|
private var perfStatsUpdater: (() -> Unit)? = null
|
||||||
private var socUpdater: (() -> Unit)? = null
|
private var socUpdater: (() -> Unit)? = null
|
||||||
|
|
||||||
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var isOverlayVisible = true
|
||||||
|
|
||||||
private var _binding: FragmentEmulationBinding? = null
|
private var _binding: FragmentEmulationBinding? = null
|
||||||
|
|
||||||
private val binding get() = _binding!!
|
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
|
* 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 ->
|
return suspendCoroutine { continuation ->
|
||||||
requireActivity().runOnUiThread {
|
requireActivity().runOnUiThread {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
@@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
updateShowStatsOverlay()
|
updateShowStatsOverlay()
|
||||||
updateSocOverlay()
|
updateSocOverlay()
|
||||||
|
|
||||||
|
initializeOverlayAutoHide()
|
||||||
|
|
||||||
// Re update binding when the specs values get initialized properly
|
// Re update binding when the specs values get initialized properly
|
||||||
binding.inGameMenu.getHeaderView(0).apply {
|
binding.inGameMenu.getHeaderView(0).apply {
|
||||||
val titleView = findViewById<TextView>(R.id.text_game_title)
|
val titleView = findViewById<TextView>(R.id.text_game_title)
|
||||||
@@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
updatePauseMenuEntry(emulationState.isPaused)
|
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() {
|
private fun resetInputOverlay() {
|
||||||
@@ -1035,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||||
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||||
status == BatteryManager.BATTERY_STATUS_FULL
|
status == BatteryManager.BATTERY_STATUS_FULL
|
||||||
|
|
||||||
if (isCharging) {
|
if (isCharging) {
|
||||||
sb.append(" ${getString(R.string.charging)}")
|
sb.append(" ${getString(R.string.charging)}")
|
||||||
@@ -1728,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
|
||||||
private val socUpdateHandler = 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()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -80,6 +80,15 @@ namespace AndroidSettings {
|
|||||||
Settings::Category::Overlay,
|
Settings::Category::Overlay,
|
||||||
Settings::Specialization::Paired, true,
|
Settings::Specialization::Paired, true,
|
||||||
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::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
|
||||||
Settings::Category::Overlay,
|
Settings::Category::Overlay,
|
||||||
Settings::Specialization::Default, true,
|
Settings::Specialization::Default, true,
|
||||||
|
55
src/android/app/src/main/res/layout/dialog_spinbox.xml
Normal file
55
src/android/app/src/main/res/layout/dialog_spinbox.xml
Normal file
@@ -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>
|
@@ -12,6 +12,24 @@
|
|||||||
<string name="app_notification_channel_id" translatable="false">Eden</string>
|
<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_channel_description">Eden Switch emulator notifications</string>
|
||||||
<string name="app_notification_running">Eden is Running</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 -->
|
<!-- Stats Overlay settings -->
|
||||||
|
Reference in New Issue
Block a user